POST /v1/videos status codes: which of 10 are safe to retry

OpenAPI lists 202 plus ten error codes for POST /v1/videos. Which to retry with the same key, which to fix or stop on, a Python classifier and a backoff plan.

4 min readSume
All posts

POST /v1/videos documents 202 plus ten error codes: 400, 401, 402, 404, 409, 413, 429, 500, 502 and 503. Retry only 429, 500, 502 and 503, always with the same Idempotency-Key; fix and resend for 400, 404 and 413; stop for 401; top up then resend for 402; and adopt the existing job on 409.

Retry map

The 502 body reads that Sume could not start the generation job. A 5xx never proves the job was refused, which is why the same key matters: with a key, the retry adopts whatever the first call created instead of paying for it twice.

POST /v1/videos responses and what to do (read 2026-10-09)
StatusMeaningRetry?Action
202Accepted, job createdNoStore id and polling_url
400Validation, unsupported_parameterNoFix the body
401Key missing or invalidNoFix the credential; send only one
402insufficient_credits, no job startedAfter top-upAdd balance, resend with the same key
404model_not_foundNoPick an id from GET /v1/videos/models
409idempotency_conflictNoAdopt the job in error.details
413Body too largeNoShrink inputs
429queue_full or rate_limitedYesWait for retry-after
500Server errorYesSame key
502Could not start the generation jobYesSame key
503provider_capacity_exceeded or overloadYesSame key

A classifier

Keep the decision in one function so every call site agrees. The wait is the retry-after header when present, else exponential: 2, 4, 8, 16 s, which adds up to 30 s over four retries.

RETRY = {429, 500, 502, 503}

def next_step(status, headers, attempt):
    if status == 202:
        return "done", 0
    if status in RETRY and attempt < 4:
        wait = headers.get("retry-after")
        return "retry", int(wait) if wait else 2 ** (attempt + 1)
    if status == 409:
        return "adopt", 0  # read error.details.job_id
    if status == 402:
        return "top_up", 0
    return "stop", 0

Gotchas

A 429 names the budget in error.details.scope (read or write); queue_full is about generation capacity, not request rate. Cancel is a different story: it works only before generation starts and returns 409 job_generation_already_started afterwards.

Retrying 500, 502 and 503 without a key is the expensive mistake: at 30 s of Wan 3.0 at 720p, three unkeyed attempts can reserve 3 x $3.75 = $11.25.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume