# Rate limits

> Each REST key has its own request buckets. The headers on each response show the limit, what is left and when it refills.

URL: https://docs.xulo.io/api/rate-limits

## Buckets

Requests are counted in buckets. A bucket holds a number of requests and refills at a steady rate, so a short burst is fine as long as the average stays under the rate. Each REST key has its own buckets. Two keys on one server do not share them.

| Bucket | Counts | Refills | Holds |
| --- | --- | --- | --- |
| `send` | Each `POST` that does not name a segment or a tag, such as a list of ids | 50 a second | 100 |
| `broadcast` | Each `POST` to `included_segments` or `tag` | 30 a minute | 10 |
| `read` | Each `GET /api/v1/notifications/{id}` | 20 a second | 40 |
| `audience` | Sends to the same segment or tag on one app, from any of its keys | 10 a minute | 10 |
| `ip` | Every call with a valid key from one IP address, across keys | 6,000 a minute | 6,000 |
| `anonymous` | Calls from one IP address that return `401` or `403` | 120 a minute | 120 |

A call draws from `ip` first, then from its key's `send`, `broadcast` or `read` bucket, then, for a segment or tag send, from `audience`. A call that one bucket refuses does not draw from the buckets after it. A call with a missing, wrong or IP-locked key draws only from `anonymous`, never from a key's buckets.

## Headers

Each response carries the bucket closest to running out, and the list of buckets the call drew from:

```http
RateLimit-Limit: 100
RateLimit-Remaining: 99
RateLimit-Reset: 1
RateLimit-Policy: 6000;w=60;burst=6000;name="ip", 50;w=1;burst=100;name="send"
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1792310401
```

| Header | Meaning |
| --- | --- |
| `RateLimit-Limit` | How many requests that bucket holds. |
| `RateLimit-Remaining` | Requests left in that bucket. |
| `RateLimit-Reset` | Seconds until that bucket is full again. |
| `RateLimit-Policy` | Each bucket the call drew from: requests per window, `w` the window in seconds, `burst` how many it holds, and `name`. |
| `X-RateLimit-Limit` | The same as `RateLimit-Limit`. |
| `X-RateLimit-Remaining` | The same as `RateLimit-Remaining`. |
| `X-RateLimit-Reset` | A Unix timestamp in seconds: when that bucket is full again. On a `429` it is the moment `Retry-After` runs out, that is now plus `Retry-After`. |

## Over the limit

Over the limit, the call returns `429` with a `Retry-After` header, in seconds, and the headers above for the bucket that refused it:

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

Wait for `Retry-After`, then retry. Send an [Idempotency-Key](https://docs.xulo.io/api/idempotency) so that a retried send cannot go out twice. A replayed call counts against the buckets like any other call.

## Size limits

| Limit | Value | Over the limit |
| --- | --- | --- |
| Ids per list | 500 | A list of more than 500 Subscription IDs or External IDs returns 422. |
| Devices per send | 1,000,000 | A segment or tag send reaches every matching subscribed install, up to 1,000,000. A larger audience returns 422 with "This send reaches 1,200,000 devices. One send reaches at most 1,000,000." |
| Throttle | 1 to 100,000 a minute | 422 with "throttle_rate_per_minute is a whole number from 1 to 100000." |
| Message | 2,000 characters | 422 |
| Title | 120 characters | 422 |
| Apple payload | 4KB, checked with the longest language | 422 with "This notification is larger than 4KB." It is not cut short. |
| Link | 500 characters | 422 |
| Buttons | 1 | 422 with "Send one button." |
| Data | Keys up to 40 characters, values up to 2,000 | Longer keys and values are cut to that length. |
