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

# Errors

> A consistent error envelope and the status codes you'll actually see.

Every error returns the same shape, so you can handle them uniformly:

```json theme={null}
{
  "error": {
    "code": "validation_error",
    "message": "amount must be a positive integer",
    "details": [{ "path": "amount", "issue": "expected positive integer" }]
  }
}
```

* `code` — a stable machine-readable string. Switch on this.
* `message` — a human-readable summary. Log it; don't parse it.
* `details` — present on validation errors, one entry per failed field.

## Status codes

| Status | `code`             | Means                                                                              |
| ------ | ------------------ | ---------------------------------------------------------------------------------- |
| `400`  | `bad_request`      | Malformed request the validator couldn't accept.                                   |
| `401`  | `unauthorized`     | Missing or invalid credential.                                                     |
| `403`  | `forbidden`        | Authenticated, but lacks the permission (or the resource isn't yours).             |
| `404`  | `not_found`        | No such resource in this tenant + mode.                                            |
| `409`  | `conflict`         | A uniqueness or state-machine violation (e.g. duplicate slug, illegal transition). |
| `422`  | `validation_error` | The body failed schema validation. See `details`.                                  |
| `429`  | `rate_limited`     | Too many requests. Back off.                                                       |
| `500`  | `internal_error`   | Our fault. Safe to retry idempotently.                                             |

## Validation is strict and upfront

Every endpoint validates its body against a schema before any work happens — so a bad request never half-applies. A `422` with `details` tells you exactly which field to fix. Required fields, types, enums, lengths, and cross-field rules (e.g. "percentage discount ≤ 100") are all enforced at the edge.

## Retrying safely

A `500` or `429` is safe to retry **if** you send an [`Idempotency-Key`](/api-reference/idempotency) — Duro will replay the original result rather than double-apply. Never blind-retry a mutation without one.
