Errors

All API errors — authentication included — share a single envelope: an error object with a stable code, a human-readable message, and machine-readable details. Branch on code, not on the message text.

error envelope
{
  "error": {
    "code": "ERR_INVALID_REQUEST",
    "message": "Invalid request body",
    "details": { "errors": [ /* field-level validation problems */ ] }
  }
}

Error codes

CodeHTTPNotes
ERR_INVALID_REQUEST400Malformed body or parameters. details.errors lists the field-level validation problems. One non-field case: details.reason = provider_policy_no_route — the providers enabled for your account cannot render this model at the requested resolution or aspect ratio. Not retryable as-is: change resolution or contact support to widen your account's provider set.
ERR_CONTENT_POLICY400Reserved. Prompts are screened by the model's safety filter at generation time; a declined prompt fails the creation (fully refunded) rather than returning this error.
ERR_API_KEY_INVALID401No API key on the request, or a key that is not recognized — details.reason is api_key_missing or api_key_invalid.
ERR_ACCOUNT_DISABLED403The key is valid but the account is disabled. Contact your account manager.
ERR_API_SUSPENDED403The key is valid but API access is paused by your account manager; the portal still works. Contact your account manager to resume.
ERR_INSUFFICIENT_BALANCE402Wallet can't cover the request (details include required — the USD amount the request needs — available — balance plus any credit line at the moment of refusal, so you can tell an empty wallet from an exhausted line — and a top-up URL), or a per-key monthly budget was exceeded (details.reason = monthly_budget_exceeded with the cap).
ERR_MODEL_NOT_FOUND404The requested model is not available to your account.
ERR_NOT_FOUND404The creation id doesn't exist or doesn't belong to your account — both cases return the same error.
ERR_RATE_LIMIT429Per-account request rate or the polling minimum interval was exceeded — or, with details.reason: burst_block, too many rejected auth requests came from your IP address in one minute: a block of up to 120 s. A suspended/disabled-account 403 loop blocks the public API only and your portal stays reachable; an invalid-key 401 loop blocks all API traffic from that IP. Honor Retry-After.
ERR_QUOTA_EXCEEDED429A concurrency or capacity limit was hit — details.scope / details.reason identifies which. Honor Retry-After.
ERR_INTERNAL500Unexpected server error.
ERR_INPUT_FETCH_FAILED502A source image could not be downloaded. No generation started and nothing was charged. details.reason is input_fetch_failed; details.retryable is true for network failures, timeouts and origin 5xx/429 responses, and false for other origin 4xx responses. Retry only when true; otherwise fix or replace the source URL. details.input_index identifies the zero-based image_urls item when available; URLs and credentials are omitted.
ERR_GENERATION_FAILED502Generation failed after acceptance. The charge has already been refunded in full — safe to retry.
ERR_PROVIDER_UNAVAILABLE503The image provider is temporarily unavailable. With details.reason: provider_unavailable an upstream outage while starting the job — the charge is refunded in full and the job never started. With details.reason: overloaded the service itself was shedding load and nothing was charged. With details.reason: no_provider_enabled no provider is currently enabled for this model on your account — refused before any charge; it clears when the provider is switched back on, so honor Retry-After and retry, or contact your account manager if it persists. In every case honor Retry-After and retry shortly.

Authentication errors (401 / 403)

Authentication failures use the same envelope: ERR_API_KEY_INVALID (401) and ERR_ACCOUNT_DISABLED / ERR_API_SUSPENDED (403), with the exact cause in details.reason. See Authentication.

401 / 403 responses
HTTP/1.1 401 Unauthorized
{"error": {"code": "ERR_API_KEY_INVALID", "message": "no API key on the request", "details": {"reason": "api_key_missing"}}}

HTTP/1.1 401 Unauthorized
{"error": {"code": "ERR_API_KEY_INVALID", "message": "API key not recognized", "details": {"reason": "api_key_invalid"}}}

HTTP/1.1 403 Forbidden
{"error": {"code": "ERR_ACCOUNT_DISABLED", "message": "this account is disabled", "details": {"reason": "account_disabled"}}}

HTTP/1.1 403 Forbidden
{"error": {"code": "ERR_API_SUSPENDED", "message": "API access is paused for this account by its account manager", "details": {"reason": "api_suspended"}}}

Retry-After

Every 429 and 503 response includes a Retry-After header (in seconds). Back off for at least that long before retrying — see Rate limits.

Retryability & refunds

A 502 ERR_GENERATION_FAILED (a job that ran and failed) and a 503 ERR_PROVIDER_UNAVAILABLE (the provider was down before the job started) both mean the up-front charge has already been refunded — or, for a 503 with details.reason: overloaded or details.reason: no_provider_enabled, was never made — so you can safely submit the request again. For the 503, honor the Retry-After interval first. Use an idempotency key on retries so a request that actually succeeded is never charged twice.

Failed creations

When a creation fails after the 202 acceptance, polling GET /creations/{id} returns status: "failed" with an error string naming the failure reason, and the charge is refunded automatically. error is always one of:

error values on a failed creation
"Prompt rejected by the safety filter. Make the scene more specific — abstract or generic prompts are declined more often."
"The source image was rejected. Try a different photo."
"Generation failed. Please try again."
"Nothing was rendered for this request. Describe the image you want in the prompt, or attach the source photo it refers to."
"Generation timed out."
"The service is temporarily unavailable. Please try again later."
"Generation failed."

The safety-filter and source-image values are about the request itself and not retryable as-is — revise the prompt or attach a different source photo. The rest are safe to retry: the charge is refunded, so submit the request again.