> ## 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.

# Démarrer une exécution

> Démarre une exécution Data App avec des entrées qui satisfont le contrat d’entrée. Attendez éventuellement le résultat.

**`POST`** `https://api-datahub.octoparse.com/v1/data-apps/{app_id}/runs`

Authentification : clé API requise (`Authorization: Bearer <API Key>`).

Le corps de la requête est une instance du contrat d’entrée de l’app (`input_schema` du détail). La validation est stricte.

`wait` contrôle si cet appel attend un résultat :

* **Omise** : suit le défaut de `execution.mode` de l’app. Les apps `sync` attendent un état terminal (plafonné par `execution.timeout_seconds` de l’app) ; les apps `async` renvoient un `run_id` immédiatement.
* **Explicite 0–60** : les deux modes se comportent de la même façon et attendent au plus `wait` secondes. `wait=0` renvoie dès que l’exécution est en file. Pour une app `sync`, vous pouvez prendre d’abord le `run_id`, puis faire un long-poll avec <a href="/docs/fr/datahub/api/reference/runs/get-run">Obtenir une exécution</a> `wait`.

Quand la réponse atteint déjà un état terminal, `sample_records` peut inclure le premier lot. Paginer le résultat complet avec <a href="/docs/fr/datahub/api/reference/runs/get-run-records">Obtenir les enregistrements d’exécution</a>.

`version` épingle l’exécution à une release historique (contrat et tarifs suivent cette version). `build` sert au debug de l’auteur : il épingle un snapshot de build immuable (`run_kind=test`, exclu des stats publiques, toujours facturé). `version` et `build` sont mutuellement exclusifs.

La porte d’exécution correspond à la porte de détail : les apps invisibles renvoient `404` ; les apps visibles qui n’acceptent pas d’exécutions renvoient `403` (les auto-tests de l’auteur sont sans restriction) ; les nouvelles exécutions épinglées à une version yankée sont rejetées (`422`).

## Requête

### Paramètres de chemin

<ParamField path="app_id" type="string" required>
  Référence d’app : `app_<hex>` ou `<namespace>/<app_name>`.
</ParamField>

### Paramètres de requête

<ParamField query="wait" type="number">
  Secondes maximales d’attente d’un état terminal. Omettez pour utiliser le défaut du mode de l’app ; `0` renvoie immédiatement après le démarrage.

  Plage 0 à 60.
</ParamField>

<ParamField query="max_records" type="integer">
  Nombre maximal d’enregistrements à produire. L’exécution se termine normalement quand le plafond est atteint. Utilisez-le pour contrôler coût et durée.

  Plage ≥ 1.
</ParamField>

<ParamField query="triggered_by" type="string" default="api">
  Marqueur de canal. Défaut `api` ; les SDK et MCP définissent leurs propres valeurs. Utilisable comme filtre sur la liste d’exécutions et la facturation.
</ParamField>

<ParamField query="version" type="string">
  Épingler à une version précise. Par défaut la dernière version.
</ParamField>

<ParamField query="build" type="string">
  Debug auteur uniquement. Épingler à un snapshot de build. Mutuellement exclusif avec `version`.
</ParamField>

### Corps de la requête

Objet JSON façonné par `input_schema` de l’app. Le démarrage le plus sûr est de copier une entrée de `examples` dans le détail de l’app et de l’éditer.

### Exemple de requête

```bash theme={null}
curl -X POST \
  -H "Authorization: Bearer $OCTOPARSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"product": "p-9001"}' \
  "https://api-datahub.octoparse.com/v1/data-apps/carol/probe-b/runs?wait=60"
```

## Réponse

### 200 succès

```json theme={null}
{
  "data": {
    "run_id": "run_c62bc0fb8df2",
    "namespace": "carol",
    "app_name": "probe-b",
    "app_version": "0.1.0",
    "build_id": null,
    "run_kind": "production",
    "state": "SUCCEEDED",
    "input": {
      "product": "p-now"
    },
    "progress": {
      "done": 20,
      "total": null,
      "status_text": null
    },
    "dataset_id": "ds_0bd5345d13d0",
    "partial": false,
    "cancel_requested": false,
    "triggered_by": "api",
    "upstream_ref": null,
    "created_at": "2026-09-15T07:45:40.411993+00:00",
    "started_at": "2026-09-15T07:45:40.419610+00:00",
    "first_started_at": "2026-09-15T07:45:40.419610+00:00",
    "finished_at": "2026-09-15T07:45:40.822250+00:00",
    "usage": {
      "metrics": {
        "records_collected": 20
      },
      "duration_ms": 402
    },
    "billing": {
      "events": [
        {
          "event": "record",
          "label": "One record",
          "qty": 20.0,
          "unit_price": 0.001,
          "amount": 0.02,
          "unit_size": null,
          "raw_qty": null
        }
      ],
      "total": 0.02,
      "currency": "USD",
      "charged": true
    },
    "error": null,
    "sample_records": null,
    "warnings": []
  }
}
```

La charge utile est encapsulée dans `data`. Champs :

<ResponseField name="run_id" type="string" required>
  Id de l’exécution. Utilisez-le pour le statut, les enregistrements et l’annulation.
</ResponseField>

<ResponseField name="namespace" type="string">
  Nom d’utilisateur de l’éditeur.
</ResponseField>

<ResponseField name="app_name" type="string">
  Nom de l’app.
</ResponseField>

<ResponseField name="app_version" type="string">
  Version épinglée. `null` pour les exécutions de debug.
</ResponseField>

<ResponseField name="build_id" type="string">
  Snapshot de build épinglé par une exécution de debug. `null` pour les exécutions de production.
</ResponseField>

<ResponseField name="run_kind" type="string">
  `production` pour les exécutions normales ; `test` pour le debug de l’auteur.
</ResponseField>

<ResponseField name="state" type="enum" required>
  État de l’exécution. Valeurs : `PENDING` / `QUEUED` / `RUNNING` / `SUCCEEDED` / `PARTIALLY_SUCCEEDED` / `FAILED` / `CANCELLED` / `EXPIRED`.
</ResponseField>

<ResponseField name="input" type="object">
  Écho de l’entrée. Les champs marqués `sensitive` dans le contrat d’entrée sont expurgés.
</ResponseField>

<ResponseField name="progress" type="object">
  Progression. `done` / `total` sont rapportés par l’app ; `status_text` est une chaîne d’état lisible de l’app.

  <Expandable title="fields">
    <ResponseField name="done" type="integer">
      —
    </ResponseField>

    <ResponseField name="total" type="integer">
      —
    </ResponseField>

    <ResponseField name="status_text" type="string">
      —
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="dataset_id" type="string">
  Result dataset id.
</ResponseField>

<ResponseField name="partial" type="boolean">
  `true` quand l’exécution a partiellement réussi ou a été annulée. Les enregistrements produits restent utilisables.
</ResponseField>

<ResponseField name="cancel_requested" type="boolean">
  `true` tant que l’annulation a été demandée et que l’exécution se termine encore (arrêt coopératif / récupération de résultat partiel). Toujours `false` en états terminaux (normalisé côté serveur), donc les clients n’ont pas à le dériver de `state`.
</ResponseField>

<ResponseField name="triggered_by" type="string">
  Start channel.
</ResponseField>

<ResponseField name="upstream_ref" type="string">
  Id de la tâche upstream, le cas échéant.
</ResponseField>

<ResponseField name="created_at" type="string">
  Heure de début. Aussi l’ancre des filtres de plage temporelle et de l’attribution de facturation.
</ResponseField>

<ResponseField name="started_at" type="string">
  Most recent execution start time. Re-stamped on retry.
</ResponseField>

<ResponseField name="first_started_at" type="string">
  Quand un worker a réclamé l’exécution pour la première fois. Contrairement à `started_at`, ce n’est jamais réécrit en cas de réessai, donc c’est l’ancre du temps en file : en file = `first_started_at - created_at`, et temps total mur = `finished_at - first_started_at`. Dériver le temps en file de `started_at` compte les tentatives antérieures comme file. `null` seulement pour les exécutions jamais réclamées.
</ResponseField>

<ResponseField name="finished_at" type="string">
  End time.
</ResponseField>

<ResponseField name="usage" type="object">
  Mesure objective d’usage : ce que l’exécution a fait. Séparée de la facturation ; c’est ce que comparent les évaluations.

  <Expandable title="fields">
    <ResponseField name="metrics" type="object">
      —
    </ResponseField>

    <ResponseField name="duration_ms" type="integer">
      —
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="billing" type="object">
  Grand livre de facturation = usage × tarifs × règles de facturation. `events[]` liste chaque événement facturable avec quantité, prix unitaire et montant ; `total` est la somme ; `charged` indique si le débit a été appliqué. Complet seulement après un état terminal.

  <Expandable title="fields">
    <ResponseField name="events" type="object[]">
      —

      <Expandable title="fields">
        <ResponseField name="event" type="string" required>
          —
        </ResponseField>

        <ResponseField name="label" type="string">
          —
        </ResponseField>

        <ResponseField name="qty" type="number" required>
          —
        </ResponseField>

        <ResponseField name="unit_price" type="number" required>
          —
        </ResponseField>

        <ResponseField name="amount" type="number" required>
          —
        </ResponseField>

        <ResponseField name="unit_size" type="integer">
          —
        </ResponseField>

        <ResponseField name="raw_qty" type="number">
          —
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="total" type="number">
      —
    </ResponseField>

    <ResponseField name="currency" type="string">
      —
    </ResponseField>

    <ResponseField name="charged" type="boolean">
      —
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="error" type="object">
  Objet d’erreur en cas d’échec (`code` / `category` / `message` / `retryable`).

  <Expandable title="fields">
    <ResponseField name="code" type="string" required>
      —
    </ResponseField>

    <ResponseField name="category" type="string">
      —
    </ResponseField>

    <ResponseField name="message" type="string" required>
      —
    </ResponseField>

    <ResponseField name="retryable" type="boolean">
      —
    </ResponseField>

    <ResponseField name="retry_after" type="number">
      —
    </ResponseField>

    <ResponseField name="item_index" type="integer">
      —
    </ResponseField>

    <ResponseField name="details" type="object[]">
      —
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="sample_records" type="object[]">
  Premier lot d’enregistrements quand la réponse est déjà terminale ; sinon `null`.
