# Errors

> Status codes and error bodies of the REST API, and the validation messages you can get back.

URL: https://docs.xulo.io/api/errors

Errors are JSON with a `message`. Validation errors add an `errors` object keyed by field.

## Status codes

| Status | When |
| --- | --- |
| `200` | The call was handled. Check `status` and `sent` in the body. See [Send notification](https://docs.xulo.io/api/send-notification#response). |
| `202` | A segment or tag send was accepted with `status: "queued"`. Xulo sends it in the background. See [Send notification](https://docs.xulo.io/api/send-notification#a-segment-or-a-tag). |
| `401` | No key, a wrong key, a revoked key, a key in the query string, or an organization `perk_` key. |
| `402` | Delivery is paused because the app has an invoice past its due date. Nothing is recorded or sent. |
| `403` | The key's IP lock refused the calling address. |
| `404` | `app_id` or `bundle_id` is not the key's app, or the send id does not exist on this app. |
| `409` | A call with the same idempotency key is still being handled. The response has `Retry-After: 1`. See [Idempotency](https://docs.xulo.io/api/idempotency). |
| `422` | The body did not pass validation. |
| `429` | Too many requests. See [Rate limits](https://docs.xulo.io/api/rate-limits). |

A replayed call returns the status of the first call, with the header `Idempotent-Replay: true`.

## Bodies

```json
{ "message": "Invalid REST API key." }
```

```json
{ "message": "This REST key is locked to other IP addresses." }
```

```json
{ "message": "Delivery is paused until the invoice for this app is paid." }
```

```json
{ "message": "A request with this idempotency key is still in progress. Retry in a moment." }
```

```json
{ "message": "Too many requests. Retry after 1 second." }
```

A `404` has an empty message:

```json
{ "message": "" }
```

A `422` names the field:

```json
{
  "message": "The push needs a message.",
  "errors": {
    "contents": ["The push needs a message."]
  }
}
```

## Validation messages

| Field | Message |
| --- | --- |
| `app_id` | Name the app with its app id. |
| `contents` | The push needs a message. |
| `contents` | Keep the message to 2000 characters. |
| `contents` | This notification is larger than 4KB. |
| `contents`, `headings` | Send languages as an object, such as {"en": "Hello"}. |
| `contents`, `headings` | Use a language code such as en or tr. |
| `headings` | Keep the title to 120 characters. |
| `included_segments` | Name one segment, or a tag, or the subscriptions, or the external ids. Do not mix them. |
| `included_segments` | Send to one segment. |
| `included_segments` | That segment is not on this app. |
| `included_segments`, `tag` | This send reaches 1,200,000 devices. One send reaches at most 1,000,000. |
| `included_segments`, `include_subscription_ids`, `include_external_ids` | Send a list. |
| `tag` | tag is an object with key and value. |
| `tag` | tag needs a key and a value. |
| `tag` | include_missing is true or false. |
| `include_subscription_ids` | Send to 500 subscriptions or fewer. |
| `include_subscription_ids` | A subscription id is a UUID. |
| `include_external_ids` | Send to 500 external ids or fewer. |
| `include_external_ids` | An external id is too long. |
| `send_after` | send_after needs a time. |
| `send_after` | That time has already passed. |
| `delayed_option` | delayed_option is timezone, or leave it out. |
| `throttle_rate_per_minute` | throttle_rate_per_minute is a whole number from 1 to 100000. |
| `url` | Use an https address or an app address such as tips://match/1. |
| `url` | Send one address. url and app_url differ. |
| `url` | Keep the address to 500 characters. |
| `image` | The image needs an https address. |
| `data` | data is a dictionary of strings. |
| `data` | data keys are strings. |
| `data` | data values are strings. |
| `buttons` | buttons is a list of one button. |
| `buttons` | Send one button. |
| `buttons` | A button has an id and a text. |
| `Idempotency-Key`, `idempotency_key` | Use a short idempotency key: letters, numbers, dot, or dash. |
| `idempotency_key` | idempotency_key is a string. |
| `idempotency_key` | The Idempotency-Key header and idempotency_key differ. Send one key. |
| `idempotency_key` | This idempotency key was already used with a different request. |

The `image` message also covers `big_picture` and `ios_attachments`. The device cap message names the field you sent, `tag` or `included_segments`, and the real device count.

## Retrying

- Do not retry `401`, `403`, `404` or `422` without changing the request.
- After a `402`, nothing goes out until the invoice is paid.
- After a `409`, wait for `Retry-After` and retry with the same idempotency key.
- After a `429`, wait for `Retry-After`.
- After a timeout or a `5xx`, retry with the same [Idempotency-Key](https://docs.xulo.io/api/idempotency).
