# Errors

Openfort uses conventional HTTP status codes to signal the result of a request:

* **`2xx`** — the request succeeded.
* **`4xx`** — the request failed with the information given (a missing parameter, a bad credential, an unknown resource).
* **`5xx`** — something went wrong on Openfort's side.

## Error response format

Every error returns the same JSON envelope:

```json
{
  "error": {
    "type": "invalid_request_error",
    "message": "The chainId field is required.",
    "details": {
      "chainId": {
        "message": "Required",
        "value": null
      }
    }
  }
}
```

| Field | Type | Description |
| --- | --- | --- |
| `error.type` | string | A stable, machine-readable category. Branch on this. |
| `error.message` | string | A human-readable explanation. May change without notice — don't parse it. |
| `error.details` | object | Present on validation errors. Keyed by the offending field, each with a `message` and the rejected `value`. |

:::tip
Branch your code on the **HTTP status code** and `error.type`, never on `error.message` — messages are human-readable text and can change at any time.
:::

## Status codes

| Status | Meaning | What to do |
| --- | --- | --- |
| `400` | Bad Request — the request was malformed or a parameter was invalid. Check `error.details` for the offending fields. | Fix the request and retry. |
| `401` | Unauthorized — the credential is missing, malformed, or for the wrong mode. See [Authentication](/docs/api-reference/authentication). | Send a valid key or token. |
| `403` | Forbidden — the credential is valid but not permitted to perform this request. | Use a credential with the required access. |
| `404` | Not Found — the route or resource does not exist (or isn't visible to this key's mode). | Check the id and that you're using the matching test/live key. |
| `409` | Conflict — the request conflicts with the current state (for example, a duplicate). | Reconcile state, then retry. |
| `422` | Unprocessable Entity — the request was well-formed but could not be processed. | Adjust the request per `error.message`. |
| `429` | Too Many Requests — you exceeded the rate limit. | Back off and retry after a short delay. |
| `500` | Internal Server Error — an unexpected error on Openfort's side. Transient. | Retry with exponential backoff. |

## Retrying safely

`5xx` responses are transient — retry with exponential backoff. Read requests (`GET`) are always safe to retry. For a write (`POST`, `PUT`, `DELETE`) that returns `5xx`, the operation may still have succeeded on the backend: treat the outcome as unknown and re-read the resource before retrying.