</ResponseField>

<ResponseField name="warnings" type="object[]">
  Avertissements structurés, par exemple `billing-qty-missing`.

  <Expandable title="fields">
    <ResponseField name="code" type="string" required>
      —
    </ResponseField>

    <ResponseField name="event" type="string">
      —
    </ResponseField>

    <ResponseField name="detail" type="object">
      —
    </ResponseField>

    <ResponseField name="count" type="integer">
      —
    </ResponseField>

    <ResponseField name="updated_at" type="string">
      —
    </ResponseField>
  </Expandable>
</ResponseField>

### Erreurs

| HTTP | `code`                   | `category`      | Description                                                                                                                                          |
| ---- | ------------------------ | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401  | `unauthorized`           | `forbidden`     | Clé API manquante ou invalide.                                                                                                                       |
| 400  | `invalid-input`          | `invalid_input` | Le corps ne satisfait pas le contrat d’entrée de l’app. `details[]` liste chaque chemin de champ et la raison.                                       |
| 404  | `app-not-found`          | `not_found`     | L’app n’existe pas, a été renommée, ou est invisible pour la credential actuelle (privée / hors portée de partage).                                  |
| 403  | `app-not-accepting-runs` | `forbidden`     | L’app est en maintenance et n’accepte pas de nouvelles exécutions. `message` peut inclure une note de l’éditeur.                                     |
| 402  | `balance-negative`       | `forbidden`     | Le solde du portefeuille est négatif ; les nouvelles exécutions sont bloquées. Rechargez, puis réessayez la même requête.                            |
| 503  | `billing-unavailable`    | `temporary`     | Le compte a des charges en souffrance et la facturation ne peut pas vérifier le solde pour l’instant. `retryable` vaut `true` ; réessayez plus tard. |
| 422  | `version-yanked`         | `invalid_input` | La version épinglée a été yankée par l’éditeur et n’accepte plus de nouvelles exécutions.                                                            |

Les réponses d’erreur utilisent `{"error": {code, category, message, retryable}}`. Voir <a href="/docs/fr/datahub/api/reference/introduction#errors">Erreurs</a>.

## Bibliothèques clientes

<CodeGroup>
  ```python Python theme={null}
  # Démarrer et attendre un état terminal (le SDK sonde en interne)
  run = client.call("carol/probe-b", {"product": "p-9001"}, max_records=100)
  print(run["state"], run["billing"]["total"])

  # Démarrer seulement ; ne pas attendre
  run = client.run("carol/probe-b", {"product": "p-9001"}, wait=0)
  run_id = run["run_id"]
  ```

  ```js JavaScript theme={null}
  // Démarrer et attendre un état terminal (le SDK sonde en interne ; timeout en ms)
  const run = await client.call("carol/probe-b", { product: "p-9001" }, { maxRecords: 100, timeout: 120_000 });
  console.log(run.state, run.billing.total);

  // Démarrer seulement ; ne pas attendre
  const started = await client.run("carol/probe-b", { product: "p-9001" }, { wait: 0 });
  const runId = started.run_id;
  ```
</CodeGroup>

## Notes

* Vocabulaire d’état : `PENDING`, `QUEUED`, `RUNNING`, `SUCCEEDED`, `PARTIALLY_SUCCEEDED`, `FAILED`, `CANCELLED`, `EXPIRED`. Les cinq derniers sont terminaux.
* Ne démarrez pas une deuxième exécution pour le même objectif seulement parce qu’un wait a expiré. Sondez d’abord le `run_id` existant.
* Les exécutions en échec ne sont pas facturées. Le succès partiel et l’annulation ne facturent que les enregistrements produits.
