---
title: 대량 실행
description: concurrency 창으로 Format run을 최대 100개까지 큐에 넣습니다. POST …/bulk-runs, GET /v1/format-run-queues/{id} 폴링, 자식 run은 GET /v1/format-runs/{run_id}입니다.
---

노트북에서 fan-out을 돌리지 않고 Format run 목록을 밤새 남겨 둘 수 있습니다. bulk 요청은
**다른 실행 엔진이 아니라, 보통 Format run의 서버 측 큐**입니다. 각 item은
`POST …/runs`와 같은 작업 단위입니다. 샌드박스 하나, agent 턴 하나,
[run receipt](/formats/runs) 하나입니다.

정확한 요청·응답 스키마는 라이브 OpenAPI
(`https://api.sume.com/reference/json`)에서 가져옵니다. 여기 표는 읽기 쉬운 요약이며,
두 번째 스키마가 아닙니다.

## 엔드포인트

| Method | Path | Scope |
|---|---|---|
| `POST` | `/v1/formats/{format_id}/bulk-runs` | `formats:write` |
| `POST` | `/v1/formats/{handle}/{slug}/bulk-runs` | `formats:write` |
| `GET` | `/v1/format-run-queues/{queue_id}` | `formats:read` |

두 POST 경로는 쌍입니다. 새 연동에서는 `{handle}/{slug}`를 선호하세요. 불투명 `skl_…`
경로는 계속 유효합니다. 요청 본문, 헤더, 스코프, 큐 receipt는 동일합니다.

