Which field do I branch on in a Sume API error: code or next_action?

Branch on the HTTP status, then on error.code. Use retryable, retry_after_seconds and next_action to decide on a resend. Never match on message.

4 min readSume
All posts

Branch on the HTTP status first, then on error.code. The code is the stable token to switch on: it matches ^[a-z0-9_]+$ and is never a sentence. Use retryable and retry_after_seconds to decide whether resending can succeed, and next_action for what to do about it. The message is for humans and may change, so log it and never match on it.

This follows Format API errors, read 2026-09-29. The envelope fields below are documented for Format run calls; the Errors and rate limits page shows a shorter envelope with code, message, request_id and details for the general API.

What is in a Sume error body?

An error object with code, message, request_id, category, stage, retryable, retry_after_seconds, public_reason, next_action and details. Each one has one job.

From Format API errors, read 2026-09-29.
FieldUse it for
codeThe stable token to switch on
messageA human sentence; log it, never match on it
request_idAlso sent as the x-sume-request-id header; quote it to support
retryable, retry_after_secondsWhether resending the same request can succeed, and how long to wait first
next_actionauthenticate, fix_input, add_funds, retry_later, poll_status, inspect_events or contact_support
category, stage, public_reasonCoarser labels for dashboards and alerts
detailsCode-specific data such as required_scope or index

What does a 4xx tell me about retrying?

On a Format run create, a 4xx means nothing ran and nothing was charged, so fix the call rather than retrying it. The docs single out one mistake: retrying a 403 insufficient_scope in a loop is the most common and most expensive mistake. Scopes cannot be added to an existing key, so the fix is to mint a new key.

On that Format run create, a failed create also releases its Idempotency-Key, so once the cause is fixed you can resend with the same key. Generation submits differ: in current code a same-key retry after 429 queue_full or 503 provider_capacity_exceeded replays that refusal, so send a new key once the cause clears.

How do I write the dispatcher?

Map the documented next_action values to your own outcomes, and fall through for anything new. The errors page says to treat the set of codes as open: new codes may appear, so handle the ones you know and fall through on the rest.

def decide(status: int, error: dict) -> str:
    if error.get("retryable") is True:
        return f"wait {error.get('retry_after_seconds') or 'a backoff'}, then resend"
    action = error.get("next_action")
    if action == "authenticate":
        return "fix the key or its scopes; do not loop"
    if action == "fix_input":
        return "fix the request body or URL"
    if action == "add_funds":
        return "top up, then resend"
    if action == "contact_support":
        return f"quote request_id {error.get('request_id')}"
    return "log message and fall through"

What are the next_action values on a run receipt?

A different field on a different object: the run receipt carries next_action too, but a Format run emits only three values. It is poll_status while queued or processing, retry_later on a skipped run, and none on every terminal run.

Do failed jobs use the same fields?

A failed generation job exposes public error metadata such as category, stage, retryability, retry-after seconds, public reason and next action. The general errors page maps each job error category to a next step: validation means fix input, auth means check the API key and workspace access, quota means add funds or lower request cost, and queue means retry later (after queue_full or provider_capacity_exceeded, use a new key, as above). Internal provider payloads are never public API fields.

What should I send to support?

The request_id, the run id and the error.code. Do not send API keys, signing secrets or raw media URLs.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume