# Handle errors

> The error format, the stable codes, and what to do depending on the family of the error.

Every error follows RFC 9457 (`application/problem+json`):

```json
{
    "type": "https://nessflow.com/en/developers/errors/project.locked",
    "title": "Read-only project: your plan no longer covers it. Nothing runs, everything stays readable.",
    "status": 403,
    "code": "project.locked"
}
```

Your code relies on `code`, which does not change; `title` is translated according to `Accept-Language` and may be reworded. `type` leads to the entry of the code catalogue.

## What to do, by family

- `401` (`unauthenticated`): missing, invalid or expired token. Do not retry, renew the token.
- `403`: authorisation refused. `token.missing_scope` (the token lacks the scope, see `required_scopes`), `api.write_not_in_plan` or `plan_limit.…` (the plan, see `upgrade_to`), `project.locked` (read-only project). Retrying will not change anything.
- `404` (`not_found`): the resource does not exist, or belongs to another team.
- `409`: conflict with the current state (`crawl.already_running`, `export.not_ready`…). Wait, then retry.
- `422` (`validation_failed`): `errors` says, field by field, what is wrong.
- `429` (`rate_limited`): wait `Retry-After` seconds.
- `5xx`: retry with an increasing delay; a billed write is retried with the same `Idempotency-Key`.

## An example

A billed write without an idempotency key is refused before anything runs:

```bash expect=400
curl -X POST "https://api.nessflow.com/v1/campaigns/$CAMPAIGN_ID/crawls" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN"
```

The response carries `code: idempotency.key_required` and `header: Idempotency-Key`.
