# Errors

Successful calls answer `200` (or `201` when something was created) with the resource itself as the body. There is
no wrapper around a single resource.

Failed calls answer a `4xx` or `5xx` status with this body:

```json
{
  "status": false,
  "error": {
    "message": "The title field is required.",
    "errors": {
      "title": ["The title field is required."]
    }
  }
}
```

- `status` is always `false` on an error.
- `error.message` is a human readable summary. Show it in logs, do not parse it.
- `error.errors` appears on validation errors (`422`). Keys are field names, values are lists of messages.
- Some errors add `error.reason` (a stable code) and `error.details`. See [client lookup errors](#client-lookup-errors).

## Status codes

| Status | When |
| --- | --- |
| `200` | Success. Also returned when a create is replayed with the same `external_ref`. |
| `201` | A resource was created. |
| `401` | The API key is missing, wrong or revoked. |
| `403` | The key lacks the ability, is switched off, the IP is not allowed, or the organization lacks the add-on the endpoint needs. |
| `404` | The resource does not exist, belongs to another organization, or sits on a board the key cannot open. Body: `{"status": false, "error": {"message": "Not found"}}`. |
| `405` | The method is not allowed on this path. |
| `409` | A conflict, for example an `external_ref` used by a deleted job, or a request with the same `Idempotency-Key` still running. |
| `422` | Validation failed, or the action is not possible in the current state (for example completing a cancelled job). |
| `429` | Too many requests. See [API key settings](/concepts/api-keys/). |
| `500` | An error on our side. The message is `Server Error`. Retry later; contact support if it persists. |

## Business rule errors

Some `422` answers are not about a single field. They have a `message` and no `errors` object:

```json
{ "status": false, "error": { "message": "Reopen the job to change its SLA." } }
```

## Client lookup errors

When you identify a client by phone, ISP username or external reference on a service job, a failed lookup answers
`422` with a `reason`:

| `reason` | Meaning | `details` |
| --- | --- | --- |
| `client_not_found` | No client matches. Only on create; list filters return an empty list instead. | `field` |
| `client_ambiguous` | More than one client matches. Send `client_id` instead. | `field`, `candidates` (up to 10 client ids) |
| `client_lookup_conflict` | More than one lookup field was sent. Send only one. | `sent` |

```json
{
  "status": false,
  "error": {
    "message": "client_phone matches more than one client; send client_id instead.",
    "reason": "client_ambiguous",
    "details": {
      "field": "client_phone",
      "candidates": ["b8f2a1c4-7d39-4e52-9a06-1f4c8b3d5e27", "0d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6"]
    }
  }
}
```

## Handling errors well

- Retry `429` after `Retry-After`, and `500`, `502`, `503`, `504` with exponential backoff.
- Do not retry other `4xx` answers without changing the request.
- For create calls, send an `external_ref` so a retry after a timeout cannot create a duplicate. See
  [Retries and idempotency](/concepts/idempotency/).