---
title: Rate limits
description: The per-route limits, and what to do when you hit one.
---

Limits are per API key, per minute, and vary by route.

| Route | Per minute |
|---|---|
| `POST /v1/subscribe` | 30 |
| `POST /v1/contacts` | 120 |
| `GET` endpoints | 120 |
| `POST`/`DELETE` on list members | 60 |
| `POST`/`DELETE` on suppressions | 120 |
| `GET /v1/reports/*` | 30 |

Subscribe is the tightest because it is the one route a browser can
reach, and an unlimited public endpoint that sends email is an
invitation to use your form to mail a stranger repeatedly.

[Reports](/api/reports) are lower than other `GET` routes because each
one adds up figures across your whole workspace rather than fetching a
single record. Send back the `ETag` as `If-None-Match` and an unchanged
report answers `304 Not Modified`, which is cheap enough that 30 a
minute comfortably covers a dashboard refreshing every few seconds.

Requests count against the limit whether they return `200` or `304`.

## When you hit one

```json
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limited",
    "message": "Too many requests. Retry after 24 seconds."
  }
}
```

`429`, with a `Retry-After` header in seconds. Wait that long, then
retry.

## Backing off properly

```js
async function call(url, options, attempt = 0) {
  const res = await fetch(url, options);
  if (res.status !== 429 || attempt >= 5) return res;

  const after = Number(res.headers.get('Retry-After') ?? 1);
  // Jitter, so a fleet of workers that hit the limit together does
  // not come back together and hit it again.
  const wait = after * 1000 + Math.random() * 1000;
  await new Promise((r) => setTimeout(r, wait));
  return call(url, options, attempt + 1);
}
```

## Importing a large audience

For a bulk import, prefer the dashboard's CSV import. It is built for
volume and gives you a report of what merged, what was skipped and
why.

If you must do it over the API, 120 contacts a minute is about two a
second. Space the calls rather than firing them in parallel and
retrying the failures, which just converts one limit into a slower
version of the same limit.

## Confirmation emails have their own limit

Separately from the route limit, we will not send a second
confirmation email to the same address within a short window, however
many times the form is submitted.

Your call still succeeds and still returns `confirmation_sent`. We
simply do not mail them again. A signup box that emails on every
submission is a way to mail a stranger repeatedly by pressing a
button.