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

인증: API 키 필요(`Authorization: Bearer <API Key>`).

요청 본문은 App 입력 계약(App 상세의 `input_schema`)의 인스턴스 하나입니다. 검증은 엄격합니다.

`wait`는 이 호출이 결과를 기다릴지 여부를 제어합니다:

* **생략 시**: App의 `execution.mode` 기본값을 따릅니다. `sync` App은 최종 상태까지 기다리고(App의 `execution.timeout_seconds`가 상한), `async` App은 즉시 `run_id`를 반환합니다.
* **0–60 명시 시**: 두 모드 모두 같게 동작하며 최대 `wait`초 동안 기다립니다. `wait=0`은 실행이 대기열에 들어가는 즉시 반환합니다. `sync` App이라도 먼저 `run_id`를 받은 뒤 <a href="/docs/ko/datahub/api/reference/runs/get-run">Get a run</a>의 `wait`으로 롱 폴링할 수 있습니다.

응답 시점에 이미 최종 상태에 도달했다면 `sample_records`에 첫 번째 배치가 포함될 수 있습니다. 전체 결과는 <a href="/docs/ko/datahub/api/reference/runs/get-run-records">Get run records</a>로 페이지를 넘기며 가져옵니다.

`version`은 실행을 과거 Release에 고정합니다(계약과 가격은 해당 버전을 따름). `build`는 작성자 디버깅용으로, 변경할 수 없는 Build 스냅샷에 고정합니다(`run_kind=test`, 공개 통계에서 제외되지만 요금은 청구됨). `version`과 `build`는 함께 지정할 수 없습니다.

실행 게이트는 상세 게이트와 같습니다: 보이지 않는 App은 `404`를 반환합니다. 보이지만 실행을 받지 않는 App은 `403`을 반환합니다(작성자의 자체 테스트는 제한 없음). yank된 버전에 고정한 새 실행은 거부됩니다(`422`).

## 요청

### 경로 파라미터

<ParamField path="app_id" type="string" required>
  App 참조: `app_<hex>` 또는 `<namespace>/<app_name>`.
</ParamField>

### 쿼리 파라미터

<ParamField query="wait" type="number">
  최종 상태까지 기다리는 최대 초. 생략하면 App 모드의 기본값을 사용합니다. `0`은 시작 직후 바로 반환합니다.

  범위 0\~60.
</ParamField>

<ParamField query="max_records" type="integer">
  생성할 최대 레코드 수. 상한에 도달하면 실행이 정상 종료됩니다. 비용과 소요 시간을 제어하는 데 사용합니다.

  범위는 1 이상입니다.
</ParamField>

<ParamField query="triggered_by" type="string" default="api">
  채널 표시. 기본값은 `api`이며, SDK와 MCP는 각자의 값을 설정합니다. 실행 목록과 청구의 필터로 사용할 수 있습니다.
</ParamField>

<ParamField query="version" type="string">
  특정 버전에 고정합니다. 기본값은 최신 버전입니다.
</ParamField>

<ParamField query="build" type="string">
  작성자 디버그 전용. Build 스냅샷에 고정합니다. `version`과 함께 지정할 수 없습니다.
</ParamField>

### 요청 body

App의 `input_schema`에 맞춘 JSON 객체. 가장 안전한 시작 방법은 App 상세의 `examples`에서 항목 하나를 복사해 수정하는 것입니다.

### 요청 예시

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

## 응답

### 200 성공

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

페이로드는 `data`로 감쌉니다. 필드:

<ResponseField name="run_id" type="string" required>
  실행 ID. 상태 확인, 레코드 조회, 취소에 사용합니다.
</ResponseField>

<ResponseField name="namespace" type="string">
  게시자 사용자 이름.
</ResponseField>

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

<ResponseField name="app_version" type="string">
  고정된 버전. 디버그 실행은 `null`.
</ResponseField>

<ResponseField name="build_id" type="string">
  디버그 실행이 고정한 Build 스냅샷. 프로덕션 실행에서는 `null`.
</ResponseField>

<ResponseField name="run_kind" type="string">
  일반은 `production`. 작성자 디버그는 `test`.
</ResponseField>

<ResponseField name="state" type="enum" required>
  실행 상태. 값: `PENDING` / `QUEUED` / `RUNNING` / `SUCCEEDED` / `PARTIALLY_SUCCEEDED` / `FAILED` / `CANCELLED` / `EXPIRED`.
</ResponseField>

<ResponseField name="input" type="object">
  입력 에코. 입력 계약에서 `sensitive`로 표시된 필드는 마스킹됩니다.
</ResponseField>

