# Errors and retries

A refusal is never a bare status code. It is a structured object on the answer's `errors` list, with a class, a code inside the class, a retry verdict and words you can show. This guide is the grammar and the one retry rule.

## The shape

```json
{
  "errors": [
    {
      "major": "NOT_FOUND",
      "minor": "CONSTRUCT",
      "retryable": false,
      "message": "That record could not be found."
    }
  ],
  "extensions": {
    "at": {
      "callId": "01EXAMPLE-CALL-ID",
      "version": {
        "requested": null,
        "serviced": {
          "name": "genesis",
          "number": 0
        }
      }
    }
  }
}
```

`major` names the class, `minor` the code inside it, `retryable` says whether the identical call may succeed if you simply try again, and `message` is the refusal in plain words; `detail` appears when the refusal has more to say. The answer still carries `extensions.at` with its `callId` — quote it when a refusal needs a look.

## The 12 classes

[VALIDATION](/errors/VALIDATION/) · [AUTHN](/errors/AUTHN/) · [AUTHZ](/errors/AUTHZ/) · [NOT_FOUND](/errors/NOT_FOUND/) · [CONFLICT](/errors/CONFLICT/) · [RATE_LIMIT](/errors/RATE_LIMIT/) · [PAYMENT](/errors/PAYMENT/) · [FULFILLMENT](/errors/FULFILLMENT/) · [TAX](/errors/TAX/) · [FISCAL](/errors/FISCAL/) · [INTEGRATION](/errors/INTEGRATION/) · [INTERNAL](/errors/INTERNAL/) — 109 codes in all, every one on the [errors pages](/errors/) with what happened, why, what to do, and whether to retry.

## Four codes every call can answer

- `VALIDATION/INVALID` — the request itself is not acceptable: a missing field, a bad value, an unknown filter field. Fix the request.
- `AUTHN/REQUIRED` — no session token, or an expired one. Exchange the key again ([Authentication and key hygiene](/guides/authentication-and-key-hygiene/)).
- `AUTHZ/FORBIDDEN` — the key's scope does not allow this operation. A wider scope is a new key.
- `RATE_LIMIT/THROTTLED` — too many calls in the window; retryable after the wait it names ([Rate limits](/guides/rate-limits/)).

Beyond the four: an operation that takes an id can answer `NOT_FOUND`; a change can answer `CONFLICT` — the record's state, or an edit made in the meantime, does not allow it — and `VALIDATION/VERSION_REQUIRED` when the envelope named no version. Each operation page lists exactly the codes it can answer.

## The one retry rule

When `retryable` is `true`, the same call, unchanged, may succeed a moment later — wait, then send it again; a `RATE_LIMIT/THROTTLED` refusal names how long in `detail.retryAfterSeconds`. When it is `false`, change something first: the values, the record's state, or who is calling. Retrying a `false` refusal unchanged only repeats it.

## Sign-in refusals say one thing

A failed key exchange answers `AUTHN/LOGIN_FAILED` whatever the cause: an unknown, expired or retired key, and an organization the key does not reach, all read the same. Nothing leaks about which keys exist, so do not branch on the cause — check the secret and the organization, and mint a new key if in doubt.
