> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sigmora.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> Every failure carries a stable code alongside its message. Branch on the code.

## The envelope

```json theme={null}
{
  "success": false,
  "error": "Not enough credits: text.script costs 150. Top up to continue.",
  "code": "insufficient_credits",
  "details": { "required": 150, "available": 40 }
}
```

`code` is stable and machine-readable. `error` is for humans and its wording may
change between releases — matching on it will break.

## The distinctions worth handling

Several pairs share a status code but call for opposite responses. That is
precisely why the codes exist.

| Status         | Codes                                             | The difference                                                                                                           |
| -------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `402`          | `insufficient_credits` / `budget_capped`          | Topping up clears the first and does nothing for the second.                                                             |
| `503`          | `upstream_unavailable` / `capability_unavailable` | Retry the first with backoff. The second means this deployment cannot serve that operation and retrying will never help. |
| `400` vs `502` | `invalid_request` / `upstream_error`              | A complaint about your body versus a fault behind ours. Only one is worth fixing in your client.                         |

<Note>
  `429` carries `Retry-After`. Honour the header rather than guessing. Rate limits
  are keyed per key with a workspace-wide ceiling above them, so one noisy key
  cannot starve the rest of a workspace.
</Note>

## Retries, and the one code that means "you already did this"

`conflict` (`409`) is what an `Idempotency-Key` answers with when the retry is not
a retry:

| Situation                                            | Answer                                                                            |
| ---------------------------------------------------- | --------------------------------------------------------------------------------- |
| Same key, same body, the first call finished         | The stored response, replayed. No second charge, no second run.                   |
| Same key, same body, the first call is still running | `409 conflict` — one logical call is already in flight.                           |
| Same key, a different body                           | `409 conflict` — a stored answer to a different question would be a wrong answer. |

A failed call does not hold its key: it delivered nothing and charged nothing, so
retrying it genuinely retries.

<Note>
  `upstream_unavailable` and `rate_limited` are the two codes worth retrying
  unchanged. A failure to verify a credential against a store we could not reach
  is reported as the former (with `Retry-After`), never as `unauthorized` — the
  fault is ours and it clears.
</Note>

## Validation detail

An `invalid_request` from a schema failure carries the field-level detail in
`details`, so you can point at the offending key rather than diffing your payload
against the reference by hand.

```json theme={null}
{
  "success": false,
  "error": "Invalid body",
  "code": "invalid_request",
  "details": { "fieldErrors": { "domain": ["Invalid enum value."] } }
}
```
