---
title: 오류와 비용
description: Formats API가 돌려주는 모든 오류를 단계별로 정리했습니다. 오류 봉투, 코드별 대응, 실패한 run이 담는 것, 웹훅 전달 결과, 크레딧과 spend cap, 요청 한도.
---

API의 모든 오류는 같은 봉투를 쓰고, 모든 코드는 분기할 수 있는 소문자 토큰입니다. 이 페이지는
Formats 표면의 코드 전부를 한 곳에 모아, 언제 발생하는지로 묶었습니다. run이 생기기 전, run을
읽는 동안, run 자체가 실패할 때, 웹훅을 전달하지 못했을 때. 비용과 요청 한도는 끝에 있습니다.

## 오류 봉투

```json
{
  "error": {
    "code": "workspace_key_required",
    "message": "This Format belongs to a team workspace. Use an API key created in that workspace.",
    "request_id": "req_0123456789abcdef0123456789abcdef",
    "category": "auth",
    "stage": "auth",
    "retryable": false,
    "retry_after_seconds": null,
    "public_reason": "workspace_key_required",
    "next_action": "authenticate",
    "details": { "workspace_id": "org_…" }
  }
}
```

| 필드 | 용도 |
|---|---|
| `code` | `switch`로 분기할 안정적인 토큰. `^[a-z0-9_]+$`이며, 문장이 오지 않습니다. |
| `message` | 사람이 읽는 문장이며 바뀔 수 있습니다. 로그에는 남기되, 매칭하지 마세요. |
| `request_id` | `x-sume-request-id` 헤더로도 옵니다. 지원 요청 시 인용하세요. |
| `retryable`, `retry_after_seconds` | 같은 요청을 다시 보내면 성공할 수 있는지, 먼저 얼마나 기다릴지. |
| `next_action` | `authenticate`, `fix_input`, `add_funds`, `retry_later`, `poll_status`, `contact_support`, `none` 중 하나. |
| `category`, `stage`, `public_reason` | 대시보드와 알림용 큰 분류. |
| `details` | 코드별 상세: `required_scope`, `workspace_id`, `violations[]`, `index`, `status`, `scope` 등. 아래 코드마다 적었습니다. |

HTTP status로 먼저 분기하고, 그다음 `code`로 분기하세요. 생성 시점의 `4xx`는 아무것도 실행되지
않았고 아무것도 청구되지 않았다는 뜻이므로, 재시도가 아니라 호출을 고쳐야 합니다.
`403 insufficient_scope`를 루프에서 재시도하는 것이 가장 흔하고 가장 비싼 실수입니다.

## 생성 시 오류

`POST /v1/formats/{handle}/{slug}/runs`와 `POST …/bulk-runs`. 아무것도 실행되지 않고, 청구도
없으며, 실패한 생성은 `Idempotency-Key`를 풀어 줍니다.

