---
title: Request errors
description: "HTTP-level errors: the status table, what carries Retry-After, and which are safe to retry."
---

# Request errors

Use the HTTP status to decide whether to correct a request, restore access, follow an existing job, or retry later. A failed HTTP request and a failed generation are separate events: a successful job read can report a failed job.

## Response structure

Problem responses use the same `Error` object as [Model errors](./errors.html). `status` describes this HTTP response; `code`, when supplied, gives a stable reason. Show `detail` to the customer and keep `request_id` for support. Do not require a code or request id on every error: the captured `401` below has neither.

<!-- gen:fields schema=Error -->
| 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 |  |
<!-- /gen -->

There is no separate runner-error object or error-type header; the problem object and the job's `failure` are the available error surfaces.

## Status reference

The example routes below declare these responses in the OpenAPI contract. `500` is covered by the route's `default` problem response and verified in its handler. Other routes can use the same status for a different refusal, so read their response contract too.

| Status | When it happens | Retry unchanged? | Example endpoint |
| --- | --- | --- | --- |
| `400` | Invalid input, unsupported fields, or a malformed upload. A generation may supply `validation` or a remembered moderation code. | No; correct the request. See the [new-model rollout exception](./errors.html#validation). | `POST /assets` |
| `401` | Authentication is missing, invalid or expired. | No; obtain a valid credential first. | `GET /me` |
| `402` | Insufficient credits, a member budget ceiling, or a plan requirement. | No; resolve the balance, budget or plan. | `POST /generate/image` |
| `403` | The credential or organization role lacks permission. | No; resolve access with the owner or administrator. | `POST /assets/{id}/share` |
| `404` | A resource is unknown or not visible in the current scope. | No; check its id and scope. | `GET /jobs/{id}` |
| `408` | The long-poll window closed before the job became terminal. | Yes; wait again on the same job id. | `GET /jobs/{id}/wait` |
| `409` | Duplicate generation submission; other endpoints also use it for busy or conflicting state. | Follow the supplied `job_id`; do not create a replacement automatically. | `POST /generate/image` |
| `410` | A share link expired, was revoked, or points to trashed/deleted media. | No; ask the owner for a new usable link. | `GET /share/{token}` |
| `413` | The uploaded file exceeds the accepted size. | No; reduce the size or use the appropriate upload flow. | `POST /assets`, `POST /assets/uploads` |
| `422` | A generation or cost-confirmation gate refused the request. | No blind retry; inspect `code`. Re-quote for `confirmation_rejected`. | `POST /generate/image` |
| `429` | A rate limit was reached; generation routes also use it for concurrency or quota. | Yes, after the supplied delay or reset time, with backoff. | `GET /share/{token}` |
| `500` | The server could not complete its operation, such as a job lookup failure. | Retry a read with backoff; establish whether a write took effect before repeating it. | `GET /jobs/{id}` (`default` problem response) |
| `502` | An upstream model or service failed. | Yes for a temporary failure, with backoff and the same generation key. | `POST /generate/video/enhance-prompt` |
| `503` | A required service or feature is temporarily unavailable. | Yes with backoff; persistent configuration unavailability needs support. | `POST /jobs/{id}/sse-ticket` |

This unauthenticated request returned the following production response on 2026-09-21.

```json title="401 Unauthorized — production response"
{
  "type": "about:blank",
  "title": "Unauthorized",
  "status": 401,
  "detail": "valid authentication is required"
}
```

| Field | Meaning |
| --- | --- |
| `type` | Problem type URI. |
| `title`, `status` | The request was unauthorized; this is not a credit refusal. |
| `detail` | Authenticate before retrying. |

A repeated generation returned this production `409`. The missing `code` is deliberate: `status` and `job_id` fully identify the duplicate.

```json title="409 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 | Meaning |
| --- | --- |
| `job_id` | The already accepted generation to read or wait for. |
| `request_id` | This refusal's correlation id for support. |
| `status`, `title`, `type` | The duplicate was refused with `409`; there was no second bill. |
| `detail` | Explanation for the customer; use `job_id` directly instead of extracting it from this text. |

## Handling request errors

Retry `408`, `429`, `502` and `503` with bounded exponential backoff and jitter. A `408` from wait retries only the wait, not generation submission. On `429`, pause your queue so parallel workers do not immediately consume the next available slot together. Stop after a bounded number of attempts and preserve the last problem and request id.

Do not automatically retry `401`, `402`, `403` or `404`. They require a valid credential, sufficient balance or entitlement, permission, or the correct resource and scope. Resolve `400`, `410`, `413` and `422` using the status table rather than repeating the same input.

| Header | Where it is supplied | How to use it |
| --- | --- | --- |
| `Retry-After` | Public share-link `429` responses use `60` seconds. OAuth token/revocation `503` responses also declare it. | Do not retry before the indicated delay. Generic HTTP clients should also handle the standard HTTP-date form. |
| `Retry-After-Reset` | Generation quota refusals when a reset time is available. | Wait until that RFC 3339 UTC instant before submitting again. |
| Neither | Some concurrency and transient service errors do not include a retry header. | Use bounded exponential backoff with jitter; a missing header does not mean retry immediately. |

> [!WARNING]
> A network timeout can hide a successful submit. Keep the same request body and `Idempotency-Key` when retrying that intended generation. If the API returns `409`, follow its `job_id`. A new key requests a new billable generation; duplicate protection is a five-minute window, not a permanent replay guarantee.

If you already have a job id, keep following that job through temporary read errors. Read `failure.code` and `failure.credits_refunded` only when the generation itself fails; a failed status request does not prove that happened. See [Model errors](./errors.html) for typed refusals and job failures.

:::cards
- [Model errors](./errors.html): Interpret typed generation failures and refunds.
- [Concurrency limits](./concurrency-limits.html): Read your current limit and avoid a submit burst.
:::