큐 목록이나 큐 전체 취소 공개 엔드포인트는 없습니다. 자식 run은
`POST /v1/format-runs/{run_id}/cancel`로 취소합니다 — [취소](/formats/runs#cancel)를
참고하세요.

## 인증

단일 [Format run](/formats/call)과 같은 API 키 규칙입니다.

1. Bearer API 키 (`Authorization: Bearer $SUME_API_KEY`).
2. 큐를 만들려면 `formats:write`, 폴링하려면 `formats:read`가 필요합니다.
3. **팀 워크스페이스**가 소유한 Format이면, 키가 **그 워크스페이스에서** 발급된 것이어야
   합니다.
4. 서비스 계정 키로는 Format run이나 bulk 큐를 만들 수 없습니다. `403
   insufficient_scope`와 `details.reason`이
   `service_account_format_runs_unsupported`인 응답으로 실패합니다.

Format API 호출 트리거가 출시되기 전에 만든 키에는 이 스코프가 없습니다. 기존 키에
스코프를 추가할 수 없으니 새 키를 만드세요.
[Format 호출하기](/formats/call#스코프)와
[팀 Format에는 팀 키가 필요합니다](/formats/call#팀-format에는-팀-키가-필요합니다)를
참고하세요.

| Scope | Needed for |
|---|---|
| `formats:write` | `POST …/bulk-runs` (및 단일 run 생성·취소). |
| `formats:read` | `GET /v1/format-run-queues/{queue_id}` (및 Format·run 읽기). |

## 큐 만들기

```bash
curl -sS -X POST "https://api.sume.com/v1/formats/chase/product-promo/bulk-runs" \
  -H "Authorization: Bearer $SUME_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "concurrency": 3,
    "items": [
      { "instruction": "clip 1", "input": { "url": "https://example.com/1.jpg" } },
      { "instruction": "clip 2", "input": { "url": "https://example.com/2.jpg" } },
      { "instruction": "clip 3", "input": { "url": "https://example.com/3.jpg" } },
      { "instruction": "clip 4", "input": { "url": "https://example.com/4.jpg" } }
    ]
  }'
```

불투명 경로:

```bash
curl -sS -X POST "https://api.sume.com/v1/formats/skl_.../bulk-runs" \
  -H "Authorization: Bearer $SUME_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "concurrency": 3,
    "items": [
      { "instruction": "clip 1" },
      { "instruction": "clip 2" }
    ]
  }'
```

수락된 create는 `{ "data": { …queue } }`와 함께 **`202`**를 반환합니다. 목록이 창보다
길면, 그 receipt에서 이미 처음 `concurrency`개가 실행 중입니다.

### 요청 본문

| Field | Notes |
|---|---|
| `concurrency` | 필수 정수 **1–16**. 동시에 띄워 둘 자식 Format run 수입니다. |
| `items` | 필수 배열, **1–100**개, 제출 순서입니다. 각 항목은 [`POST …/runs`](/formats/call#요청-본문)와 같은 본문입니다. |
| `idempotency_key` | `Idempotency-Key` 헤더의 본문 표기입니다. 둘 다 보내면 헤더가 이깁니다. |

알 수 없는 최상위 필드는 거절됩니다. `items`는 필수입니다 — `{ "concurrency": 3 }`은
빈 큐가 아니라 `400`입니다.

각 item은 `instruction`, `input`, `previous_run_id`, `attachments` 중 하나 이상을
이름 붙여야 합니다. 잘못된 item은 큐가 생기기 **전에** create를 실패시킵니다 (`400
invalid_request`, `details.index`). 아무것도 디스패치되지 않습니다.

bulk 컨트롤러가 single-flight를 소유합니다. 모든 item은 `on_active_run: "allow"`로
실행됩니다. item에 `skip`이나 `reject`를 넣어도 이 Format의 첫 진행 중 run에서 창이
멈추지 않습니다. 자식 run에는 워크스페이스 generation concurrency가 그대로 적용됩니다.

item의 `communication.webhook_url`은 단일 run과 같습니다. 자식마다 종료 웹훅을 등록할
수 있습니다. **큐 객체에는 웹훅이 없습니다.** 큐 단위 콜백을 기대하지 마세요.

## 큐 receipt

`data`는 `format.run_queue`입니다.

```json
{
  "data": {
    "id": "frq_...",
    "object": "format.run_queue",
    "format": {
      "id": "skl_...",
      "slug": "product-promo",
      "title": "Product promo",
      "version": 3
    },
    "concurrency": 3,
    "status": "running",
    "counts": {
      "total": 4,
      "queued": 1,
      "running": 3,
      "completed": 0,
      "failed": 0,
      "canceled": 0
    },
    "items": [
      { "index": 0, "status": "running", "run_id": "run_...", "error": null },
      { "index": 1, "status": "running", "run_id": "run_...", "error": null },
      { "index": 2, "status": "running", "run_id": "run_...", "error": null },
      { "index": 3, "status": "queued", "run_id": null, "error": null }
    ],
    "created_at": "2026-08-24T00:00:00.000Z",
    "updated_at": "2026-08-24T00:00:00.000Z",
    "finished_at": null,
    "status_url": "https://api.sume.com/v1/format-run-queues/frq_..."
  }
}
```

| Field | Notes |
|---|---|
| `id` | 큐 id (`frq_…`). |
| `object` | 항상 `format.run_queue`입니다. |
| `format` | `{ "id", "slug", "title", "version" }`. `id`는 불투명 `skl_…`입니다. |
| `concurrency` | 보낸 창 (1–16). |
| `status` | `queued` / `running` / `completed`. 아래를 보세요. |
| `counts` | `total`, `queued`, `running`, `completed`, `failed`, `canceled`. 모두 필수입니다. |
| `items` | 제출한 item마다 한 행이며, 같은 순서입니다. |
| `created_at`, `updated_at` | ISO-8601. |
| `finished_at` | 큐 status가 `completed`가 되면 설정되고, 그 전에는 `null`입니다. |
| `status_url` | `GET /v1/format-run-queues/{id}` — 진행은 이 URL을 폴링하세요. |

### 큐 status

| Status | Meaning |
|---|---|
| `queued` | 아직 아무것도 디스패치되지 않았습니다. |
| `running` | concurrency 창이 목록을 비우는 중입니다. |
| `completed` | **모든 item이 종료 상태입니다.** 실패는 `counts`를 보세요 — 큐 `completed`는 "전부 성공"이 아닙니다. |

`counts.total`은 `items.length`입니다. `counts.running`은 API가 아직 진행 중으로 보는
item을 포함합니다 (`run_id`가 아직 없는 클레임된 item도 `running`입니다).

## Items

| Field | Notes |
|---|---|
| `index` | 제출한 `items` 배열의 0부터 시작하는 위치입니다. |
| `status` | `queued` / `running` / `completed` / `failed` / `canceled`. |
| `run_id` | 디스패치된 뒤의 자식 Format run id입니다. queued이면 `null`이고, 자식 run이 시작되기 전에 실패한 item도 `null`입니다. 전체 receipt는 `GET /v1/format-runs/{run_id}`입니다. |
| `error` | `{ "code", "message" }`이거나 `null`입니다. |

| Item status | Meaning |
|---|---|
| `queued` | 아직 시작되지 않았습니다. `run_id`는 `null`입니다. |
| `running` | concurrency 창 안입니다. |
| `completed` | 자식 run이 `completed`입니다. 종료이며 슬롯을 비웁니다. |
| `failed` | 자식 run이 `failed`(또는 여기서 `failed`로 기록되는 `skipped`)이거나, 자식이 시작되지 못했습니다. 종료이며 슬롯을 비웁니다. |
| `canceled` | 자식 run이 취소되었습니다. 종료이며 슬롯을 비웁니다. |

run을 시작하지 못하고 실패한 item도 그 `index`를 차지하며 `run_id`는 `null`, `error`는
그 생성 실패입니다 (예: `format_run_failed_to_start`). 나머지 큐는 계속됩니다.

자식 run이 끝나면 `error`는 다음과 같습니다.

| 자식 run | Item `error` |
|---|---|
| `completed` | `null` |
| `failed` | `{ "code": "format_run_failed", "message": "The Format run failed." }` |
| `canceled` | `{ "code": "format_run_canceled", "message": "The Format run was canceled." }` |

자식이 왜 실패했는지는 큐 item만이 아니라 run receipt
(`GET /v1/format-runs/{run_id}`)에서 읽으세요.

## concurrency 창

서버는 `concurrency`개의 자식 run을 동시에 띄운 채, 슬롯이 열리는 즉시 다음 queued
item을 시작해 목록을 비웁니다. 클라이언트가 돌리는 fan-out이 아닙니다.

진행 중 슬롯은 공개 status가 `running`인 item입니다. `completed`, `failed`,
`canceled`는 종료이며 **슬롯을 비웁니다**. 진행 중 자식이 `completed`나 `failed`(흔한
배수 경로)가 되면 다음 queued item이 바로 시작해 창을 가득 유지합니다. `canceled`
자식도 같습니다.

create가 이미 창을 채웁니다. `concurrency: 3`에 item 8개면 `202` receipt는
`running` 3개와 `queued` 5개입니다. item 0이 끝나면 item 3이 시작되고, 남은 item이
3개 미만이 될 때까지 창은 3으로 유지됩니다.

진행을 여러 번 폴링하거나 advance해도 창은 `concurrency`를 넘지 않습니다.

자식 run은 보통 Format-run 접수(지갑, 워크스페이스 generation concurrency, spend cap)를
그대로 탑니다. 시작에 실패한 자식은 그 **item**이 `failed`가 됩니다. 큐 create는 이미
`202`를 반환한 뒤입니다.

## 큐 폴링

`status_url`을 따르거나, `id`로 `GET /v1/format-run-queues/{queue_id}`를 만드세요.

```bash
curl -sS "https://api.sume.com/v1/format-run-queues/$QUEUE_ID" \
  -H "Authorization: Bearer $SUME_API_KEY"
```

`200`은 create와 같은 큐 객체를 반환합니다. 대시보드에는 `counts`, 행별 `run_id` /
`error`에는 `items`를 쓰세요.

모든 item이 종료이면 큐는 `completed`이고 `finished_at`이 설정됩니다.
`counts.failed`와 `counts.canceled`로 분기하세요. 큐 `completed`를 성공으로 취급하지
마세요.

볼 수 없는 큐는 없는 큐와 같습니다. `404 format_run_queue_not_found` (알 수 없는 id,
또는 다른 소유자의 큐).

## 자식 run

디스패치된 각 item은 보통 Format run입니다.

```bash
curl -sS "https://api.sume.com/v1/format-runs/$RUN_ID" \
  -H "Authorization: Bearer $SUME_API_KEY"
```

그 receipt의 `status_url` / `result_url` / `events_url` / `cancel_url`은
[실행과 결과](/formats/runs)와 같습니다. 큐는 그 엔드포인트를 대체하지 않고, 위에
counts와 item 상태를 더합니다.

자식 취소(`POST /v1/format-runs/{run_id}/cancel`)는 그 큐 item을 `canceled`로 표시하고
슬롯을 다음 queued item에 넘깁니다.

## 멱등성

create에 `Idempotency-Key`를 보내세요 (헤더. 본문 `idempotency_key`도 받으며, 헤더가
이깁니다). 키는 Format 하나에 범위가 묶입니다.

| 재시도 | 결과 |
|---|---|
| 같은 키, 같은 `{ concurrency, items }` | `202`와 기존 큐. |
| 같은 키, 다른 페이로드 | `409 idempotency_conflict` (`details.queue_id`가 원본을 가리킵니다). |

단일 run과 달리 bulk 재시도는 **`202`로 남습니다.** 큐 객체에는 `idempotency_hit`
필드가 없습니다.

큐 단위 웹훅은 없고, 위의 헤더/본문 키 외에 큐 전용 멱등성 동작도 없습니다.

## 오류

Create (`POST …/bulk-runs`):

| Code | Status | What to do |
|---|---|---|
| `unauthorized` | 401 | 없거나, 형식이 틀리거나, 취소되었거나, 알 수 없는 API 키입니다. |
| `insufficient_scope` | 403 | 키에 `formats:write`가 없거나 서비스 계정 키입니다 (`details.reason`은 `service_account_format_runs_unsupported`). `next_action`은 `authenticate`입니다. 기존 키에 스코프를 덧붙일 수는 없으니 새 키를 만드세요. 빠진 스코프는 `format_not_found`가 아닙니다. |
| `workspace_key_required` | 403 | 팀 Format인데 개인 키입니다. `details.workspace_id`에서 만든 키를 쓰세요. |
| `invalid_request` | 400 | `concurrency`가 1–16 정수가 아니거나, `items`가 없거나 비었거나 100개를 넘거나, item이 객체가 아니거나, `items[i]`가 `instruction` / `input` / `previous_run_id` / `attachments` 중 아무것도 없는 경우입니다. 잘못된 item은 `details.index`가 가리킵니다. |
| `format_not_found` | 404 | 알 수 없거나, 보관되었거나, 이 키의 워크스페이스 밖이거나, 멤버가 아닌 팀 handle입니다. |
| `format_api_trigger_disabled` | 409 | 이 Format의 API 호출 트리거가 꺼져 있습니다. |
| `format_inactive` | 409 | Format이 비활성입니다. |
| `idempotency_conflict` | 409 | 그 `Idempotency-Key`가 다른 bulk 페이로드로 이미 사용되었습니다. |
| `invalid_attachment` / `attachment_not_found` / `attachment_too_large` / `attachment_fetch_failed` | 400 / 413 / 502 | 큐가 생기기 **전에** item의 `attachments`를 해석하다 발생합니다 — [Format 호출하기](/formats/call#오류)와 같은 코드입니다. |
| `rate_limited` | 429 | `retry-after`초를 기다리세요. create는 write 예산을 씁니다. |
| `studio_agent_upstream_unavailable` | 503 | Sume 쪽 장애입니다. 나중에 재시도하세요. |

폴링 (`GET /v1/format-run-queues/{queue_id}`):

| Code | Status | What to do |
|---|---|---|
| `unauthorized` | 401 | 없거나 잘못된 API 키입니다. |
| `insufficient_scope` | 403 | 키에 `formats:read`가 없습니다. `details.required_scope`가 가리킵니다. |
| `format_run_queue_not_found` | 404 | 알 수 없는 큐 id이거나, 다른 소유자의 큐입니다. |
| `rate_limited` | 429 | `retry-after`를 기다리세요. 폴링은 create와 별개인 **read** 예산을 씁니다. |
| `studio_agent_upstream_unavailable` | 503 | 나중에 재시도하세요. 큐는 계속 비워집니다. |

폴링 루프의 `429`나 `503`은 일시적입니다. 큐는 계속 동작합니다. 실패한 큐로 보지 말고
backoff하세요.

`202` 이후 자식의 지갑·접수 실패는 create를 실패시키지 않습니다. 그 item이 해당
create-run 오류와 함께 `failed`가 되고, 창은 남은 `queued` item으로 채워집니다.

## 처음부터 끝까지

```bash
export SUME_API_KEY="sume_live_..."

QUEUE=$(curl -sS -X POST "https://api.sume.com/v1/formats/chase/product-promo/bulk-runs" \
  -H "Authorization: Bearer $SUME_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
        "concurrency": 3,
        "items": [
          { "instruction": "clip 1", "input": { "url": "https://example.com/1.jpg" } },
          { "instruction": "clip 2", "input": { "url": "https://example.com/2.jpg" } },
          { "instruction": "clip 3", "input": { "url": "https://example.com/3.jpg" } },
          { "instruction": "clip 4", "input": { "url": "https://example.com/4.jpg" } }
        ]
      }')

QUEUE_ID=$(echo "$QUEUE" | jq -r '.data.id')
STATUS=$(echo "$QUEUE" | jq -r '.data.status')

while [ "$STATUS" = "queued" ] || [ "$STATUS" = "running" ]; do
  sleep 5
  QUEUE=$(curl -sS "https://api.sume.com/v1/format-run-queues/$QUEUE_ID" \
    -H "Authorization: Bearer $SUME_API_KEY")
  STATUS=$(echo "$QUEUE" | jq -r '.data.status')
done

echo "$QUEUE" | jq '.data.counts'
# 큐 status completed는 모든 item이 종료라는 뜻입니다 — counts.failed를 보세요.
```

프로덕션에서는 고정 5초 sleep 대신 exponential backoff를 쓰세요. Format이 비디오를
만들면 자식 하나도 분 단위입니다. 큐를 매초 폴링해도 이득이 없고 rate limit만
소모합니다.

끝난 자식의 미디어는 `items[]`의 `run_id`를 집어 [실행과 결과](/formats/runs)를
따르세요.

## 다음

- [Format 호출하기](/formats/call) — item별 invoke 계약과 모든 제출 오류
- [실행과 결과](/formats/runs) — 자식 receipt, 폴링, 취소
- [구조화 출력](/formats/structured-output) — item마다 스키마 바인딩
- [Run 웹훅](/agents/run-webhooks) — 자식별 `communication.webhook_url`, 큐 콜백 아님
- [제품에 Format 임베드하기](/cookbooks/embed-a-format) — 단일 run 주변의 파트너 연동
