> ## Documentation Index
> Fetch the complete documentation index at: https://help.retainful.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate limits

> Per-IP request limits across three windows, the headers that report them, and how to back off correctly.

API requests are rate-limited. Three windows apply simultaneously, and the same limits apply to every endpoint — reads and writes alike.

## The limits

| Window     | Limit          |
| ---------- | -------------- |
| Per second | 10 requests    |
| Per minute | 100 requests   |
| Per hour   | 1,000 requests |

Exceeding any one of them returns `429 Too Many Requests`.

<Note>
  Limits are counted **per client IP address**, not per API key. If several of your systems share an outbound IP, they share a single budget. Conversely, spreading traffic across hosts gives each its own.
</Note>

## Rate limit headers

Every response reports your standing in all three windows. Header names carry the window as a suffix:

| Header                                    | Meaning                                                                       |
| ----------------------------------------- | ----------------------------------------------------------------------------- |
| `X-RateLimit-Limit-short`                 | Requests allowed in the 1-second window.                                      |
| `X-RateLimit-Remaining-short`             | Requests left in it.                                                          |
| `X-RateLimit-Reset-short`                 | When it resets.                                                               |
| `X-RateLimit-Limit-medium`                | The same three, for the 1-minute window.                                      |
| `X-RateLimit-Limit-long`                  | The same three, for the 1-hour window.                                        |
| `Retry-After-short` / `-medium` / `-long` | On a `429` — how long to wait. The suffix tells you which window you tripped. |

<Warning>
  The suffix matters. There is no unsuffixed `X-RateLimit-Remaining` or `Retry-After` — reading those returns nothing. Parse the suffixed names.
</Warning>

## When you exceed the limit

You receive `429` with a body explaining the failure. Wait for the window named in the `Retry-After-*` header, then resume.

```python Exponential backoff theme={null}
import time, requests

RETRY_AFTER_HEADERS = ("Retry-After-short", "Retry-After-medium", "Retry-After-long")

def post_with_backoff(url, json, headers, max_attempts=5):
    for attempt in range(max_attempts):
        resp = requests.post(url, json=json, headers=headers)
        if resp.status_code != 429:
            return resp
        wait = next(
            (float(resp.headers[h]) for h in RETRY_AFTER_HEADERS if h in resp.headers),
            2 ** attempt,
        )
        time.sleep(wait)
    raise RuntimeError("rate limited after retries")
```

## Designing within the limits

* **Don't sync by polling.** Instead of re-fetching all contacts hourly, push changes as they happen with [events](/developers/api/events).
* **Mind the hourly ceiling.** 1,000 requests per hour is the binding constraint for backfills — averaging under one call every 3.6 seconds. A job that respects the per-second limit can still exhaust the hour.
* **Batch your backfills.** For large one-time contact imports, the dashboard's [CSV import](/audience/import-contacts) avoids the API entirely and is the better tool.
* **Queue on your side.** Put API calls on a job queue with retry and backoff rather than calling inline from request handlers.
* **Watch `X-RateLimit-Remaining-long`** and slow down *before* it reaches zero — it beats reacting to `429`s.
