# Errors

An error answers with an HTTP status and a JSON body that names a stable code:

```json
{"error": "invalid", "details": {"name": ["has already been taken"]}}
```

`error` is always there; read it, not the wording. `details` appears only for some codes. Its messages are written for
people: show them to the admin, do not parse them.

## Every code

| Status | `error` | Meaning | What to do |
|---|---|---|---|
| `400` | `bad_request` | The body is not valid JSON, or a required parameter is missing (then `details.missing` names it). | Fix the request. Send `Content-Type: application/json` with a valid, flat JSON body. |
| `401` | `invalid_token` | The key is missing, mistyped, expired or revoked, or its admin is no longer an owner or admin of the community. The answer is the same for every cause, with the header `WWW-Authenticate: Bearer realm="Zen Social", error="invalid_token"`. | Check the `Authorization: Bearer …` header. If it is right, stop and ask the admin for a new key. See [Keys and access](keys.md). |
| `403` | `forbidden` | The key works, but its admin could not do this in the app, for example change a circle they are not an owner or admin of. | Ask the admin to join that circle in the app (as its owner or admin, if it is to be changed). Do not look for a way around it. |
| `404` | `not_found` | It does not exist, or it belongs to another community. | Check the id. A key never reaches outside its community. |
| `409` | `record_deleted` | A create's `external_id` belongs to something that was imported and has been deleted since. It is never brought back. | Skip it and report it to the admin. Do not delete or retry. |
| `409` | `changed_since_import` | A create's `external_id` belongs to something that someone changed in the app after your last write. It was not overwritten. | Skip it and report it to the admin. Do not delete or retry. |
| `409` | `conflict` | Two requests made the same thing at the same moment, for example two creates with one `external_id`. | Send the request again, once, after the other one has answered. |
| `422` | `invalid` | A value is not allowed. `details` names each field and why, for example `{"accessibility": ["is not included in the list"]}` or `{"photo": ["could not be fetched as an image"]}`. Nothing was saved. | Fix the value. When it is a choice for the admin (a name that is taken), ask them. |
| `422` | `confirmation_required` | A circle delete without `confirm`, or with a `confirm` that is not the circle's slug. Nothing was deleted. | Only after the admin's clear yes: send the circle's slug as `confirm`. See [Circles](circles.md). |
| `422` | `multiple_owners` | A circle delete for a circle that does not have exactly one owner. Nothing was deleted. | Tell the admin. The circle's owners sort it out in the app first. |
| `429` | (see below) | Too many requests. | Wait the seconds in `Retry-After`, then go on, one request at a time. |

## Too many requests: 429

At most 300 requests a minute per key, and 600 a minute from one network address. Above that the answer has the header
`Retry-After: 60` and a body without a stable code:

```json
{"error":"Too many requests. Please try again later."}
```

Wait that many seconds before the next request.

## An address that does not exist

A path the API does not have, such as `/posts` in this version, answers `404` with a web page, not JSON. Check the
path against these pages.

## Server errors: 500

Something went wrong on Zen Social's side. Stop, tell the admin what you were doing, and try again later. If a create
fails like this, sending it again with the same `external_id` is safe.
