# Errors

Every error uses the same JSON shape, including errors from authentication and rate limiting.

```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "ticket_type_sold_out",
    "message": "VIP is sold out.",
    "param": "items[0].ticket_type",
    "details": { "items.0.quantity": ["Only 0 left."] },
    "request_id": "req_01JB...",
    "doc_url": "https://docs.vabu.live/errors.html#ticket_type_sold_out"
  }
}
```

| Field | Meaning |
|---|---|
| `type` | The class of error. See below. |
| `code` | A stable, specific code. Branch on this. |
| `message` | A sentence for people. It can change, so do not match on it. |
| `param` | The request field at fault, when there is one. |
| `details` | Validation messages per field. |
| `request_id` | `req_...`. Include it in support requests. |
| `doc_url` | A link to the matching entry on the [error codes](https://docs.vabu.live/errors.md) page. |

## Types

| Type | HTTP | When | Retry? |
|---|---|---|---|
| `invalid_request_error` | 400, 404, 409, 422 | Bad input, missing resource, conflict or validation | No. Fix the request. |
| `authentication_error` | 401 | Missing or bad key or token | No. |
| `permission_error` | 403 | Scope, role or product not enabled | No. |
| `rate_limit_error` | 429 | You hit a limit | Yes, after `Retry-After`. |
| `idempotency_error` | 409 | Key reused with a different body, or still in flight | See [idempotency](https://docs.vabu.live/concepts/idempotency.md). |
| `provider_error` | 502 | An upstream provider failed | Yes, with backoff. |
| `api_error` | 500, 503 | Our fault | Yes, with backoff. |

## Retrying

Retry `429`, `502`, `500` and `503` with exponential backoff and jitter, and send an [`Idempotency-Key`](https://docs.vabu.live/concepts/idempotency.md) so a retry cannot repeat the action. Do not retry other `4xx` responses without changing the request.

New error codes can be added at any time. Treat an unknown `code` as its `type`.