<ResponseField name="progress" type="object">
  진행 상황. `done` / `total`은 App이 보고하며, `status_text`는 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">
  결과 데이터셋 ID.
</ResponseField>

<ResponseField name="partial" type="boolean">
  실행이 부분적으로 성공했거나 취소되면 `true`. 생성된 레코드는 계속 사용할 수 있습니다.
</ResponseField>

<ResponseField name="cancel_requested" type="boolean">
  취소 요청 후 정리 중이면 `true`(협조 중지/부분 결과 회수). 종료 시 항상 `false`(서버 정규화). `state`에서 유도 불필요.
</ResponseField>

<ResponseField name="triggered_by" type="string">
  시작 채널.
</ResponseField>

<ResponseField name="upstream_ref" type="string">
  업스트림 작업 ID(있는 경우).
</ResponseField>

<ResponseField name="created_at" type="string">
  시작 시각. 기간 필터와 청구 귀속의 기준이기도 합니다.
</ResponseField>

<ResponseField name="started_at" type="string">
  가장 최근 실행 시작 시각. 재시도할 때마다 다시 기록됩니다.
</ResponseField>

<ResponseField name="first_started_at" type="string">
  워커가 처음 실행을 가져온 시각. `started_at`과 달리 재시도에 덮어쓰지 않아 벽시계 기준: queued = `first_started_at - created_at`, 총 벽시간 = `finished_at - first_started_at`. `started_at`으로 queued를 구하면 이전 시도를 대기로 오산. 미할당만 `null`.
</ResponseField>

<ResponseField name="finished_at" type="string">
  종료 시각.
</ResponseField>

<ResponseField name="usage" type="object">
  객관 사용량: 실행이 한 일. 과금과 분리. 평가 비교 기준.

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

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

<ResponseField name="billing" type="object">
  청구 원장 = 사용량 × 가격 × 청구 규칙. `events[]`는 청구 대상 이벤트를 수량, 단가, 금액과 함께 나열하고, `total`은 그 합계, `charged`는 청구가 적용되었는지 여부입니다. 최종 상태에 도달해야 확정됩니다.

  <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">
  실패 시 오류 객체(`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[]">
  응답 시점에 이미 최종 상태라면 첫 번째 배치의 레코드, 그렇지 않으면 `null`.
</ResponseField>

<ResponseField name="warnings" type="object[]">
  구조화된 경고(예: `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>

### 오류

| HTTP | `code`                   | `category`      | 설명                                                                          |
| ---- | ------------------------ | --------------- | --------------------------------------------------------------------------- |
| 401  | `unauthorized`           | `forbidden`     | API 키 누락 또는 무효.                                                             |
| 400  | `invalid-input`          | `invalid_input` | 요청 본문이 App 입력 계약을 충족하지 않습니다. `details[]`에 각 필드 경로와 이유가 나열됩니다.               |
| 404  | `app-not-found`          | `not_found`     | App이 없거나 이름이 바뀌었거나 현재 자격 증명에 보이지 않음(비공개/공유 범위 밖).                           |
| 403  | `app-not-accepting-runs` | `forbidden`     | App이 유지보수 중이며 새 실행을 받지 않습니다. `message`에 게시자의 안내가 포함될 수 있습니다.                |
| 402  | `balance-negative`       | `forbidden`     | 지갑 잔액이 마이너스여서 새 실행이 차단됩니다. 충전한 뒤 같은 요청을 재시도하세요.                             |
| 503  | `billing-unavailable`    | `temporary`     | 계정에 미결제 요금이 있어 현재 청구 쪽에서 잔액을 확인할 수 없습니다. `retryable`이 `true`이므로 나중에 재시도하세요. |
| 422  | `version-yanked`         | `invalid_input` | 고정한 버전이 게시자에 의해 yank되어 더 이상 새 실행을 받지 않습니다.                                  |

오류 응답은 `{"error": {code, category, message, retryable}}`입니다. <a href="/docs/ko/datahub/api/reference/introduction#errors">오류</a> 참고.

## 클라이언트 라이브러리

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

## 참고 사항

* 상태 용어: `PENDING`, `QUEUED`, `RUNNING`, `SUCCEEDED`, `PARTIALLY_SUCCEEDED`, `FAILED`, `CANCELLED`, `EXPIRED`. 마지막 다섯 개가 최종 상태입니다.
* 대기가 타임아웃되었다고 같은 목적의 두 번째 실행을 시작하지 마세요. 먼저 기존 `run_id`를 폴링하세요.
* 실패한 실행에는 요금이 청구되지 않습니다. 부분 성공과 취소는 생성된 레코드에 대해서만 청구됩니다.