| Status | `error.code` | 의미 | 대응 |
|---|---|---|---|
| 400 | `invalid_request` | 본문에 `instruction` / `input` / `previous_run_id` / `attachments` 중 하나도 없음; `output_schema`와 `response_format`을 둘 다 보냄; `input`이 object가 아니거나 최상위 키가 64개를 넘거나 2 MiB를 넘음; `generation_spend_cap_usd`가 `0`이거나 500 초과; `model`이 카탈로그에 없음; bulk 본문의 `concurrency` 또는 `items`가 잘못됨; 웹훅 URL이 공개 HTTPS가 아님; `communication` alias가 서로 충돌. | `message`와 `details`를 읽으세요. |
| 400 | `unknown_parameter` | API가 모르는 최상위 필드. `thread_id`도 여기 해당합니다. `details.errors[].suggestion`이 의도했을 필드를 알려 줍니다. | 이름을 고치거나 제거하세요. |
| 400 | `output_schema_invalid` | 스키마가 지원 subset 밖입니다. `details.violations[]`에 모든 `{ path, rule, message }`가 나열됩니다. | 위반 항목을 각각 고치세요. [지원 스키마](/formats/structured-output#지원-스키마)를 참고하세요. |
| 400 | `invalid_attachment` | `attachments[]` 항목이 잘못됐거나, `attachments[]`와 `input` URL이 공유하는 미디어 예산을 넘었습니다. | [Format API](/formats)의 attachment 오류를 참고하세요. |
| 400 | `attachment_not_found` | 이 워크스페이스에 없는 `asset_id`. | 업로드하거나 URL을 쓰세요. |
| 400 | `previous_run_format_mismatch` | `previous_run_id`가 다른 Format에서 만들어진 run입니다. | 그 Format에서 이어 가세요. |
| 400 | `previous_run_not_resumable` | 그 run에는 이어 갈 것이 없습니다. `details`에 `previous_run_status`, `has_thread`, `artifact_count`가 옵니다. | 새 run을 시작하세요. |
| 401 | `unauthorized` | 키가 없거나, 형식이 잘못됐거나, 폐기됐거나, 다른 호스트의 키이거나, 자격 증명을 두 개 보냈습니다. | 헤더를 고치세요. |
| 402 | `insufficient_credits` | 워크스페이스 지갑이 이 run을 감당할 수 없습니다. `next_action`은 `add_funds`. | 충전하세요. 충전 없이 재시도하면 같은 답이 옵니다. |
| 402 | `organization_wallet_not_provisioned` | 지갑이 아직 준비되지 않은 조직 워크스페이스. | 관리자가 충전해야 합니다. |
| 403 | `insufficient_scope` | 키에 `formats:write`가 없거나(`details.required_scope`), 서비스 계정 키입니다(`details.reason: service_account_format_runs_unsupported`). | 스코프를 가진 새 키를 만드세요. 기존 키에는 스코프를 추가할 수 없습니다. |
| 403 | `workspace_key_required` | 팀 Format인데 개인 키입니다. `details.workspace_id`가 워크스페이스를 가리킵니다. | 그 워크스페이스에서 키를 만드세요. |
| 404 | `format_not_found` | 모르는 handle이나 slug, 보관된 Format, 또는 키의 워크스페이스 밖 Format. | 주소와 키를 확인하세요. |
| 404 | `previous_run_not_found` | `previous_run_id`가 모르는 값이거나 내 것이 아닙니다. | id를 확인하세요. |
| 409 | `format_inactive` | Format이 inactive입니다. | 대시보드에서 활성화하세요. |
| 409 | `format_api_trigger_disabled` | 이 Format의 API 트리거가 꺼져 있습니다. | 대시보드에서 켜세요. |
| 409 | `format_run_in_progress` | `on_active_run: "reject"`인데 이미 진행 중인 run이 있습니다. | 기다리거나 `reject`를 빼세요. |
| 409 | `format_not_forkable` | Format이 아니라 내장 capability를 주소로 썼습니다. | Formats by Sume나 직접 만든 Format을 호출하세요. |
| 409 | `previous_run_not_terminal` | 이어 가려는 run이 아직 실행 중입니다. | 폴링한 뒤 다시 호출하세요. |
| 409 | `idempotency_conflict` | 같은 `Idempotency-Key`가 다른 본문으로 이미 쓰였습니다. bulk에서는 `details.queue_id`가 원래 큐를 가리킵니다. | 키 유도 방식을 고치세요. 그대로 재시도하지 마세요. |
| 409 | `idempotency_key_in_use` | 같은 키의 다른 요청이 진행 중입니다. `retryable: true`. | 1초쯤 기다렸다가 다시 보내세요. |
| 413 | `payload_too_large` | 요청 본문이 4 MiB를 넘습니다(`details.limit_bytes`). | `input`을 줄이고, 미디어는 URL로 보내세요. |
| 413 | `attachment_too_large` | 이미지 하나가 30 MB를 넘거나 전체가 500 MB를 넘습니다. | 크기를 줄이세요. |
| 429 | `rate_limited` | 이 키의 write 예산을 다 썼습니다. `error.details.scope`는 `write`. | `retry-after`만큼 기다리세요. [요청 한도](#요청-한도)를 참고하세요. |
| 502 | `attachment_fetch_failed` | Sume가 attachment를 가져오지 못했습니다(`details.index`). `5xx`이지만 입력 문제입니다: `next_action`은 `fix_input`. | URL을 공개적으로 접근 가능하게 만드세요. |
| 503 | `studio_agent_upstream_unavailable` | Sume 쪽 장애. | 같은 `Idempotency-Key`로 나중에 재시도하세요. |
| 4xx/5xx | `format_run_failed_to_start` | 더 구체적인 코드가 없는 이유로 run을 시작하지 못했습니다. | `message`를 읽고 한 번 재시도한 뒤, `request_id`와 함께 지원에 문의하세요. |

`202`가 나중에 위 오류로 바뀌는 일은 없습니다. receipt를 받은 뒤의 실패는 receipt 위에
`status: "failed"`로 옵니다.

## 읽기 시 오류

`GET /v1/format-runs/{run_id}`, `/status`, `/result`, `/events`, `POST …/cancel`,
`POST …/webhook/redeliver`, `GET /v1/format-run-queues/{queue_id}`, 그리고 Format 읽기.

| Status | `error.code` | 의미 | 대응 |
|---|---|---|---|
| 401 | `unauthorized` | 위와 같습니다. | 헤더를 고치세요. |
| 403 | `insufficient_scope` | 읽기에는 `formats:read`, 취소와 redeliver에는 `formats:write`가 필요합니다. | 새 키를 만드세요. |
| 404 | `format_run_not_found` | 모르는 run id이거나 다른 소유자의 run. | id와 키를 확인하세요. 볼 수 없는 run은 존재하지 않는 run과 같게 읽힙니다. |
| 404 | `format_run_queue_not_found` | 모르는 큐이거나 다른 소유자의 큐. | 같습니다. |
| 404 | `format_not_found` | 위와 같습니다. | 같습니다. |
| 409 | `run_not_completed` | run이 종료되기 전에 `GET …/result`를 호출했습니다. `details.status`가 현재 status이고, `retry_after_seconds`가 다음 폴링 시점을 제안합니다. | `status_url`을 폴링한 뒤 `result_url`을 읽으세요. |
| 409 | `webhook_not_configured` | `webhook_url` 없이 만든 run에 redeliver를 호출했습니다. | 다시 보낼 것이 없습니다. |
| 409 | `run_not_terminal` | run이 아직 실행 중인데 redeliver를 호출했습니다. | 종료 receipt를 기다리세요. |
| 429 | `rate_limited` | read 예산을 다 썼습니다(`details.scope: read`). | `retry-after`만큼 기다리세요. run은 계속 실행됩니다. |
| 503 | `studio_agent_upstream_unavailable` | Sume 쪽 장애. | 재시도하세요. run은 계속 실행됩니다. |

폴링 루프 안의 `429`나 `503`은 일시적입니다. 루프를 포기해도 run이나 그 비용은 멈추지 않으니,
물러났다가 다시 폴링하세요.

## run 자체가 실패할 때

끝내지 못한 run은 `status: "failed"`, `error: { code, message }`, 그리고 대개 더 자세한
`output_error`와 함께 돌아옵니다. `artifacts[]`에는 run이 생성한 모든 것이 여전히 나열되고,
`output`에는 스키마를 만족한 부분 결과가 그대로 실립니다. 실패가 절대 받지 못하는 것은
포인터입니다. `primary_output_url`이 `null`이므로 `if (run.primary_output_url)`은 "완성본이
존재한다"의 안전한 검사로 남습니다.

| `error.code` | 의미 | 대응 |
|---|---|---|
| `unattended_blocked` | 사람 없이는 넘을 수 없는 게이트에 걸렸습니다. 브리프에 맞는 아바타가 없거나, 채팅이었다면 물어봤을 입력이 빠진 경우. `message`는 그대로 보여 줘도 되게 쓰여 있습니다. | 입력이나 브리프를 고치고, 새 `Idempotency-Key`로 재시도하세요. |
| `output_schema_unsatisfied` | run은 끝났지만 결과가 `output_schema`에 맞지 않았거나, 만들지 않은 미디어를 참조했습니다. `details.rejected_urls[]` 또는 `details.violations[]`, 그리고 미디어 종류별 `harvested` 수. | `details.harvested`를 스키마의 필수 항목과 비교하세요. 대개 Format이 만들지 않는 파일을 요구하는 스키마입니다. nullable로 풀거나 instruction을 바꾸세요. |
| `deliverable_missing` | Format이 미디어를 만든다고 선언했는데(`io.output_kind`) 이 run은 하나도 만들지 않았습니다. | 재시도하세요. 반복되면 입력이 레시피가 기대하는 것이 아닙니다. |
| `primary_output_missing` | 결과는 스키마를 만족했지만, 지정한 `primary_output_key`가 비어 있습니다. `output`에 부분 결과가 실립니다. | run을 이어 가서 빈 곳을 채우거나 재시도하세요. |
| `agent_reported_failure` | run 자신이 제출한 `return_format_output`이 "전달하지 못했다"고 말했습니다. 명시적 실패 payload, `failed` / `stand-in`으로 표시된 미디어 슬롯, 또는 Format이 선언한 산출물이 아닌 primary(video 키 아래의 오디오나 정지 이미지). `output`에는 실제로 만들어진 것의 ledger가 실리고 `primary_output_url`은 null입니다. | `details.reason`과 `output`을 읽으세요. `output`의 클립은 실제 결과이며 재시도해도 다시 생성되지 않습니다. run을 이어 가거나 새 `Idempotency-Key`로 다시 실행하세요. |
| `incomplete_assembly` | 생성 job이 아직 끝나지 않은 채 run이 시간 한도에 닿아, 전달된 미디어가 지불한 전부가 아닙니다. `details.pending_job_count`와 `details.pending_jobs[]`가 그 job들을 가리키고, 스키마가 허용하면 부분 ledger가 `output`에 실립니다. | `previous_run_id`로 run을 이어 가세요. 끝난 클립은 스레드에 있고 다시 생성되지 않습니다. |
| `output_extraction_failed` | 투영 자체를 실행하지 못했습니다. `details.reason: harvest_unavailable`은 run이 마무리되는 동안 미디어를 읽지 못했다는 뜻이며, `status`는 `completed`로 남고 다음 읽기에서 receipt가 채워집니다. `details.reason: harvest_threw`는 run이 끝난 뒤 호스트의 harvest 자체가 크래시했다는 뜻입니다. run은 `failed`가 되고 `details.thrown`에 던져진 프레임과 빌드가 실리며, ledger에 Format이 선언한 미디어가 하나도 없는 run은 대신 `deliverable_missing`을 보고합니다. | `harvest_unavailable`: run을 한 번 더 읽고, 계속되면 새 키로 재시도하세요. `harvest_threw`: 호스트 결함입니다 — run id를 알려 주세요. 스레드의 클립은 실제 결과이며 재시도해도 다시 생성되지 않습니다. |
| `mcp_unavailable` | 이 run에 필요한 턴별 Sume MCP 도구가 붙지 않아, 도구 없는 턴을 실행하는 대신 호스트가 모델 실행 전에 run을 실패시켰습니다(#7378). `details.retryable`은 `true`, `details.charged`는 `false`입니다 — 생성이 실행되지 않았고 과금도 없습니다. | 새 `Idempotency-Key`로 재시도하세요. 반복되면 요청이 아니라 도구가 다운된 것입니다. |
| `provider_unavailable` | 모델 제공자 스트림이 끊기고 재연결 횟수를 다 쓴 뒤에도 run이 완성본을 만들지 못했습니다. 입력 때문이 아닙니다. `details.retryable`은 `true`입니다. | 새 `Idempotency-Key`로 재시도하세요. 끝난 클립은 스레드에 있고 다시 생성되지 않습니다. |
| `format_run_failed` | 일반 실패. | `message`를 읽으세요. cap을 넘어 쓰려던 run도 여기로 오므로, 브리프를 키우기 전에 `usage.billable_amount_usd_micros`와 `usage.generation_spend_cap_usd_micros`를 비교하세요. |

이 집합은 열려 있다고 보세요. 새 코드가 추가될 수 있으니 아는 것만 처리하고 나머지는 그대로
통과시키세요. 실패한 run은 **새** `Idempotency-Key`로 재시도하세요. 옛 키는 이미 받은 receipt에
묶여 있습니다. 실패가 클립을 남겼다면 새 run보다 [run 이어 가기](/formats/runs)를 택하세요.

웹훅으로는 같은 run이 `status: "ERROR"`, `outcome: "error"`, 그리고 `payload.error.code`를
그대로 비추는 `error.code`와 함께 도착합니다. 완료됐지만 스키마를 채우지 못한 run은
`status: "OK"`, `outcome: "degraded"`로 도착합니다. 실제 미디어는 있고, `output`은 `null`입니다.

## 웹훅 전달 실패

전달 결과는 run을 절대 바꾸지 않습니다. 모든 receipt의 `webhook_delivery` 블록이 무슨 일이
있었는지 말해 줍니다.

| `webhook_delivery.status` | 의미 | 대응 |
|---|---|---|
| `retrying` | 시도가 실패했고 `next_attempt_at`이 다음 시도 시각입니다. 엔드포인트의 HTTP `429`/`503`은 `Retry-After`를 최대 1시간까지 존중합니다. | `last_status_code`가 고칠 수 있는 것이 아니라면 할 일이 없습니다. |
| `failed`, `exhausted` | 열 번의 시도가 거부되거나, 시간 초과(각 10초)되거나, 리다이렉트되거나, URL이 재검증에 실패했습니다. `last_status_code`와 `last_error`가 이유를 말합니다. | `result_url`에서 receipt를 읽고, 엔드포인트를 고친 뒤 `POST …/webhook/redeliver`로 다시 보내세요. |
| `payload: null`인 봉투 | receipt가 1 MiB를 넘었습니다. `error.code`는 `payload_too_large`이고 `error.result_url`이 가져올 곳을 말합니다. `status`는 여전히 run의 실제 결과를 보고합니다. | `result_url`을 가져오세요. `payload`가 object라고 가정한 handler는 가장 큰 run에서 터집니다. |

전체 전달 규칙: [실행과 결과](/formats/runs#웹훅-전달-확인하기).

## 크레딧과 비용

run은 키가 속한 워크스페이스에서 비용을 씁니다. 두 개의 게이트가 서로 다른 시점에 적용됩니다.

| 게이트 | 시점 | 실패 시 |
|---|---|---|
| 지갑 | 생성 시. 워크스페이스가 run을 감당할 수 있어야 합니다. | `402 insufficient_credits`(`next_action: add_funds`) 또는 `402 organization_wallet_not_provisioned`. 아무것도 실행되지 않았습니다. |
| Spend cap | run 도중. run은 유효 cap을 넘어 쓸 수 없습니다. | run이 `failed`로 끝나고, `usage`가 cap에 얼마나 가까웠는지 보여 줍니다. |

cap은 여러분이 쥔 제어 수단입니다. 모든 Format이 cap을 하나 갖고 있고(Format의
`generation_spend_cap_usd_micros`; 지정한 적이 없으면 $400), 요청의 `generation_spend_cap_usd`가
이 run만의 천장을 플랫폼 최대 $500까지 지정합니다. Format cap보다 큰 값도 그대로 적용되고,
`null`은 $500으로 실행하며, `0`은 거부됩니다. 프로덕션의 긴 영상 run은 보통 $120 안팎의 cap으로
만들어지고, 씬 하나 재시도는 몇 달러면 됩니다. 자세한 내용: [Spend caps](/formats/call#spend-caps).

청구되는 것은 계량된 생성(영상, 이미지, 아바타, 음성, 타임라인 작업)이며,
[API 요금 페이지](https://www.sume.com/pricing/api)의 요율을 따릅니다. receipt는 이것을
`usage.billable_amount_usd_micros`로 보고합니다. run이 진행되는 동안 올라가고, 예약된 금액과
확정된 금액을 모두 세며, run이 종료되면 정산됩니다. 에이전트 자체의 LLM 턴은 제외되므로 run의
총비용은 아니고, 청구서가 아니라 receipt 숫자입니다. [`GET /v1/usage`](/dashboard/usage)와
`GET /v1/balance`가 청구 기록입니다. `usage`가 `null`이면 비용을 읽지 못한 것이며, `0`과는
다릅니다.

취소나 실패 전에 끝난 생성은 청구되며, 뒤 단계가 실패해도 환불되지 않습니다. 생성 시점의
`4xx`, 멱등 `200` 재생, `skipped` run은 비용이 들지 않습니다.

## 요청 한도

모든 키는 워크스페이스 플랜에 따라 `/v1` 전체에 걸친 분당 요청 예산을 갖습니다. 읽기와 쓰기는
예산이 분리되어 있고, 읽기는 쓰기 숫자의 40배를 받으므로 폴링이 여러분의 생성 요청을 굶기지
않습니다.

| 플랜 | 분당 쓰기 | 분당 읽기 |
|---|---:|---:|
| Free | 120 | 4800 |
| Pro | 300 | 12000 |
| Startup | 600 | 24000 |
| Scale | 1200 | 48000 |
| Enterprise | 계약값. 준비 전까지는 Scale | 계약값 |

**읽기**는 모든 `GET`입니다. receipt, `status_url`, `events_url`, `result_url`, Format과 run
목록. **쓰기**는 나머지 전부입니다. run과 큐 생성, 취소, redeliver.

모든 응답에 현재 상태가 실리고, `429`는 어느 예산에서 나왔는지 알려 줍니다.

| 헤더 | 의미 |
|---|---|
| `ratelimit-limit` | 이 요청이 쓴 예산 기준으로, 현재 창에서 허용되는 요청 수. |
| `ratelimit-remaining` | 그 창에 남은 요청 수. |
| `ratelimit-reset` | 창이 초기화되기까지의 초. |
| `retry-after` | `429`에 실리는 대기 초. `error.details.scope`는 `read` 또는 `write`. |

직접 요청을 세지 말고 헤더에 맞춰 속도를 조절하세요. 요청 속도는 생성 용량이 아닙니다. 동시에
실행되는 생성 수는 플랜의 concurrency 한도가 정하며, 요청 속도를 올려도 늘어나지 않습니다.

## 도움 받기

모든 응답에 `x-sume-request-id`가, 모든 receipt에 `request_id`가 실립니다. 문의할 때 이 값과
run id, `error.code`를 인용하세요. API 키, 서명 시크릿, 원본 미디어 URL은 보내지 마세요.

## 다음

- [Run 만들기](/formats/call): 생성 계약, `401` / `403` / `404` 구분 상세 포함
- [실행과 결과](/formats/runs): 폴링, 웹훅, `webhook_delivery`, 실패한 run 이어 가기
- [구조화 출력](/formats/structured-output): 스키마 규칙과 모든 `output_error`
- [인증](/authentication): 키, 교체, 요청 한도 상세
