How it works

AI video API error codes: what they mean and when to retry

Video generation jobs fail more often than ordinary API calls: models get busy, refuse prompts or time out. This guide lists every error you can get back, which ones are worth retrying, and how to retry safely.

Two places an error can show up

A video or image job can go wrong at two moments, and your code has to handle both:

  1. When you submit. POST /v1/generations answers with an HTTP error status and a body like {"error": {"code": "...", "message": "..."}}. Nothing was started and nothing is charged.
  2. While it runs. The submit succeeded with 202, but the job later ends with status: "failed". Its error has a code, a message you can show to the person who asked, and retryable.

A failed job is never charged on any model. The price is held from your balance when you submit and released if the job fails.

HTTP errors when you submit

HTTP error codes from the generations endpoint
StatusCodeMeaningRetry?
400invalid_requestThe body is not JSON, lacks model or input, or has a field the API does not know.No. Fix the body.
401unauthorizedThe API key is missing or wrong.No. Check the key.
402insufficient_creditsThe balance does not cover the price held for the job.After a top-up.
403account_disabled, generation_not_enabledThe account is paused, or the key can read prices but not generate.No. Contact support.
404not_foundNo generation with that id on your account.No.
409idempotency_conflictThe Idempotency-Key was already used with a different body.With a new key.
409not_readyYou asked for the file before the job succeeded.Yes, once it has succeeded.
413body_too_largeThe JSON body is over 1 MB. Upload files and send links instead.No. Shrink the body.
422invalid_inputA value the model does not take, such as a length or size it does not list. The message says which.No. Change the value.
422max_cost_exceededEvery route costs more than your max_cost_usd.No. Raise the cap or lower the settings.
422model_not_availableNo model with that id.No. Check the id.
429rate_limitedToo many requests in the same second from one account.Yes, after Retry-After.
429concurrency_limitThe account already has its maximum number of jobs running.Yes, when one finishes.
500server_errorSomething broke on our side.Yes, with the same Idempotency-Key.
503busyA moment of load.Yes, after Retry-After, same key.
503model_unavailableNo route for this model is up right now.Yes, shortly.

The rule of thumb: 429, 500 and 503 are worth retrying with backoff; anything in the 4xx range except 429 means the request itself has to change.

Why a job fails after it starts

Error codes on a failed generation
error.coderetryableWhat happened
content_policynoThe model refused the prompt or media, or withheld its result. reason says why: sexual, minors, copyright, real_person or other.
invalid_inputnoA reference could not be read, or the model rejected a value. The message says which.
model_declinednoThe model answered in words instead of making the picture or video. Describe the result itself in the prompt.
reference_changednoA reference file changed after it was checked. Send it again.
max_cost_exceedednoThe only route able to run costs more than your cap.
model_busyyesEvery route was at capacity. Try again in a minute.
model_unavailableyesNo route could take the job right now.
timeoutyesNo route returned a result in time, or your max_wait_s passed.
all_routes_failedyesEvery route failed for reasons on its side.

Behind the scenes a job that hits a busy or broken route moves on to the next route by itself, so by the time you see model_busy or all_routes_failed every route has already been tried. The attempts field says how many. Waiting a minute or two before trying again is usually enough.

Content refusals

content_policy is the failure people hit most. The model, not the API, decides it, and the reason tells you which rule fired. Common causes are a famous person's name or face in the prompt or a reference picture (real_person), brands and well-known characters (copyright), and anything that reads as involving a minor. Retrying the same request will not help; reword the prompt or change the picture. Models differ in how strict they are, so the same prompt can pass on one and be refused on another.

A retry loop that does the right thing

This Python helper retries what is worth retrying and stops on what is not. Two details matter:

  • Resending a submit after a 503 or a dropped connection uses the same Idempotency-Key, so it can never start a second, paid job.
  • Trying again after a job has failed uses a new key. The same key would just hand back the failed job.
Python
import os
import time
import uuid

import requests

API = "https://api.revana.si/v1"
AUTH = {"Authorization": f"Bearer {os.environ['REVANA_API_KEY']}"}
RETRY_HTTP = {429, 500, 503}


class GenerationError(Exception):
    pass


def submit(body):
    """POST a job. Retries 429, 500 and 503 with the same Idempotency-Key, so a retry never starts a second job."""
    headers = {**AUTH, "Idempotency-Key": str(uuid.uuid4())}
    for attempt in range(6):
        r = requests.post(f"{API}/generations", headers=headers, json=body, timeout=60)
        if r.ok:
            return r.json()
        if r.status_code in RETRY_HTTP:
            time.sleep(float(r.headers.get("Retry-After", 2 ** attempt)))
            continue
        err = r.json()["error"]  # 400, 401, 402, 403, 409, 413, 422: fix the request, do not retry
        raise GenerationError(f"{r.status_code} {err['code']}: {err['message']}")
    raise GenerationError("still busy after 6 tries")


def wait(job):
    while job["status"] in ("queued", "running"):
        time.sleep(5)
        job = requests.get(f"{API}/generations/{job['id']}", headers=AUTH).json()
    return job


def generate(body, tries=3):
    for n in range(tries):
        job = wait(submit(body))  # each call uses a new key: a failed job is final
        if job["status"] == "succeeded":
            return job
        err = job["error"]
        if not err["retryable"]:
            raise GenerationError(f"{err['code']}: {err['message']}")
        time.sleep(60 * (n + 1))  # model_busy, timeout and the like: wait, then try again
    raise GenerationError(f"gave up after {tries} tries: {err['code']}")


job = generate({
    "model": "seedance-2.0-fast",
    "input": {"prompt": "Waves roll onto black sand at sunset.", "resolution": "480p", "duration": 5},
    "max_cost_usd": 0.3,
})
print(job["output"]["urls"][0], job["cost_usd"])

The sample job is a 5 second 480p Seedance 2.0 Fast clip, which costs $0.25 if it succeeds and nothing if it fails. The max_cost_usd cap stops a wrong setting from costing more than expected.

Avoiding errors in the first place

  • Read the options before you send. GET /v1/models and the models page list every model's sizes, lengths and reference limits. A value outside them is refused at once with 422 invalid_input.
  • Upload files instead of embedding them. POST /v1/uploads takes pictures up to 30 MB, clips up to 50 MB and sound up to 15 MB and returns a link valid for 14 days, which keeps the request body small.
  • Check your balance in code. GET /v1/balance before a large batch saves a run of 402 errors. Credit packs are on the pricing page.

For ways to cut what successful jobs cost, see how to cut AI video API costs. The full error reference is in the docs; get an API key to try it.

Frequently asked questions

Am I charged when an AI video generation fails?

No. The price is held when you submit and released if the job fails, whatever the reason.

Which errors should I retry?

On submit, retry 429, 500 and 503 after the Retry-After delay. On a failed job, retry when error.retryable is true: model_busy, model_unavailable, timeout and all_routes_failed.

What does content_policy mean?

The model refused the prompt or media, or withheld what it made. The reason field says which rule: sexual, minors, copyright, real_person or other. Change the request rather than retrying it.

How do I retry without paying twice?

Send an Idempotency-Key header and reuse it when you resend after a timeout or a 503. You get the same generation back, not a second one.

What does 422 invalid_input mean?

The model does not take a value you sent, such as a length, size or number of reference pictures. The message names the field and what it takes.

Build on an API that never bills a failure

One API key for every video and image model, with automatic fallback between routes. Prepaid credit from $10, charged only for jobs that succeed.