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

# 実行を開始

> 入力契約を満たすパラメータで Data App 実行を開始。結果待ち可。

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

Authentication: API key required (`Authorization: Bearer <API Key>`).

The request body is one instance of the app input contract (`input_schema` from app detail). Validation is strict.

`wait` controls whether this call waits for a result:

* **Omitted**: follow the app `execution.mode` default. `sync` apps wait until a terminal state (capped by the app `execution.timeout_seconds`); `async` apps return a `run_id` immediately.
* **Explicit 0–60**: both modes behave the same and wait at most `wait` seconds. `wait=0` returns as soon as the run is queued. For a `sync` app you can take the `run_id` first, then long-poll with <a href="/docs/jp/datahub/api/reference/runs/get-run">Get a run</a> `wait`.

When the response already reaches a terminal state, `sample_records` may include the first batch. Page the full result with <a href="/docs/jp/datahub/api/reference/runs/get-run-records">Get run records</a>.

`version` pins the run to a historical release (contract and pricing follow that version). `build` is for author debugging: it pins an immutable build snapshot (`run_kind=test`, excluded from public stats, still billed). `version` and `build` are mutually exclusive.

The run gate matches the detail gate: invisible apps return `404`; visible apps that are not accepting runs return `403` (author self-tests are unrestricted); new runs pinned to a yanked version are rejected (`422`).

## Request

### Path parameters

<ParamField path="app_id" type="string" required>
  App reference: `app_<hex>` or `<namespace>/<app_name>`.
</ParamField>

### Query parameters

<ParamField query="wait" type="number">
  Maximum seconds to wait for a terminal state. Omit to use the app mode default; `0` returns immediately after start.

  Range 0 to 60.
</ParamField>

<ParamField query="max_records" type="integer">
  Maximum records to produce. The run ends normally when the cap is reached. Use it to control cost and duration.

  Range ≥ 1.
</ParamField>

<ParamField query="triggered_by" type="string" default="api">
  Channel marker. Default `api`; SDKs and MCP set their own values. Usable as a filter on run list and billing.
</ParamField>

<ParamField query="version" type="string">
  Pin to a specific version. Defaults to the latest version.
</ParamField>

<ParamField query="build" type="string">
  Author debug only. Pin to a build snapshot. Mutually exclusive with `version`.
</ParamField>

### Request body

JSON object shaped by the app `input_schema`. The safest start is to copy an entry from `examples` in app detail and edit it.

### Example request

```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"
```

## Response

### 200 success

```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": []
  }
}
```

The payload is wrapped in `data`. Fields:

<ResponseField name="run_id" type="string" required>
  Run id. Use it for status, records, and cancel.
</ResponseField>

<ResponseField name="namespace" type="string">
  Publisher username.
</ResponseField>

<ResponseField name="app_name" type="string">
  App name.
</ResponseField>

<ResponseField name="app_version" type="string">
  Pinned version. `null` for debug runs.
</ResponseField>

<ResponseField name="build_id" type="string">
  Build snapshot pinned by a debug run. `null` for production runs.
</ResponseField>

<ResponseField name="run_kind" type="string">
  `production` for normal runs; `test` for author debug runs.
</ResponseField>

<ResponseField name="state" type="enum" required>
  Run state. Values: `PENDING` / `QUEUED` / `RUNNING` / `SUCCEEDED` / `PARTIALLY_SUCCEEDED` / `FAILED` / `CANCELLED` / `EXPIRED`.
</ResponseField>

<ResponseField name="input" type="object">
  Input echo. Fields marked `sensitive` in the input contract are redacted.
</ResponseField>

<ResponseField name="progress" type="object">
  Progress. `done` / `total` are reported by the app; `status_text` is a human-readable status string from the 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` when the run partially succeeded or was cancelled. Produced records remain usable.
</ResponseField>

<ResponseField name="cancel_requested" type="boolean">
  `true` while cancellation has been requested and the run is still winding down (cooperative stop / partial-result recovery). Always `false` in terminal states (normalized server-side), so clients need not derive it from `state`.
</ResponseField>

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

<ResponseField name="upstream_ref" type="string">
  Upstream task id, when present.
</ResponseField>

<ResponseField name="created_at" type="string">
  Start time. Also the anchor for time-range filters and billing attribution.
</ResponseField>

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

<ResponseField name="first_started_at" type="string">
  When a worker first claimed the run. Unlike `started_at`, this is never rewritten on retry, so it is the anchor for queue time: queued = `first_started_at - created_at`, and total wall time = `finished_at - first_started_at`. Deriving queue time from `started_at` counts earlier attempts as queueing. `null` only for runs never claimed.
</ResponseField>

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

<ResponseField name="usage" type="object">
  Objective usage metering: how much the run did. Kept separate from billing; this is what evaluations compare against.

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

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

<ResponseField name="billing" type="object">
  Billing ledger = usage × pricing × billing rules. `events[]` lists each billable event with quantity, unit price, and amount; `total` is the sum; `charged` is whether the charge was applied. Complete only after a terminal state.

  <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">
  Error object on failure (`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[]">
  First batch of records when the response is already terminal; otherwise `null`.
</ResponseField>

<ResponseField name="warnings" type="object[]">
  Structured warnings, for example `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>

### Errors

| HTTP | `code`                   | `category`      | Description                                                                                                          |
| ---- | ------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------- |
| 401  | `unauthorized`           | `forbidden`     | Missing or invalid API key.                                                                                          |
| 400  | `invalid-input`          | `invalid_input` | Request body does not satisfy the app input contract. `details[]` lists each field path and reason.                  |
| 404  | `app-not-found`          | `not_found`     | App does not exist, was renamed, or is invisible to the current credential (private / outside share scope).          |
| 403  | `app-not-accepting-runs` | `forbidden`     | App is in maintenance and not accepting new runs. `message` may include a publisher note.                            |
| 402  | `balance-negative`       | `forbidden`     | Wallet balance is negative; new runs are blocked. Top up, then retry the same request.                               |
| 503  | `billing-unavailable`    | `temporary`     | The account has outstanding charges and billing cannot verify balance right now. `retryable` is `true`; retry later. |
| 422  | `version-yanked`         | `invalid_input` | The pinned version was yanked by the publisher and no longer accepts new runs.                                       |

Error responses use `{"error": {code, category, message, retryable}}`. See <a href="/docs/jp/datahub/api/reference/introduction#errors">Errors</a>.

## Client libraries

<CodeGroup>
  ```python Python theme={null}
  # Start and wait for a terminal state (SDK polls internally)
  run = client.call("carol/probe-b", {"product": "p-9001"}, max_records=100)
  print(run["state"], run["billing"]["total"])

  # Start only; do not wait
  run = client.run("carol/probe-b", {"product": "p-9001"}, wait=0)
  run_id = run["run_id"]
  ```

  ```js JavaScript theme={null}
  // Start and wait for a terminal state (SDK polls internally; timeout is in ms)
  const run = await client.call("carol/probe-b", { product: "p-9001" }, { maxRecords: 100, timeout: 120_000 });
  console.log(run.state, run.billing.total);

  // Start only; do not wait
  const started = await client.run("carol/probe-b", { product: "p-9001" }, { wait: 0 });
  const runId = started.run_id;
  ```
</CodeGroup>

## Notes

* State vocabulary: `PENDING`, `QUEUED`, `RUNNING`, `SUCCEEDED`, `PARTIALLY_SUCCEEDED`, `FAILED`, `CANCELLED`, `EXPIRED`. The last five are terminal.
* Do not start a second run for the same goal just because a wait timed out. Poll the existing `run_id` first.
* Failed runs are not billed. Partial success and cancel bill only for produced records.
