Retry, fix or stop: classify every Sume /v1/videos error in code

One Python function that maps each documented /v1/videos error code (400, 402, 404, 409, 429, 502) to retry, fix or stop, plus the two 409s that look alike.

4 min readSume
All posts

Classify Sume video errors by the error code in the body, not by the HTTP status. Most are fix-the-request errors, 402 means stop and add funds, 429 and 502 mean retry with the same Idempotency-Key, and the two 409s are opposites: job_not_completed is retryable, job_failed is not. The function below returns one of retry, fix or stop for each documented code.

The documented set

The /v1/videos contract lists a small set of errors. Error bodies use the standard Sume envelope, with error.code, error.message and a request_id that is safe to share with support. Log the request_id on every non-2xx response, and never log the key or signed URLs.

The table is the same data as the code, on purpose. If you keep both in your repo, a code review can compare them line by line, and a new error that Sume adds shows up as an unknown code in your logs instead of being silently treated as a retry.

/v1/videos error codes and the right reaction (Sume docs, read 2026-10-05)
HTTPCodeReactionWhy
400invalid_requestfixMissing prompt or malformed body
400unsupported_parameterfixsize, seed or provider.options are rejected loudly
400unsupported_capabilityfixValue is outside the model's advertised list
401unauthorizedstopMissing or invalid key
402insufficient_creditsstopBalance is below the reserve
404model_not_foundfixNot a catalog id, alias or sume/auto
404job_not_foundstopUnknown or foreign job
409job_not_completedretryContent asked for while running
409job_failedstopTerminal failure, not retryable
429rate_limitedretryHonour retry-after
429queue_fullretryWait for capacity
502provider submissionretrySame Idempotency-Key

The function

Use the code, not the status, as the key, with a fallback by status class for codes you have not seen yet. An unknown 5xx is a retry, and an unknown 4xx is a fix, since a client mistake does not heal on its own.

RULES = {
    "invalid_request": "fix", "unsupported_parameter": "fix",
    "unsupported_capability": "fix", "model_not_found": "fix",
    "unauthorized": "stop", "insufficient_credits": "stop",
    "job_not_found": "stop", "job_failed": "stop",
    "job_not_completed": "retry", "rate_limited": "retry",
    "queue_full": "retry", "provider_capacity_exceeded": "retry",
}

def classify(status: int, body: dict) -> str:
    code = (body.get("error") or {}).get("code", "")
    if code in RULES:
        return RULES[code]
    if status >= 500 or status == 429:
        return "retry"
    return "fix" if 400 <= status < 500 else "stop"

samples = [(400, {"error": {"code": "unsupported_capability"}}),
           (409, {"error": {"code": "job_not_completed"}}),
           (409, {"error": {"code": "job_failed"}}),
           (429, {"error": {"code": "queue_full"}}),
           (503, {}), (418, {})]
for s, b in samples:
    print(s, b.get("error", {}).get("code", "-"), "->", classify(s, b))

The two 409s

Sume deliberately split the content-route 409. When a job is still running, you get job_not_completed with retryable true and a next action of poll status. After a terminal failure you get job_failed with retryable false and a next action of inspect events. The split exists so that clients do not poll forever for a file that will never exist. A 409 also appears when an Idempotency-Key is reused with a different body, and that is a fix, since the key belongs to another request.

Which models trigger which 400

The catalog gates the values, so the same request is valid on one model and a 400 on another. Gemini Omni Flash 1.1 takes 3 to 10 seconds, so duration 30 fails there but passes on Seedance 2.5, which takes 4 to 30. Read supported_durations from GET /v1/videos/models before you submit, and you remove most of the fix-class errors before they cost a round trip. A fix-class error is free, since no provider work started.

Wrap classify in your HTTP helper, not in business code. The helper can then log the request_id, count retries per Idempotency-Key, and cap them at a budget such as five attempts. A retry class without a cap is how a transient error turns into a self-inflicted outage.

  • Retry with backoff and jitter. Use retry-after when the response has it.
  • Keep the Idempotency-Key on every retry of a submit.
  • Stop the whole batch on a 401 or a 402. Every remaining job will fail the same way.
  • Read the poll error field for failed jobs. It is the public remap of the worker error, so an unreachable input URL says so.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume