# API errors and status codes

> The error format of the Privatus Analytics API, every HTTP status and error code (401, 402, 403, 422, 429 and more), and when to retry a failed request.

Errors always have the same shape:

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Name can't be blank",
    "details": { "name": ["can't be blank"] }
  }
}
```

`details` is present for validation errors: field names mapped to
messages.

| Status | `code` | When |
|---|---|---|
| 400 | `bad_request` | Malformed input, e.g. `filters` that isn't a JSON array |
| 401 | `unauthorized` | No token, or an invalid, revoked or expired one |
| 402 | `plan_limit` | A plan limit was reached (sites, goals, funnels, members…). The message says which and how to upgrade |
| 403 | `forbidden` | Missing permission or site access, or the token's IP allowlist |
| 404 | `not_found` | The record doesn't exist or you can't see it |
| 422 | `validation_failed` | Invalid input (see `details`) |
| 423 | `locked` | The workspace's dashboards are locked by Privatus Analytics support. Reaching the monthly limit never locks the API (see [Monthly limit](/docs/billing/overage)) |
| 429 | `rate_limited` | Too many requests ([Rate limits](/docs/api/rate-limits)) |
| 5xx | Any | Our fault. Retry with backoff and check the status page |

Handle errors by `code`, not by `message`: messages are for people and may
change.

## Retrying

- `GET` requests are always safe to retry.
- Retry `429` and `5xx` with exponential backoff.
- Don't retry `4xx` other than `429` without changing the request.
