# Errors

Every failure is JSON, with a stable machine-readable code:

```json
{ "error": "quota_exceeded", "detail": "This request exceeds the quota on your plan." }
```

Branch on `error`. It is a stable string and will not change meaning. `detail` is a sentence for a
human, may be reworded at any time, and should be logged or displayed rather than matched.

## Codes you should handle

These are the ones worth writing a branch for, because each implies something different to do.

| Status | `error` | Meaning | What to do |
| ---: | --- | --- | --- |
| 401 | `unauthorized` | Missing, unknown, or revoked key. | Fix the credential |
| 402 | `out_of_tokens` | The account balance cannot cover this call. | Top up, then retry |
| 403 | `forbidden` | Valid key, but without the scope this endpoint needs. | Use a key that has it |
| 404 | `not_found` | No such resource, or not one this account can see. | Do not retry |
| 405 | `method_not_allowed` | Wrong HTTP method for this path. | Fix the request |
| 410 | `gone` | It existed and has expired. | Request it again |
| 429 | `quota_exceeded` · `limit_reached` | Past a quota or a cap on your plan. | Wait for the reset |
| 409 | `not_ready` | Still being produced. Its own status is `pending`, `running`, or `ready`. | Poll the status URL |
| 500 | `internal_error` | A fault on our side. It is logged. | Retry with backoff |
| 502 | `unavailable` · `upstream_error` · `no_upstream` · `search_failed` | A component of this request could not complete. Transient. | Retry shortly |
| 504 | `timeout` | Took too long to complete. | Retry |

## Codes that mean the request was malformed

All `400`, all fixed by changing the request, none worth retrying unchanged: `bad_request`,
`invalid_request`, `invalid_json`, `query_required`, `question_required`, `messages_required`,
`id_required`, `invalid_path`, `invalid_repo`, `model_not_allowed`.

The specific ones exist so you do not have to parse a sentence to find out which field was wrong.

## Device authorisation

The device flow uses its own codes, matching the OAuth device grant: `authorization_pending` while
you should keep polling, `slow_down` if you are starting authorizations faster than the limit,
`access_denied` if the person declined, `expired_token` if the code aged out, and `invalid_grant`
for a code that was already exchanged. `verification_failed` means the challenge did not pass.

## What errors deliberately do not tell you

A failure describes what happened to *your request*. It never names an internal component, and it
never carries an upstream message, hostname, or trace id from anything behind the API.

The two distinctions that change what you do — is this mine to fix, and is retrying worth it —
are in the tables above. Anything they do not explain is logged; quote the error code and the
time, and we will read it.

## Retrying

`500`, `502` and `504` deserve exponential backoff with jitter. `429` should wait for the reset
rather than retry quickly. Everything under `4xx` will return the same answer until the request or
the key changes.

Requests are not automatically idempotent. A retried call that already had an effect can have it
twice, so for anything that creates a resource, check whether it exists before retrying rather
than assuming the first attempt did nothing.
