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

# Rate limits

> 60 requests per minute, counted per IP address rather than per API key.

The API allows **60 requests per minute**.

<Warning>
  The limit is counted **per IP address, not per API key.** Every key calling from the same
  outbound IP shares one 60/minute budget. If several integrations run from one server — or
  from the same office network — they compete for the same allowance.
</Warning>

## Headers

Every response carries your current standing, so you rarely need to guess:

| Header                  | Meaning                                            |
| ----------------------- | -------------------------------------------------- |
| `X-RateLimit-Limit`     | Requests allowed per minute (`60`)                 |
| `X-RateLimit-Remaining` | Requests left in the current window                |
| `Retry-After`           | Seconds until the window resets. **Only on `429`** |

## When you exceed it

You get `429 Too Many Requests`:

```json theme={"dark"}
{ "message": "Too Many Attempts." }
```

<Warning>
  Send `Accept: application/json` on every request. The rate limiter sits in front of the
  API's JSON error handling, so a `429` to a client that didn't ask for JSON comes back as
  an **HTML error page** — which will blow up a naive `response.json()` in your retry
  path. Every other status returns JSON either way; `429` is the exception.
</Warning>

Wait the number of seconds in `Retry-After`, then retry. Retrying sooner just burns another
request and extends the problem.

```python theme={"dark"}
headers = {"x-api-key": "dmly_xxxxxxxxxxxx", "Accept": "application/json"}

r = requests.get(url, headers=headers)

if r.status_code == 429:
    time.sleep(int(r.headers.get("Retry-After", 60)))
    r = requests.get(url, headers=headers)
```

## Staying under the limit

<Steps>
  <Step title="Page in hundreds, not tens">
    `?per_page=100` fetches the same data in a quarter of the requests that the default 25
    would need. See [Pagination](/api-reference/pagination).
  </Step>

  <Step title="Use webhooks instead of polling">
    Polling for new messages every few seconds will exhaust the budget on its own.
    [Subscribe to events](/api-reference/webhooks) and let DMLY call you.
  </Step>

  <Step title="Watch the remaining header">
    Read `X-RateLimit-Remaining` and slow down as it approaches zero, rather than waiting to
    be refused.
  </Step>

  <Step title="Serialise bulk jobs">
    Importing thousands of contacts in parallel from one host will hit the limit. One
    worker, spacing requests, finishes sooner than ten workers fighting each other.
  </Step>
</Steps>

<Note>
  Because the limit is per IP, a `429` can be caused by something other than your own code —
  another integration behind the same NAT, for example. If a `429` looks inexplicable,
  check what else calls DMLY from that network.
</Note>
