# Errors & recovery

Understand what happened and choose the right next step.

First distinguish a rejected HTTP request from a generation that was accepted and failed later. They need different recovery actions.

## Read an API error

Rejected API requests use an `error` object:

```json
{
  "error": {
    "code": "validation_error",
    "message": "The request contains invalid fields.",
    "retryable": false,
    "errors": [
      {
        "pointer": "/quality",
        "code": "invalid_value",
        "message": "Choose a value supported by this model."
      }
    ]
  }
}
```

Use `error.code` for program logic and `error.message` to explain the problem. Validation errors can include `errors`, with a JSON Pointer such as `/quality` identifying the field. Do not depend on exact English message text.

The `X-Request-Id` response header identifies the API request. Keep it with the HTTP status, error code, method, timestamp and run ID when available. Never include your API key in logs or support reports.

## Choose the next step

| HTTP status | Typical cause | What to do |
| --- | --- | --- |
| `400` | Malformed request or conflicting authentication headers. | Correct the request; send one supported authentication header. |
| `401` | Missing or invalid API key. | Check the key and header. Signing into the website does not authenticate the API. |
| `402` | `insufficient_balance`. | Add funds before submitting again. |
| `403` | Account access is denied, or the media source blocks automated downloads. | Check resource access; source URLs must be publicly downloadable. |
| `404` | Resource is missing, inaccessible, or the source URL has expired. | Check the ID and owner, or replace the source URL. |
| `409` | `request_in_progress`: the original submission is still being accepted. | Wait for `Retry-After`, then retry with the **same `Idempotency-Key` and unchanged body**. |
| `409` | `run_not_cancelable`. | Read the run's current state; do not retry cancellation automatically. |
| `410` | `cursor_expired`. | Restart history from your original filters. |
| `410` | `media_expired`: a referenced input has expired. | Replace the input with available media or import the original file again. Repeating the expired reference will not help. |
| `413` | `request_too_large` or `remote_media_too_large`. | Reduce the JSON body to at most 1 MiB, or use a smaller source file, respectively. |
| `415` | Unsupported request content type. | Send JSON with `Content-Type: application/json`. |
| `422` | `idempotency_conflict`: the key belongs to a different request body. | Restore the original body to recover that submission. Use a new key only when you intend to submit a different request; do not switch keys automatically. |
| `422` | `estimate_requires_authentication`. | Supply a valid API key to estimate with media or LoRA inputs. |
| `422` | Invalid or unsupported input. | Fix the fields indicated by the error. Consult the model's current schema. |
| `429` | Too many requests. | Wait, respect `Retry-After` when present and reduce concurrency. |
| `502` / `504` | `remote_media_provider_unavailable` / `remote_media_timeout`. | The source download failed temporarily. Respect any retry delay and follow the safe retry rules below. |
| `503` | A service is unavailable. | Inspect `error.code` and `error.retryable`: maintenance can be temporary; `remote_media_provider_not_configured` requires another source or support. |
| Other `5xx` | Unexpected service or gateway failure. | The outcome may be uncertain. Follow the safe retry rules below. |

HTTP errors and generation errors use the same general error shape, but a failed run is returned inside a successful read response.

## When a submission times out

A timeout, interrupted connection or gateway error does not prove that a generation was rejected. The run may already exist.

- **You have the run ID:** read that run or wait for its webhook.
- **You sent an idempotency key:** repeat the original request with the same key and body after a delay.
- **You have neither:** inspect your recent run history before resubmitting. An unkeyed repeat can create and charge for a second generation.

For future requests, save an idempotency key and request body before sending. Do not switch to a new key after a timeout.

## Retry temporary failures

`error.retryable` indicates whether the API classifies the problem as potentially temporary. It is not permission to create another generation blindly.

For `409 request_in_progress`, the server returns `retryable: true` and `Retry-After: 2`. Wait at least two seconds before repeating the same submission with its original key and body. A new key would create a separate submission and could generate and charge twice. Choose recovery by `error.code`, not HTTP status alone.

For reads, estimates and safe idempotent replays, use bounded exponential backoff with jitter and respect `Retry-After`. Set a maximum waiting time appropriate to your application. Avoid tight retry loops and simultaneous retries from many workers.

Errors such as `execution_paused`, `webhook_unavailable`, `balance_unavailable`, `remote_media_provider_unavailable` and `remote_media_timeout` can include a retry delay. Continue to use the same idempotency key and unchanged body when retrying a submission. A `5xx` status alone does not mean the error is automatically retryable.

An intermediary can return a non-JSON error page. Check the HTTP status and content type before parsing; keep enough diagnostic information to distinguish a gateway problem from an API validation error.

## A generation failed after acceptance

```json
{
  "id": 123456,
  "status": "failed",
  "cost": {
    "unit": "common_fuz",
    "expected": "40.0000",
    "status": "settled",
    "charged": "0.0000"
  },
  "error": {
    "code": "execution_failed",
    "message": "The run could not be completed."
  }
}
```

`GET /run/{id}` still returns HTTP `200` because the run was retrieved successfully. Inspect `status` and `error`, not just the HTTP response.

Keep any usable partial outputs and read the returned cost. Repeating a GET cannot restart the generation. If you decide to try generating again after reviewing the failure, submit a new request with a new idempotency key.

## Common integration mistakes

- **A field is rejected:** use the exact flat field names from the model's `request_schema`. Fields vary between models and versions.
- **Model discovery works but generation returns 401:** discovery is public; generation requires an API key.
- **The request schema changes with quality:** selector defaults and conditional rules are part of the model schema. Do not hard-code one inference-step default for every quality preset.
- **History pagination returns 422:** follow the cursor URL unchanged, with no extra filters. Cursors belong to the account that created them.
- **No webhook arrived:** verify your public HTTPS receiver returns `2xx`; use the existing run ID for recovery. A missing callback is not a reason to submit again.
- **A file URL stopped working:** check `url_expires_at`; save outputs in your own storage before expiration.
