A refused submission returns an HTTP problem. An accepted generation can fail later, in which case the job remains readable and explains the failure. Read the machine-readable code to choose an action and the recorded refund outcome to tell the customer what happened to their credits.
The API uses the RFC 7807 Error object. On a submit refusal, code is a field of that problem body; on an accepted job, the typed code is failure.code. An HTTP 200 from GET /jobs/{id} can contain a job whose status is failed.
Field
Type
Required
Description
code
string
No
Machine-readable error code.…
type
string
Yes
A URI reference identifying the problem type.
title
string
Yes
status
integer
Yes
detail
string
No
instance
string
No
job_id
string
No
The job this refusal points at, so a client can follow it without reading detail.…
request_id
string
No
This is the production 400 response for an unknown image model, captured on 2026-09-21.
Branch on code, never on detail or failure.message. Show the explanation to the customer and retain request_id for support. Treat an unfamiliar generation code as job_failed; if no code is supplied, use the status and structured fields such as job_id.
For a failed job, failure.credits_refunded: true means the hold was released and the job cost nothing. false means charged. An absent or null value does not prove either outcome: the hold may still be settling, no hold may exist, or the job may predate the field.
Field
Type
Required
Description
code
GenerationErrorCode
No
Stable refinement of kind, present on every job that failed after this field shipped and derived from the recorded reason for older ones.…
kind
string
Yes
What stopped the job.…
message
string
Yes
Human-readable reason, safe to show the customer as-is. The same text as error.detail.
credits_refunded
boolean, nullable
No
What the credit ledger actually did with this job's credit hold, recorded when the hold settled.…
There is no separate runner-error taxonomy or error-type response header; use the problem status and code, or the job's typed failure.
These eleven values come from the generation contract. The sections below distinguish an HTTP refusal from a code on a failed job; an upstream status inside job.error is not the status of your successful job read.
Value
Meaning
out_of_credits
the wallet cannot pay for the job. Nothing was submitted and nothing was charged. Top up, or submit a cheaper model or fewer seconds.
rate_limit
refused for now, not refused outright - the caller's own concurrency ceiling, or the provider's. Retry later; the same request will be accepted.
prompt_nsfw
a content filter refused the request or the result it produced, on safety grounds. Editing the prompt or the reference media is the fix. Whether the credits were refunded is failure.credits_refunded, not this code.
ip_detected
a content filter refused it for a real person's likeness or a protected work, rather than for safety. The fix is different from prompt_nsfw - change the reference image or the named subject, not the tone of the prompt - which is why it is its own code.
job_failed
the job ran and did not produce an asset for any other reason, including a provider error. The default for an unclassified failure.
timeout
the job ran past its time budget, or a GET /jobs/{id}/wait returned before the job reached a terminal status. On a failed job the work is over; on a wait the job may still be running.
validation
the request itself is not acceptable - a missing or malformed field, a model that does not support the capability asked of it, a duration the model will not render. Nothing was submitted and nothing was charged. Retrying unchanged will fail identically.
confirmation_rejected
the cost confirmation gate did not pass. Either a client showed the customer a quote and the customer declined, or a supplied confirmation token was refused. A submit that carries no confirmation token is never refused for this reason.
canceled
the job was canceled by its owner (POST /jobs/{id}/cancel), not broken. It is the code the SDK wait helpers raise for a canceled job; cancellation on the job says what the provider did and what was refunded. A canceled job carries no failure.
job_not_cancellable
POST /jobs/{id}/cancel refused (409) because the job already finished, or its finished result is already being delivered. The job will reach succeeded or failed on its own; nothing was changed.
approval_required
refused with 402 (title Approval Needed) because the generation would take an agent run past the price its customer approved (the credit_ceiling a guided preset's review card showed, sent with the brief). Nothing was submitted and nothing was charged. The detail names the generation's cost and the new run total; the agent must ask the customer to approve that total before it continues, never retry, split the job or switch models to fit.
run_ended
refused with 409 (title Run Ended) because the agent run this generation was started for (the turn its turn-scoped credential names) has already ended: the platform failed, swept or stopped the turn, or it finished and delivered its reply. Nothing was submitted and nothing was charged. The agent must stop working on that run, never retry or switch models; the customer's chat already shows how it ended.
The caller has reached a generation concurrency ceiling or quota, or capacity is temporarily unavailable. A refused submit has no new job; an already accepted job waiting on provider capacity keeps its existing id.
Property
Value
HTTP status
429 Too Many Requests.
Refund
No refund needed for a local concurrency or quota refusal: no new hold.
Retry unchanged?
Yes, after capacity becomes available or the quota resets.
What to do
Honour Retry-After when supplied and Retry-After-Reset on quota refusals; otherwise use backoff.
The example uses the handler's detail for two active generations on a two-slot plan.
JSON429 — example from the Error schema
{"type":"about:blank","title":"Generation Concurrency Limit Reached","status":429,"detail":"You have 2 generations running, which is the concurrent maximum of 2 for your plan. Wait for one to finish or upgrade your plan to run more at once.","code":"rate_limit"}
Field
Meaning
type, title, status
Problem metadata; the submit was refused with 429.
code
rate_limit signals a temporary refusal.
detail
Active count and ceiling in this example. Read live counts from GET /me, not by parsing this sentence.
A provider's safety filter refused the input or blocked the result it generated. The name does not mean the prompt alone caused it: reference media and output moderation can also trigger this code.
Property
Value
Where it appears
failure.code on a failed job; also an HTTP problem for an immediate refusal.
HTTP status
400 for a remembered policy refusal repeated locally; 422 when an immediate upstream content-policy rejection reaches the submit error handler.
Refund
Yes, unless the provider billed the refused attempt. A refusal whose provider answer shows no billable work, or says nothing either way, is refunded. GPT Image refusals are always refunded, including a block of the generated image, because OpenAI bills only the images it delivers. Read failure.credits_refunded. A local repeat refusal costs nothing.
Retry unchanged?
No automatic retry; change the relevant prompt or media.
What to do
Check whether the explanation identifies the input or generated output, then revise that part.
JSONFailed-job excerpt — example from JobFailure, refunded input refusal
{"status":"failed","failure":{"kind":"moderated","code":"prompt_nsfw","message":"Your request was rejected by the provider's safety system. Your credits were not charged: the provider refused this request before it did any billable work. Edit your prompt or reference image and try again.","credits_refunded":true}}
Field
Meaning
status
The job is terminal; this excerpt is not a complete Job.
failure.kind, failure.code
A moderated job, narrowed to a safety refusal.
failure.message
Customer-facing explanation from the refunded refusal path.
failure.credits_refunded
true confirms a release in this example; do not infer it from the code.
A content filter identified a recognizable person's likeness or a protected work. Changing a safety-related word in the prompt may not fix a refusal of the reference image or named subject.
Property
Value
Where it appears
failure.code on a failed job; also an HTTP problem for an immediate refusal.
HTTP status
400 for a remembered likeness refusal repeated locally; 422 when an immediate upstream likeness rejection reaches the submit error handler.
Refund
Yes, unless the provider billed the refused attempt. A likeness refusal the model provider raises on the finished output is always refunded. failure.credits_refunded is authoritative.
Retry unchanged?
No automatic retry.
What to do
Change the reference image or named subject and follow the provider-specific explanation.
JSONFailed-job excerpt — example from JobFailure, refunded likeness refusal
{"status":"failed","failure":{"kind":"moderated","code":"ip_detected","message":"The reference was refused because it looks like a recognizable real person or a protected work.","credits_refunded":true}}
Field
Meaning
status
Terminal failure; this is a schema-built excerpt, not a captured job.
failure.kind, failure.code
A moderation failure specifically about likeness or protected work.
failure.message
The explanation to show; the example is shortened.
The generation did not produce an asset for an operational or otherwise unclassified reason. This is also the fallback for an unfamiliar generation error code.
Property
Value
Where it appears
failure.code on the failed job, or a submit problem when submission itself fails.
HTTP status
Submit handler: 502 for an upstream failure, 500 for an internal failure; 422 for an upstream client or provider-billing rejection without a more specific code.
Job read
GET /jobs/{id} can still return 200; the job's error.status describes its failure, commonly 502.
Refund
Yes for an operationally failed job. Confirm the settled outcome in failure.credits_refunded.
Retry unchanged?
With backoff for a temporary operational failure; first check any existing job. A 422 needs its explanation addressed.
What to do
Keep the job id and request id, read the failure, and submit a new attempt only when the previous job is terminal.
The message and refund below match the observed Gemini video failure used in the Jobs guide. No complete failed-job production fixture was saved; this is a schema-built excerpt.
JSONFailed-job excerpt — example from JobFailure
{"status":"failed","failure":{"kind":"error","code":"job_failed","message":"Video generation failed due to an internal server issue…","credits_refunded":true}}
The optional cost confirmation did not match the request being submitted: it expired, was invalid, belonged to another account, or the body, model or price changed. The enum also covers a client declining a quote. A submit without a confirmation token is never refused for this reason.
Property
Value
HTTP status
422 Unprocessable Entity.
Refund
No refund needed: the confirmation gate runs before the credit reservation.
Retry unchanged?
No; obtain a fresh quote first.
What to do
Call POST /jobs/cost, show the new price, and submit that exact body with its new token before expires_at.
JSON422 — example from the Error schema
{"type":"about:blank","title":"Unprocessable Entity","status":422,"detail":"this quote has expired — ask for a fresh price and try again","code":"confirmation_rejected"}
Field
Meaning
type, title, status
The confirmation gate refused the submit with 422.
The job's owner canceled it with POST /jobs/{id}/cancel. It is not a failure: the job carries status: canceled and a cancellation object, never a failure. The SDK wait helpers raise this code for a canceled job.
Property
Value
HTTP status
None: it is a job state. The cancel call itself answers 200.
On the job
status: canceled, with cancellation.stage, provider_cancel, settlement and message.
Refund
cancellation.settlement: refunded before the job reached the provider or when the provider stopped it without billing, partially_refunded when the provider bills only the part it rendered, charged when it finished and billed, pending until the provider answers.
Retry unchanged?
Submit a new job if you still want the result; a canceled job is never delivered.
What to do
Show cancellation.message as written, and read the job again while settlement is pending.
POST /jobs/{id}/cancel refused because the job already finished, or the provider already finished it and the result is being delivered.
Property
Value
HTTP status
409 Conflict.
Refund
Nothing changed: the job settles under the usual rules when it reaches succeeded or failed.
Retry unchanged?
No; the job will not become cancelable.
What to do
Read the job; it reaches its own terminal status shortly.
JSON409 — example from the Error schema
{"type":"about:blank","title":"Conflict","status":409,"detail":"This job already finished, so there is nothing to cancel.","code":"job_not_cancellable"}
Field
Meaning
type, title, status
The cancel was refused with 409.
code
job_not_cancellable means the job is finished or finishing.
An agent run started from a guided preset carries the price its review card showed ("Up to N", sent as credit_ceiling with the brief). A generation that would take the run past that price is refused before anything is submitted, so the agent stops and asks the customer to approve the new total instead of spending it.
Property
Value
HTTP status
402 Approval Needed.
Refund
No refund needed: nothing submitted and nothing charged.
Retry unchanged?
No. Ask the customer to approve the new total; do not retry, split the job or switch to a cheaper model to fit.
What to do
Tell the customer what the next generation makes, what it costs and the run's new total, and continue once they approve.
JSON402 — example from the Error schema
{"type":"about:blank","title":"Approval Needed","status":402,"detail":"This generation costs 65 credits and would bring this run to 163 credits, above the 147 the customer approved. Nothing was charged. Stop and ask the customer to approve 163 credits for this run before you continue; do not retry, split the job or switch to a cheaper model to fit.","code":"approval_required"}
Field
Meaning
type, title, status
The generation was refused with 402; the account can pay, the run's approved price cannot.
code
approval_required distinguishes the run price from an empty wallet (out_of_credits) and a plan limit.
detail
The generation's cost, the run's new total and the approved price.
A generation submitted with an agent run's turn-scoped credential after the platform has already settled that run: the turn failed, the platform swept it, the customer stopped it, or it finished and its reply was delivered. The run's pod can keep working for a while after that, and anything it started now would be charged for work nobody receives as the run's reply, so it is refused before anything is submitted.
Property
Value
HTTP status
409 Run Ended.
Refund
No refund needed: nothing submitted and nothing charged.
Retry unchanged?
No. The run is over; retrying, switching models or splitting the job is refused the same way.
What to do
Stop working on that run. The customer's chat already shows how it ended, and anything the run finished is in their Library; they continue by sending a new message.
JSON409 — example from the Error schema
{"type":"about:blank","title":"Run Ended","status":409,"detail":"This agent run already ended, so this generation was refused. Nothing was charged. Stop working on this run: do not retry, switch models or start new generations for it. The customer's chat already shows how the run ended; they can send a new message to continue.","code":"run_ended"}
Field
Meaning
type, title, status
The generation was refused with 409; the account can pay, but the run it belongs to is over.
code
run_ended distinguishes an ended run from the run's price (approval_required) and an empty wallet (out_of_credits).
detail
Whether the run ended or finished, and what the agent does next.
The same generation body and Idempotency-Key, or the same body with no key both times, is refused within five minutes with 409 and the existing job_id. It carries no code: the status and job identifier are the contract. That refusal is never billed.
JSON409 Conflict — production response
{"detail":"this exact request was already submitted as job 35b6ff8d-0c78-440b-bdb3-bfe476b56d76 less than 5m0s ago and has not been billed twice — check it with GET /jobs/35b6ff8d-0c78-440b-bdb3-bfe476b56d76. To run it again anyway, resubmit with a different Idempotency-Key header.","job_id":"35b6ff8d-0c78-440b-bdb3-bfe476b56d76","request_id":"localhost/7bnUUXx7b2-007291","status":409,"title":"Conflict","type":"about:blank"}
Field
Type
Required
Description
type
string
Yes
A URI reference identifying the problem type.
title
string
Yes
status
integer
Yes
detail
string
No
job_id
string
No
The job this refusal points at, so a client can follow it without reading detail.…
request_id
string
No
Follow job_id with a job read or wait. Keep the same key on retries; use a distinct key only for a deliberate new generation of identical input. Sets have separate per-member behavior; see Sets.
Retry 408 waits on the existing job. Retry temporary 429, 502 and 503 refusals with bounded exponential backoff and jitter, keeping the same Idempotency-Key on a generation submit. Honour any Retry-After delay and Retry-After-Reset time. A lost response may have accepted the work, so a resulting 409 is your route back to that job.
Fix authentication, balance, access or validation problems before retrying. For a failed job, inspect failure.code, failure.message and failure.credits_refunded before creating another attempt. Request errors covers the complete HTTP status table.