# Errors

> **TL;DR** — Branch on `error.code`, not the message. Retry only the codes marked Yes, and network errors, with the same `Idempotency-Key`. Each error links to its own section on this page.

Errors are JSON: `{"error": {"code": "...", "message": "..."}}`. Branch on `code`; the message is for people and may change. Every response has an `X-Request-Id` header: quote it to support.

Retry only the codes marked **Yes** below and network errors, with the same `Idempotency-Key`. Wait for `Retry-After` when the response has it, otherwise back off with jitter. Never retry the others: the same request will fail the same way.

## Error body and headers
**Rolling out.** Every `/v2` error answer, in both shapes (runs and [Jobs API](https://docs.wandura.ai/developers/api-v2/errors#jobs-errors)), gains these, on top of every field it already has:

| Where | Name | What it says |
|---|---|---|
| Body | `error.docs_url` | A link to this code's section on this page, for example `https://docs.wandura.ai/developers/api-v2/errors#product-busy`. A code without its own section links to this page. |
| Body | `error.retryable` | `true` when the same request can succeed later unchanged: every `429` and `5xx` (for example `product_busy`, `service_unavailable`). `false` for input and auth errors. Jobs API errors already have it. |
| Header | `Wandura-Error-Code` | The same value as `error.code`. |
| Header | `Wandura-Should-Retry` | `true` or `false`, the same value as `error.retryable`. |

`Retry-After` and `X-Request-Id` stay as they are. All four are optional: ignore them if you do not use them, and treat a missing `retryable` like the table below.

```http
HTTP/1.1 429
Retry-After: 5
Wandura-Error-Code: product_busy
Wandura-Should-Retry: true

{"error":{"code":"product_busy","message":"…","limit":20,"retry_after_s":5,"retryable":true,"docs_url":"https://docs.wandura.ai/developers/api-v2/errors#product-busy"}}
```

## Run errors
| HTTP | `code` | Retry? |
|---|---|---|
| 400 | [`invalid_request`](https://docs.wandura.ai/developers/api-v2/errors#invalid-request) | No |
| 400 | [`idempotency_key_required`](https://docs.wandura.ai/developers/api-v2/errors#idempotency-key-required) | No |
| 400 | [`end_user_id_required`](https://docs.wandura.ai/developers/api-v2/errors#end-user-id-required) | No |
| 401 | [`invalid_api_key`](https://docs.wandura.ai/developers/api-v2/errors#invalid-api-key) | No |
| 401 | [`api_key_expired`](https://docs.wandura.ai/developers/api-v2/errors#api-key-expired) | No |
| 402 | [`wallet_empty`](https://docs.wandura.ai/developers/api-v2/errors#wallet-empty) | No |
| 403 | [`insufficient_scope`](https://docs.wandura.ai/developers/api-v2/errors#insufficient-scope) | No |
| 403 | [`style_not_allowed`](https://docs.wandura.ai/developers/api-v2/errors#style-not-allowed) | No |
| 404 | [`style_not_found`](https://docs.wandura.ai/developers/api-v2/errors#style-not-found) | No |
| 404 | [`run_not_found`](https://docs.wandura.ai/developers/api-v2/errors#run-not-found) | No |
| 404 | [`not_found`](https://docs.wandura.ai/developers/api-v2/errors#not-found) | No |
| 409 | [`idempotency_key_reused`](https://docs.wandura.ai/developers/api-v2/errors#idempotency-key-reused) | No |
| 409 | [`run_not_cancelable`](https://docs.wandura.ai/developers/api-v2/errors#run-not-cancelable) | No |
| 413 | [`file_too_large`](https://docs.wandura.ai/developers/api-v2/errors#file-too-large) | No |
| 415 | [`unsupported_content_type`](https://docs.wandura.ai/developers/api-v2/errors#unsupported-content-type) | No |
| 429 | [`end_user_cap_reached`](https://docs.wandura.ai/developers/api-v2/errors#end-user-cap-reached) | Yes |
| 429 | [`daily_cap_reached`](https://docs.wandura.ai/developers/api-v2/errors#daily-cap-reached) | No |
| 429 | [`gateway_rate_limited`](https://docs.wandura.ai/developers/api-v2/errors#gateway-rate-limited) | Yes |
| 429 | [`uploads_rate_limited`](https://docs.wandura.ai/developers/api-v2/errors#uploads-rate-limited) | Yes |
| 429 | [`product_busy`](https://docs.wandura.ai/developers/api-v2/errors#product-busy) | Yes |
| 500 | [`internal_error`](https://docs.wandura.ai/developers/api-v2/errors#internal-error) | Yes |
| 503 | [`style_unavailable`](https://docs.wandura.ai/developers/api-v2/errors#style-unavailable) | Yes |
| 503 | [`uploads_unavailable`](https://docs.wandura.ai/developers/api-v2/errors#uploads-unavailable) | Yes |
| 503 | [`service_unavailable`](https://docs.wandura.ai/developers/api-v2/errors#service-unavailable) | Yes |

Submit answers, `429` ones included, carry `X-Concurrency-Limit`, `X-Concurrency-Running` and `X-Concurrency-Waiting`. Each `product_busy` and `gateway_rate_limited` answer is a refusal: it is listed at `GET /v2/queue/refusals`. See [Queue and congestion](https://docs.wandura.ai/developers/api-v2/queue-and-congestion).

A run that fails after it was accepted is not an HTTP error: the run ends `failed` or `nsfw` with a [free-run reason](https://docs.wandura.ai/developers/api-v2/concepts#free-runs), and you are not charged.

#### `invalid_request`
`400`, do not retry. The body or a parameter is wrong; the message says which.

#### `idempotency_key_required`
`400`, do not retry. Send an `Idempotency-Key` header (max 255 chars) on every submit.

#### `end_user_id_required`
`400`, do not retry. `end_user_id` is required: the id of the person in your app this run is for.

#### `invalid_api_key`
`401`, do not retry. Missing, unknown or revoked API key.

#### `api_key_expired`
`401`, do not retry. This API key has expired. Create or rotate a key.

#### `wallet_empty`
`402`, do not retry. The product wallet and the org pool cannot cover this run. Add money, then retry.

#### `insufficient_scope`
`403`, do not retry. **Rolling out.** This key has the Read only permission: it can read runs and styles, not submit, cancel, share or upload. Use a key with the Run styles permission. 
#### `style_not_allowed`
`403`, do not retry. **Rolling out.** This key may run only some styles, and this is not one of them. Use a key that lists it, or ask your org admin.

A `403` only comes from a key made with a permission or a list of allowed styles (see [API keys](https://docs.wandura.ai/developers/api-v2/concepts#api-keys)). A key made without them never gets one.

#### `style_not_found`
`404`, do not retry. No style with that id for your org (or not public).

#### `run_not_found`
`404`, do not retry. No run with that id for this product.

#### `not_found`
`404`, do not retry. No such endpoint.

#### `idempotency_key_reused`
`409`, do not retry. This `Idempotency-Key` was already used with a different body. Use a new key for a new run.

#### `run_not_cancelable`
`409`, do not retry. Only queued runs can be canceled.

#### `file_too_large`
`413`, do not retry. The file is over the size limit for its type.

#### `unsupported_content_type`
`415`, do not retry. Uploads accept JPEG, PNG, WEBP, MP4, MOV and WEBM.

#### `end_user_cap_reached`
`429`, retry. This end user reached the daily run cap. Retry after the `Retry-After` header (resets 00:00 UTC).

#### `daily_cap_reached`
`429`, do not retry. Your product reached its daily spend cap: live charges plus open holds for the current Vietnam calendar day (resets 00:00 UTC+7). Nothing was created or charged. The body carries `cap_usd`, the cap in USD, and there is no `Retry-After`. Wait for the new day, or ask your org admin to raise the cap.

#### `gateway_rate_limited`
`429`, retry. A gateway key's product reached its runs-per-minute limit. Retry after the `Retry-After` header.

#### `uploads_rate_limited`
`429`, retry. Your org reached its presigned uploads per minute. Retry after the `Retry-After` header.

#### `product_busy`
`429`, retry. Your product's concurrency slots and its queue are full. Retry after the `Retry-After` header (about 5 s).

#### `internal_error`
`500`, retry. Something went wrong on our side. Retry with the same `Idempotency-Key`; quote `X-Request-Id` to support.

#### `style_unavailable`
`503`, retry. This style cannot run on a live key right now (no model behind it). Test keys still return a sample.

#### `uploads_unavailable`
`503`, retry. Uploads are not available on this deployment right now.

#### `service_unavailable`
`503`, retry. The job queue is briefly unavailable; nothing was created or charged. Retry with the same `Idempotency-Key` after the `Retry-After` header.

## Jobs API errors
The [Jobs API](https://docs.wandura.ai/developers/api-v2/jobs#errors) (`/v2/jobs`, `/v2/models`, `GET /v2/styles`) answers `{"error": {"code", "message", "retryable"}}`, plus `details` for `INVALID_INPUT`, with its own codes:

| HTTP | `code` | `retryable` |
|---|---|---|
| 422 | [`INVALID_INPUT`](https://docs.wandura.ai/developers/api-v2/errors#jobs-invalid-input) | false |
| 401 | [`UNAUTHORIZED`](https://docs.wandura.ai/developers/api-v2/errors#jobs-unauthorized) | false |
| 402 | [`INSUFFICIENT_CREDITS`](https://docs.wandura.ai/developers/api-v2/errors#jobs-insufficient-credits) | false |
| 403 | [`FORBIDDEN`](https://docs.wandura.ai/developers/api-v2/errors#jobs-forbidden) | false |
| 404 | [`NOT_FOUND`](https://docs.wandura.ai/developers/api-v2/errors#jobs-not-found) | false |
| 409 | [`IDEMPOTENCY_KEY_REUSED`](https://docs.wandura.ai/developers/api-v2/errors#jobs-idempotency-key-reused) | false |
| 409 | [`JOB_NOT_CANCELLABLE`](https://docs.wandura.ai/developers/api-v2/errors#jobs-job-not-cancellable) | false |
| 429 | [`RATE_LIMITED`](https://docs.wandura.ai/developers/api-v2/errors#jobs-rate-limited) | true |
| 429 | [`PRODUCT_BUSY`](https://docs.wandura.ai/developers/api-v2/errors#jobs-product-busy) | true |
| 429 | [`END_USER_LIMIT_REACHED`](https://docs.wandura.ai/developers/api-v2/errors#jobs-end-user-limit-reached) | true |
| 429 | [`DAILY_CAP_REACHED`](https://docs.wandura.ai/developers/api-v2/errors#jobs-daily-cap-reached) | false |
| 500 | [`INTERNAL_ERROR`](https://docs.wandura.ai/developers/api-v2/errors#jobs-internal-error) | true |
| 503 | [`SERVICE_UNAVAILABLE`](https://docs.wandura.ai/developers/api-v2/errors#jobs-service-unavailable) | true |

#### `INVALID_INPUT`
`422`, do not retry. The body or a header is wrong; `details[]` lists each `{field, reason}`. The run endpoints (`POST /v2/runs`, `POST /v2/styles/{style}`) answer the same code with `400` when a `resolution` or the number of files does not fit the style.

#### `UNAUTHORIZED`
`401`, do not retry. Missing, unknown, revoked or expired API key.

#### `INSUFFICIENT_CREDITS`
`402`, do not retry. The product's wallet cannot cover the job. Add funds, then retry.

#### `FORBIDDEN`
`403`, do not retry. The key is valid but may not do this. Rolling out: a key with the Read only permission submitting or cancelling, or a key limited to other styles (see [API keys](https://docs.wandura.ai/developers/api-v2/concepts#api-keys)). Not returned before that. 
#### `NOT_FOUND`
`404`, do not retry. No such job, model or endpoint for this key.

#### `IDEMPOTENCY_KEY_REUSED`
`409`, do not retry. This `Idempotency-Key` was already used with a different body. Use a new key for a new job.

#### `JOB_NOT_CANCELLABLE`
`409`, do not retry. Only queued jobs can be cancelled.

#### `RATE_LIMITED`
`429`, retry. Too many jobs submitted this minute for this product. Retry after the `Retry-After` header.

#### `PRODUCT_BUSY`
`429`, retry. Your product's concurrency slots and its queue are full. Retry after the `Retry-After` header (about 5 s).

#### `END_USER_LIMIT_REACHED`
`429`, retry. This end user reached the daily job cap. Retry after the `Retry-After` header (resets 00:00 UTC).

#### `DAILY_CAP_REACHED`
`429`, do not retry. This product reached its daily spend cap (the day resets at 00:00 Vietnam time, UTC+7). Nothing was created or held. Wait for the new day, or ask your org admin to raise the cap. See [Wandura C1.1](https://docs.wandura.ai/developers/api-v2/wandura-c1-1#errors).

#### `INTERNAL_ERROR`
`500`, retry. Something went wrong on Wandura's side. Retry with the same `Idempotency-Key`.

#### `SERVICE_UNAVAILABLE`
`503`, retry. This style cannot run on a live key right now (test keys still return a sample). Or the job queue is briefly unavailable: nothing was created or charged; retry after `Retry-After` with the same `Idempotency-Key`.

A job that ends without a result (`failed`, `timeout`) is not an HTTP error: its `error.code` is listed under [Statuses](https://docs.wandura.ai/developers/api-v2/jobs#statuses) on the Jobs API page.
