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

# Errors

> One error envelope, the codes it can carry, and which failures are worth retrying.

Every failure returns the same envelope, so a client branches on `error.code`
rather than parsing prose or matching on status alone.

```json theme={"system"}
{
  "error": {
    "code": "insufficient_scope",
    "message": "This endpoint requires the audit:read scope. The token's role (editor) may also be too low to hold it.",
    "requiredScopes": ["audit:read"],
    "requestId": "x8bj1FBgnmyN58vj2wnOX",
    "docsUrl": "https://docs.dueflow.co/errors#insufficient_scope"
  }
}
```

| Field            | Always present | Meaning                                                                     |
| ---------------- | -------------- | --------------------------------------------------------------------------- |
| `code`           | yes            | Stable, machine-readable. Branch on this.                                   |
| `message`        | yes            | Written for a human reading a log. Wording can change; don't match on it.   |
| `requestId`      | yes            | Also returned as the `X-Request-Id` header, on success and failure alike.   |
| `param`          | no             | The offending field, when the failure is attributable to one.               |
| `requiredScopes` | no             | On `insufficient_scope`: the scopes that would have made this call succeed. |

## Codes

Every error links here, at the anchor for its own code.

### invalid\_request

`400` · A malformed body, a bad query parameter, or a missing `Idempotency-Key` on a
write. `param` names the offending field. Don't retry — fix the request.

### invalid\_token

`401` · The bearer token is missing, malformed, expired or revoked. All four are
reported identically so that probing can't tell them apart. Issue a new token.

### insufficient\_scope

`403` · The token doesn't carry a scope the endpoint requires; `requiredScopes` lists
what would work. Retrying won't help — see
[403 can appear without your token changing](#403-can-appear-without-your-token-changing).

### not\_found

`404` · No such resource *for this token*. Also returned for a resource that exists but
belongs to another organization — see below.

### method\_not\_allowed

`405` · Known path, wrong method.

### conflict

`409` · The request contradicts current state: a duplicate email, an `Idempotency-Key`
reused with a different body, or a transition that no longer applies (reviewing
a reimbursement that someone already reviewed). Retrying the same call gives the
same answer.

### rate\_limited

`429` · The token's bucket for this minute is exhausted. `Retry-After` says how long to
wait. See [Rate limits](/rate-limits).

### internal\_error

`500` · Our fault. It's already reported to our error tracker along with the
`requestId` in your response. Retry with backoff.

### provider\_error

`502` · An upstream provider — email, SMS, payments — rejected the call, so the
operation did not take effect. Safe to retry.

## 404 means "not visible to you"

A resource belonging to another organization returns `404`, not `403`. Existence
is itself information: a `403` would confirm that an id is real, letting anyone
enumerate ids across organizations. This applies to chapters too — a national
reading a chapter that isn't its direct child gets a `404`.

## 403 can appear without your token changing

Effective permissions are re-derived on every request:

```
granted scopes ∩ role ceiling ∩ organization policy
```

So a call that worked yesterday can return `insufficient_scope` today because
your role was lowered, or because an admin blocked that scope for the whole
organization. Treat `insufficient_scope` as a condition to surface to a human,
not to retry.

## Retrying safely

Retry `429`, `500` and `502` with exponential backoff and jitter. Do not retry
`4xx` other than `429` — the request will fail identically.

Every `POST` requires an `Idempotency-Key`, so a retry of a write is safe by
construction: reusing the key replays the original response instead of creating
a second record. See [Idempotency](/idempotency).

<Note>
  Include `requestId` when you contact support. It identifies the exact request
  in our logs, including the endpoint, the token used and the failure.
</Note>
