> ## Documentation Index
> Fetch the complete documentation index at: https://www.octoparse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Libreria client Python

> Installa, inizializza, mappa i metodi agli endpoint e gestisci gli errori con l’SDK Python ufficiale octoparse-client.

`octoparse-client` è il client Python ufficiale per Data Hub. Wrappa solo l’API REST pubblica `/v1` e dipende solo da `httpx`. I metodi mappano 1:1 sugli endpoint, i nomi parametro coincidono con REST e i valori di ritorno sono il payload grezzo `data` (`dict` / `list`). Versione corrente 0.2.9, Python 3.10+.

## Installazione

```bash theme={null}
pip install octoparse-client
```

## Inizializzazione

```python theme={null}
from octoparse_client import Client

client = Client(api_key="<your API key>")
```

| Parametro  | Descrizione                                                                                                                                                                         |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `api_key`  | API key di Data Hub. Se omessa, viene letta `OCTOPARSE_API_KEY`. Se manca ancora, il client è anonimo e può chiamare solo gli endpoint anonimi.                                     |
| `base_url` | URL del servizio. Se omesso, viene letto `OCTOPARSE_BASE_URL`, con valore predefinito `https://api-datahub.octoparse.com`. Passalo esplicitamente per ambienti locali o di staging. |
| `timeout`  | Timeout HTTP per request in secondi. Default 90.                                                                                                                                    |

`Client` supporta lo statement `with` e chiude le connessioni in exit. Puoi anche chiamare `client.close()`. L’SDK non carica file `.env`; caricali tu se serve.

## Mappa metodo–endpoint

| Metodo                                                                 | Endpoint                                  | Note                                          |
| ---------------------------------------------------------------------- | ----------------------------------------- | --------------------------------------------- |
| `meta()`                                                               | `GET /v1/meta`                            | Costanti di piattaforma                       |
| `search(q, *, limit, offset, ...)`                                     | `GET /v1/data-apps`                       | Una pagina di card, inclusa `pagination`      |
| `get_app(app_id)`                                                      | `GET /v1/data-apps/{app_id}`              | Dettaglio app completo                        |
| `run(app_id, inputs, *, wait, max_records, version, build)`            | `POST /v1/data-apps/{app_id}/runs`        | Avvia un’esecuzione                           |
| `call(app_id, inputs, *, max_records, timeout, raise_on_failure, ...)` | `POST` + polling                          | Helper bloccante: attendi terminale o timeout |
| `get_run(run_id, *, wait)`                                             | `GET /v1/runs/{run_id}`                   | Snapshot di stato e fatturazione              |
| `list_runs_page(filters)`                                              | `GET /v1/runs`                            | Una pagina di run, inclusa `pagination`       |
| `list_runs(filters)`                                                   | `GET /v1/runs`                            | Una pagina di run, solo items                 |
| `iterate_runs(filters)`                                                | `GET /v1/runs`                            | Iterator che pagina automaticamente           |
| `cancel(run_id)`                                                       | `POST /v1/runs/{run_id}/cancel`           | Annulla un’esecuzione                         |
| `get_records(run_id, *, offset, limit, fields)`                        | `GET /v1/runs/{run_id}/records`           | Una pagina di record risultato                |
| `iterate_records(run_id, *, batch, fields)`                            | `GET /v1/runs/{run_id}/records`           | Iterator che pagina automaticamente           |
| `export_records(run_id, format)`                                       | `GET /v1/runs/{run_id}/records`           | Export completo come testo `jsonl`/`csv`      |
| `list_datasets(*, offset, limit)`                                      | `GET /v1/datasets`                        | Elenco dataset                                |
| `set_dataset_retention(dataset_id, retained)`                          | `PUT /v1/datasets/{dataset_id}/retention` | Imposta flag di retention                     |
| `get_dataset_records(dataset_id, *, offset, limit, fields)`            | `GET /v1/datasets/{dataset_id}/records`   | Una pagina di record dataset                  |
| `iterate_dataset_records(dataset_id, *, batch, fields)`                | `GET /v1/datasets/{dataset_id}/records`   | Iterator che pagina automaticamente           |
| `account()`                                                            | `GET /v1/account`                         | Info account e spesa cumulativa               |
| `billing(*, group_by, created_from, created_to, tz_offset)`            | `GET /v1/billing`                         | Aggregazione fatturazione                     |

I `filters` della lista dei run supportano `status`, `data_app`, `triggered_by`, `run_kind`, `credential`, `created_from`, `created_to`, più `offset` / `limit`. Gli endpoint di pubblicazione, operativi e dei secret non sono incapsulati; chiama direttamente la REST API.

## Uso tipico

```python theme={null}
from octoparse_client import Client, ApiError, RunFailed

with Client(api_key="<your API key>") as client:
    # Discover
    page = client.search("reviews", limit=5)
    for card in page["items"]:
        print(card["app_id"], card["namespace"], card["app_name"])
    detail = client.get_app("carol/reviews-query")

    # Run and consume. Timeouts are seconds. TimeoutError does not cancel the server-side run.
    run = client.call(
        "carol/reviews-query",
        {"product": "p-9001"},
        max_records=100,
        timeout=120,
        raise_on_failure=True,
    )
    for record in client.iterate_records(run["run_id"]):
        print(record)

    # Non-blocking: start, then poll yourself
    started = client.run("carol/reviews-query", {"product": "p-9002"}, wait=0)
    polled = client.get_run(started["run_id"], wait=60)

    # Reconcile spend
    print(client.billing(group_by="data_app", tz_offset=480))
    for r in client.iterate_runs(created_from="2026-09-01T00:00:00+08:00"):
        print(r["run_id"], r["status"], r["billing"]["total"])

    # Results are kept 90 days by default; mark datasets you need longer
    client.set_dataset_retention(run["dataset_id"], True)
```

## Gestione errori

Le risposte di errore dell'API sollevano `ApiError`. I campi corrispondono all'oggetto REST `error`:

| Attributo     | Significato                                                                              |
| ------------- | ---------------------------------------------------------------------------------------- |
| `status_code` | Stato HTTP                                                                               |
| `code`        | ID di errore stabile per la gestione dei casi                                            |
| `category`    | Classe generale                                                                          |
| `message`     | Descrizione in inglese                                                                   |
| `retryable`   | Se la stessa request può essere ritentata                                                |
| `details`     | Problemi a livello di campo quando la validazione dell'input fallisce, altrimenti `None` |

```python theme={null}
from octoparse_client import ApiError

try:
    client.run("carol/reviews-query", {})
except ApiError as e:
    if e.code == "invalid-input":
        for d in e.details or []:
            print(d["path"], d["message"])
    elif e.retryable:
        # back off and retry
        ...
    else:
        raise
```

`RunFailed` viene sollevato quando `call(..., raise_on_failure=True)` termina in `FAILED` / `CANCELLED` / `EXPIRED`. Il suo attributo `run` contiene l'oggetto run completo.

## Riferimenti app

`get_app()`, `run()`, `call()` e il filtro lista run `data_app` accettano entrambe le forme: a due segmenti `<namespace>/<app_name>` (leggibile, si rompe dopo rename) e `app_id` stabile (`app_<hex>`, sopravvive al rename). Per integrazioni longeve salva `app_id`.
