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

# Errors

> One error shape, a stable code, and what to do about each type.

Every non-2xx response has the same body:

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "validation_failed",
    "message": "email is not a valid address.",
    "param": "email",
    "detail": [
      { "field": "email", "message": "value is not a valid email address", "code": "value_error" }
    ],
    "request_id": "req_8f2c...",
    "documentation_url": "https://docs.chatsailer.com/guides/errors"
  }
}
```

Branch on `type` for control flow and on `code` for specific handling. Both are
stable; `message` is written for humans and may be reworded.

Always log `request_id`. It is echoed in the `X-Request-ID` response header, and
quoting it lets us find your exact request.

## Types

| Type                    | HTTP     | What it means                                       | What to do                                                                      |
| ----------------------- | -------- | --------------------------------------------------- | ------------------------------------------------------------------------------- |
| `invalid_request_error` | 400, 422 | The request is malformed or fails validation        | Fix the request. Read `detail` for the offending fields. Do not retry unchanged |
| `authentication_error`  | 401      | Missing, malformed, revoked, or expired token       | Check the `Authorization` header. Do not retry unchanged                        |
| `permission_error`      | 403      | Authenticated, but not allowed                      | Usually a missing scope, or a workspace write policy. See below                 |
| `not_found_error`       | 404      | No such record, **or** it is outside your workspace | Do not retry                                                                    |
| `conflict_error`        | 409      | Conflicts with existing state, e.g. a duplicate     | Reconcile, then retry                                                           |
| `rate_limit_error`      | 429      | Too many requests                                   | Back off and retry — see below                                                  |
| `api_error`             | 5xx      | Something broke on our side                         | Retry with backoff. If it persists, send us the `request_id`                    |

<Note>
  A 404 does not distinguish "does not exist" from "belongs to another
  workspace". That is deliberate: telling them apart would let anyone probe for
  record ids across workspaces.
</Note>

## Missing scopes

A 403 from a scope check names exactly what is missing, so you do not have to
guess:

```json theme={null}
{
  "error": {
    "type": "permission_error",
    "code": "insufficient_scope",
    "message": "This endpoint requires the contacts:write scope."
  }
}
```

A 403 that is *not* `insufficient_scope` usually means workspace write policy —
for example, an entity mirrored from an external CRM. Call
`GET /v1/capabilities` to see which entities you may write.

## Retrying

Retry `rate_limit_error` and `api_error`. Do not retry 4xx errors unchanged;
they will fail identically.

```python theme={null}
for attempt in range(5):
    response = httpx.request(method, url, headers=headers, json=body)
    if response.status_code < 400:
        return response.json()
    if response.status_code not in (429, 500, 502, 503, 504):
        raise SailerError(response.json()["error"])
    time.sleep(2 ** attempt)
```

Use exponential backoff with jitter. Retrying immediately during a rate limit
extends it.
