Errors and spend
Every error on the API has the same envelope, and every code is a lowercase token you can branch on. This page lists all of them for the Formats surface in one place, grouped by when they happen: before a run exists, while you read it, when the run itself fails, and when a webhook could not be delivered. Spend and rate limits are at the end.
The error envelope
| Field | Use it for |
|---|---|
code | The stable token to switch on. ^[a-z0-9_]+$, never a sentence. |
message | Written for a human, may change. Log it; never match on it. |
request_id | Also sent as the x-sume-request-id header. Quote it to support. |
retryable, retry_after_seconds | Whether resending the same request can succeed, and how long to wait first. |
next_action | authenticate, fix_input, add_funds, retry_later, poll_status, contact_support or none. |
category, stage, public_reason | Coarser labels for dashboards and alerts. |
details | Code-specific: required_scope, workspace_id, violations[], index, status, scope, … named under each code below. |
Branch on the HTTP status first, then on code. A 4xx at create means nothing ran and
nothing was charged, so fix the call rather than retrying it. Retrying a
403 insufficient_scope in a loop is the most common and most expensive mistake.
Errors at create
POST /v1/formats/{handle}/{slug}/runs and POST …/bulk-runs. Nothing runs, nothing is
charged, and a failed create releases its Idempotency-Key.
| Status | error.code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_request | The body names none of instruction / input / previous_run_id / attachments; both output_schema and response_format; input is not an object, has more than 64 top-level keys or is over 2 MiB; generation_spend_cap_usd is 0 or over 500; model is not in the catalog; a bulk body has bad concurrency or items; a webhook URL is not public HTTPS; conflicting communication aliases. | Read message and details. |
| 400 | unknown_parameter | A top-level field the API does not know, including thread_id. details.errors[].suggestion names the likely intended field. | Rename or remove it. |
| 400 | output_schema_invalid | The schema is outside the supported subset. details.violations[] lists every { path, rule, message }. | Fix each violation. See Supported schemas. |
| 400 | invalid_attachment | Bad attachments[] item, or the media budget attachments[] shares with input URLs is exceeded. | See Attachment errors. |
| 400 | attachment_not_found | asset_id is unknown in this workspace. | Upload it, or use a URL. |
| 400 | previous_run_format_mismatch | previous_run_id was created on a different Format. | Continue it on that Format. |
| 400 | previous_run_not_resumable | That run left nothing to continue. details reports previous_run_status, has_thread, artifact_count. | Start a fresh run. |
| 401 | unauthorized | No key, malformed key, revoked key, a key for the other host, or two credentials at once. | Fix the header. |
| 402 | insufficient_credits | The workspace wallet cannot fund the run. next_action is add_funds. | Top up. Retrying without doing so returns the same answer. |
| 402 | organization_wallet_not_provisioned | An organization workspace with no funded wallet. | An admin has to fund it. |
| 403 | insufficient_scope | The key lacks formats:write (details.required_scope), or it is a service-account key (details.reason: service_account_format_runs_unsupported). | Mint a new key with the scopes; scopes cannot be added to an existing one. |
| 403 | workspace_key_required | Team Format, personal key. details.workspace_id names the workspace. | Create a key in that workspace. |
| 404 | format_not_found | Unknown handle or slug, an archived Format, or one outside your key's workspace. | Check the address and the key. |
| 404 | previous_run_not_found | previous_run_id is unknown or not yours. | Check the id. |
| 409 | format_inactive | The Format is inactive. | Activate it in the dashboard. |
| 409 | format_api_trigger_disabled | The API trigger is off for this Format. | Turn it on in the dashboard. |
| 409 | format_run_in_progress | on_active_run: "reject" and a run is already in flight. | Wait, or drop reject. |
| 409 | format_not_forkable | You addressed a built-in capability rather than a Format. | Call a Format by Sume, or your own. |
| 409 | previous_run_not_terminal | The run you want to continue is still running. | Poll it, then call again. |
| 409 | idempotency_conflict | That Idempotency-Key was already used with a different body. On bulk, details.queue_id names the original queue. | Fix your key derivation; do not retry as is. |
| 409 | idempotency_key_in_use | Another request with the same key is in flight. retryable: true. | Wait about a second and resend. |
| 413 | payload_too_large | The request body is over 4 MiB (details.limit_bytes). | Shrink input; send media by URL. |
| 415 | unsupported_media_type | The body was not sent as application/json (details.received_content_type). | Set Content-Type: application/json. |
| 413 | attachment_too_large | An image over 30 MB, or a set over 500 MB. | Resize. |
| 429 | rate_limited | The write budget for this key is spent. error.details.scope is write. | Wait retry-after. See Rate limits. |
| 502 | attachment_fetch_failed | Sume could not fetch an attachment (details.index). Despite the 5xx, it is your input: next_action is fix_input. | Make the URL publicly reachable. |
| 503 | studio_agent_upstream_unavailable | A Sume-side outage. | Retry later with the same Idempotency-Key. |
| 4xx/5xx | format_run_failed_to_start | The run could not start for a reason with no more specific code. | Read message; retry once, then contact support with request_id. |
A 202 never turns into one of these later: once you hold a receipt, failures arrive on it as
status: "failed".
Errors while reading
GET /v1/format-runs/{run_id}, /status, /result, /events, POST …/cancel,
POST …/webhook/redeliver, GET /v1/format-run-queues/{queue_id}, and the Format reads.
| Status | error.code | Meaning | What to do |
|---|---|---|---|
| 401 | unauthorized | As above. | Fix the header. |
| 403 | insufficient_scope | Reads need formats:read; cancel and redeliver need formats:write. | Mint a new key. |
| 404 | format_run_not_found | Unknown run id, or a run belonging to another owner. | Check the id and the key. A run you cannot see reads the same as one that does not exist. |
| 404 | format_run_queue_not_found | Unknown queue, or another owner's. | Same. |
| 404 | format_not_found | As above. | Same. |
| 409 | run_not_completed | GET …/result before the run is terminal. details.status is the current status; retry_after_seconds suggests the next poll. | Poll status_url, then read result_url. |
| 409 | webhook_not_configured | Redeliver on a run created without a webhook_url. | Nothing to redeliver. |
| 409 | run_not_terminal | Redeliver while the run is still running. | Wait for the terminal receipt. |
| 429 | rate_limited | The read budget is spent (details.scope: read). | Wait retry-after. The run keeps executing. |
| 503 | studio_agent_upstream_unavailable | A Sume-side outage. | Retry. The run keeps executing. |
A 429 or 503 inside a poll loop is transient. Abandoning the loop does not stop the run or
its spend, so back off and poll again.
When the run itself fails
A run that could not finish comes back with status: "failed", error: { code, message },
and usually output_error with more detail. artifacts[] still lists everything the run
generated, and output still carries whatever partial result satisfied your schema. What a
failure never gets is a pointer: primary_output_url is null, so
if (run.primary_output_url) stays a safe test for "the deliverable exists".
error.code | Meaning | What to do |
|---|---|---|
unattended_blocked | The run hit a gate it could not pass without a person: no avatar matched the brief, or a missing input it would have asked about in chat. message is written to be shown. | Fix the input or the brief; retry with a new Idempotency-Key. |
output_schema_unsatisfied | The run finished, but its result did not match your output_schema, or referenced media it did not produce. details.rejected_urls[] or details.violations[], plus a harvested count by media type. | Compare details.harvested against what your schema requires. Usually a schema demanding a file the Format never makes: loosen it to nullable, or change the instruction. |
deliverable_missing | The Format is declared to produce media (io.output_kind) and this run made none. | Retry; if it repeats, the input is not what the recipe expects. |
primary_output_missing | The result satisfied your schema but left the primary_output_key you named empty. output carries the partial. | Continue the run to fill the gap, or retry. |
agent_reported_failure | The run's own accepted return_format_output said it did not deliver: an explicit-fail payload, media slots reporting failed / stand-in, or a primary that is not the Format's declared deliverable (audio or a still under a video key). output carries the ledger of what was made; primary_output_url is null. | Read details.reason and output. The clips on output are real and are not regenerated by a retry; continue the run or re-fire with a new Idempotency-Key. |
incomplete_assembly | The run reached its time limit with generation jobs still unfinished, so the delivered media is not everything it paid for. details.pending_job_count and details.pending_jobs[] name them; the partial ledger is on output when your schema allows it. | Continue the run with previous_run_id. The finished clips are on the thread and are not regenerated. |
output_extraction_failed | The projection could not run. details.reason: harvest_unavailable means the media could not be read while the run finalized; status stays completed and the receipt fills in on the next read. details.reason: harvest_threw means the host's own harvest crashed after the run finished; the run is failed, details.thrown carries the throwing frame and build, and a run whose ledger holds none of the Format's declared media reports deliverable_missing instead. | harvest_unavailable: read the run once more, then retry with a new key. harvest_threw: a host defect — report the run id; the clips on the thread are real and are not regenerated by a retry. |
mcp_unavailable | The per-turn Sume MCP tools this run needed did not attach, so the host failed it before the model ran rather than run a tool-less turn (#7378). details.retryable is true and details.charged is false — no generation ran, and nothing was billed. | Retry with a new Idempotency-Key. If it repeats, the tools are down, not your request. |
provider_unavailable | The model's provider stream was cut and its reconnects ran out before the run produced its deliverable. Nothing about your input caused it. details.retryable is true. | Retry with a new Idempotency-Key; the finished clips are on the thread and are not regenerated. |
format_run_failed | The generic failure. | Read message. A run that wanted to spend past its cap lands here, so compare usage.billable_amount_usd_micros with usage.generation_spend_cap_usd_micros before raising the brief. |
Treat this set as open: new codes may appear, so handle the ones you know and fall through on
the rest. Retry a failed run with a new Idempotency-Key; the old one is bound to the
receipt you already have. When the failure left clips behind, prefer
continuing the run over a fresh one.
Over a webhook, the same run arrives with status: "ERROR", outcome: "error", and
error.code mirroring payload.error.code. A run that completed but could not fill your
schema arrives as status: "OK", outcome: "degraded": real media, output: null.
Webhook delivery failures
A delivery outcome never changes the run. The webhook_delivery block on every receipt says
what happened:
webhook_delivery.status | Meaning | What to do |
|---|---|---|
retrying | An attempt failed; next_attempt_at is the next one. HTTP 429/503 from your endpoint honour Retry-After up to one hour. | Nothing, unless last_status_code is yours to fix. |
failed, exhausted | Ten attempts refused, timed out (10 s each), redirected, or the URL failed re-validation. last_status_code and last_error say which. | Read the receipt from result_url; fix the endpoint; POST …/webhook/redeliver to replay. |
Envelope with payload: null | The receipt was over 1 MiB. error.code is payload_too_large and error.result_url says where to fetch it. status still reports the run's real outcome. | Fetch result_url. A handler that assumes payload is an object will throw on your largest runs. |
Full delivery rules: Runs and results.
Credits and spend
Runs spend from the workspace the key belongs to. Two gates apply, at different times:
| Gate | When | On failure |
|---|---|---|
| Wallet | At create. The workspace must be able to fund the run. | 402 insufficient_credits (next_action: add_funds), or 402 organization_wallet_not_provisioned. Nothing ran. |
| Spend cap | During the run. The run cannot spend past its effective cap. | The run ends failed; usage shows how close to the cap it got. |
The cap is the control you own. Every Format carries one (generation_spend_cap_usd_micros
on the Format; $400 when it never set one), and generation_spend_cap_usd on the request
names this run's own ceiling up to the $500 platform maximum. Above the Format's cap is
honored; null runs at $500; 0 is rejected. Production long-form runs are typically created
with caps around $120, a single-scene retry with a few dollars. Details:
Spend caps.
What you are charged for is metered generation (video, image, avatar, voice, timeline work) at
the rates on the API pricing page. The receipt reports it
as usage.billable_amount_usd_micros, which climbs while the run is in flight, counts both
reserved and captured amounts, and settles when the run terminates. It excludes the agent's
own LLM turn, so it is not the run's total cost, and it is a receipt figure rather than an
invoice: GET /v1/usage and GET /v1/balance are the billing records.
usage is null when spend could not be read, which is different from 0.
Generation that finished before a cancel or a failure is billed; a later step failing does not
refund it. A 4xx at create, an idempotent 200 replay, and a skipped run cost nothing.
Rate limits
Every key has a per-minute request budget across all of /v1, set by the workspace's plan.
Reads and writes have separate budgets, and reads get forty times the write number, so
polling cannot starve your own creates.
| Plan | Writes per minute | Reads per minute |
|---|---|---|
| Free | 120 | 4800 |
| Pro | 300 | 12000 |
| Startup | 600 | 24000 |
| Scale | 1200 | 48000 |
| Enterprise | Contracted; Scale until provisioned | Contracted |
A read is any GET: the receipt, status_url, events_url, result_url, Format and run
lists. A write is everything else: creating runs and queues, cancel, redeliver.
Every response carries the current state, and a 429 names the budget it came from:
| Header | Meaning |
|---|---|
ratelimit-limit | Requests allowed in the current window, for the budget this request spent from. |
ratelimit-remaining | Requests left in that window. |
ratelimit-reset | Seconds until the window resets. |
retry-after | Seconds to wait, sent on 429. error.details.scope is read or write. |
Pace on the headers rather than counting requests yourself. Request rate is not generation capacity: how many generations run at once is governed by the plan's concurrency limit, and raising your request rate does not raise it.
Getting help
Every response carries x-sume-request-id, and every receipt carries request_id. Quote
them, the run id, and the error.code when you write in. Do not send API keys, signing
secrets, or raw media URLs.
Next
- Create a run: the create contract, including the
401/403/404dialect in detail - Runs and results: polling, webhooks,
webhook_delivery, continuing a failed run - Structured output: schema rules and every
output_error - Authentication: keys, rotation, and the full rate-limit notes