AI Video API Error Codes: What 1,339 Failed Jobs Say About Retrying

Short answer: retry a failed AI generation only when the failure came from the model side: an empty result, a 5xx, a timeout or a 503. Never retry a content block (422 content_policy_violation), a bad request (400) or an empty balance (402) without changing something first. On PicX, content blocks and invalid requests alone were 74% of failed jobs, so a blind retry loop mostly re-sends the same refusal.
That rule comes from our own logs. Between September 24 and November 11, 2026, 3,565 generation jobs started from the PicX web app reached a model. 2,226 came back with a file. 1,339 did not. Below is what those 1,339 were, what the API says in each case, and what your code should do next.
The 1,339 failures, sorted by cause

| Cause | Jobs | Share of failures | Whose problem | Retry as is? |
|---|---|---|---|---|
| Content blocked (prompt or output) | 736 | 55.0% | The request | No |
| Invalid request (bad size, missing input) | 251 | 18.7% | The request | No |
| Model returned nothing | 186 | 13.9% | The model | Once |
| Server error (5xx, 524) | 157 | 11.7% | The model side | Yes, with backoff |
| Stalled past 10 minutes | 9 | 0.7% | The model side | Once |
Two things stand out. First, the request causes most failures: 987 of 1,339. A loop that retries everything three times would have sent about 2,000 more doomed requests in seven weeks, each one taking a queue slot and, on a platform that keeps failed charges, money. Second, real model-side failures are a small slice: 352 jobs, just under 10% of everything submitted. They're worth retrying, but they don't explain why a job "keeps failing". If one job fails again and again, the request is the likeliest cause.
How we counted: these are generation_failed and generation_completed events from the PicX web app, grouped by the failure reason the API returned. A further 869 attempts never reached a model at all. Those were browser network errors such as a dropped connection or a blocked request, and they aren't in the table. The breakdown is for image and video jobs together. Video alone had too few failures in the window to split by cause with confidence.
Every error the PicX API returns, and what to do
Errors come in two places. Some come straight back when you submit: these are refusals, and nothing has been charged. Others arrive later, when you poll GET /v1/generations/{id} or receive a webhook and the job has status: "failed" with an error_message.
At submit time (nothing charged)
| Status | Body | Meaning | Your move |
|---|---|---|---|
400 | {"detail": "..."} with code invalid_request | A missing or malformed field, such as an unknown aspect ratio or an empty prompt | Fix the request. Don't retry. |
400 | Unknown model: ... | The model id isn't in the catalog | Read GET /v1/models and use an id from it |
400 | This model is not available right now. | The model has no price at the moment | Pick another model; check again later |
402 | {"detail": "...", "buy_url": "..."} | The balance doesn't cover the quoted price | Stop. Top up or lower the price (shorter, smaller, sound off) |
422 | {"detail": "...", "code": "content_policy_violation"} | The prompt or input image was refused by the safety check | Rewrite the prompt. The same text gets the same answer |
422 | validation issues | A mode is missing a required input, for example image mode without image_url | Add the input the mode needs (mode table) |
429 | rate limited | Too many requests for your key's tier | Wait for Retry-After; see reading a 429 |
503 | Generation is temporarily unavailable | The route to the model is down | Retry later with backoff, or switch models |
The 422 carries a stable code on purpose. Your code can tell "the prompt was refused" from "the JSON was wrong" without parsing English.
After the job ran (status: "failed")
error_message starts with | Meaning | Your move |
|---|---|---|
This request was blocked by our content safety system | The safety check refused the prompt or the output | Change the prompt; don't resend it as is |
The model couldn't generate a result | The model ran and produced nothing usable | Retry once, ideally with a small prompt change |
Generation exceeded 10 minutes | The job stalled and our sweep closed it | Retry once; if it repeats, try a lower resolution |
Your prompt is too long for this model | That model has a shorter prompt limit | Shorten the prompt or use a model with a longer limit |
Something went wrong | A server-side error | Retry with backoff |
A failed job refunds its credits once, automatically. The ledger records the debit at submit and the refund on failure, and the refund can't run twice. So on PicX a retry never costs twice for one failure. What a blind retry wastes is time and queue slots, and the result is the same refusal.
The tables above are the full set of messages a customer sees.
A retry loop that matches the data
With the JavaScript SDK, 429s, 5xx responses and dropped connections on individual HTTP calls are already retried for you (maxRetries, default on). What's left to decide is what to do when a job fails:
import { PicX, PicXGenerationFailedError, PicXValidationError, PicXInsufficientCreditsError } from "picx-ai";
const picx = new PicX(process.env.PICX_API_KEY);
const RETRY_ONCE = ["The model couldn't generate a result", "Generation exceeded 10 minutes"];
const RETRY_WITH_BACKOFF = ["Something went wrong"];
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function generateVideo(input, attempt = 0) {
// One key per attempt. If the submit call itself is retried after a dropped
// connection, the same key returns the same job instead of a second one.
const job = await picx.video.create(input, { idempotencyKey: crypto.randomUUID() });
try {
return await job.wait();
} catch (err) {
if (!(err instanceof PicXGenerationFailedError)) throw err;
const msg = err.message;
if (attempt === 0 && RETRY_ONCE.some((m) => msg.startsWith(m))) {
return generateVideo(input, attempt + 1);
}
if (attempt < 3 && RETRY_WITH_BACKOFF.some((m) => msg.startsWith(m))) {
await sleep(Math.min(60_000, 2 ** attempt * 5_000 + Math.random() * 1_000));
return generateVideo(input, attempt + 1);
}
throw err; // content blocks and anything unrecognised stop here
}
}
try {
const video = await generateVideo({ prompt: "slow dolly-in on a ceramic mug, steam rising", duration: 5, resolution: "720p" });
console.log(video.url);
} catch (err) {
if (err instanceof PicXValidationError) console.error("Fix the request (400/422):", err.message);
else if (err instanceof PicXInsufficientCreditsError) console.error("Top up first (402)");
else throw err;
}
Three details in that loop matter more than the backoff curve:
- One idempotency key per attempt. If a network drop hides the 202 and the submit is sent again, the same key returns the same job instead of starting a second paid one. A deliberate retry after a failed job is a new job, so it gets a new key.
- Stop on content blocks. They were 55% of our failures. A content block doesn't go away on retry, and repeated blocked prompts from one key are exactly what safety systems watch for.
- Treat "nothing returned" as retry-once. About one in seven failures was an empty result. A second try usually works. Three empty results in a row usually mean the prompt asks for something the model can't draw.
Why the request causes most failures
When jobs fail in a row, it's tempting to conclude "the model is down". Our data says otherwise, and it matches what we see in support. The biggest single group is content blocks. Most of them come from image models with strict filters on real people and on edits of uploaded photos. Next is invalid input: an aspect ratio or size the chosen model doesn't support, a reference image the model can't fetch, or a video mode without its required file. Most of these now fail at submit, before anything is charged. That change is behind the "free 4xx" rule in the table.
Model-side failures cluster. They arrive in bursts when one model has a bad hour. That's the case for a 503 with backoff and, for urgent work, for switching to a second model rather than hammering the first.
Related reading
- Reading a bad generation: when the job succeeded but the output is wrong
- Synchronous or queued generation: polling, SSE and webhooks compared
- Hard spend caps for agents: stop a retry loop from becoming a bill
- Generate Video API reference
FAQ
Should I retry an AI video generation that failed?
Only if the failure came from the model side: an empty result, a timeout, a 5xx or a 503. Don't retry content blocks (422), invalid requests (400) or insufficient balance (402) without changing the request first. On PicX, content blocks and invalid requests alone were 74% of failed jobs.
Am I charged for a failed generation on PicX?
No. Refusals at submit (400, 402, 422) are never charged. A job that fails after it starts refunds its credits once, automatically.
What does content_policy_violation mean?
The prompt or an input image was refused by the safety check before the job ran. The same request gets the same answer, so rewrite the prompt instead of retrying.
How long can a video generation take before it counts as failed?
Most video jobs finish in under a few minutes. A job still running after 10 minutes is closed by our sweep with "Generation exceeded 10 minutes" and refunded. Retry it once, at a lower resolution if it happens again.
How do I avoid paying twice when a request times out on my side?
Send an idempotency_key. Resubmitting with the same key returns the original job instead of starting a new one.
- #api-design
- #errors
- #retries
- #video
- #reliability


