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

# Rate limits

> Per-token buckets, what a request costs, and how to back off.

Limits are per token, per minute, in three buckets:

| Bucket      | Limit     | Applies to                       |
| ----------- | --------- | -------------------------------- |
| `read`      | 600 / min | `GET` endpoints                  |
| `write`     | 120 / min | `POST`, `PATCH`, `DELETE`        |
| `messaging` | 20 / min  | Endpoints that send email or SMS |

Messaging is separate and far tighter because those calls spend real money and
land in somebody's inbox: a runaway loop against `/reminders/:id/send` is a
different kind of incident from a runaway loop against `/members`.

## Every response carries your budget

```
RateLimit-Limit: 600
RateLimit-Remaining: 435
RateLimit-Reset: 12
```

`RateLimit-Reset` is seconds until the window rolls over. A `429` adds
`Retry-After`, also in seconds:

```json theme={"system"}
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit of 600 read requests per minute exceeded.",
    "requestId": "bE6hSMLt6FUnWN2kuCbl4"
  }
}
```

## Some requests cost more than one

A request is priced by the work it asks for, not by being one HTTP call. Reading
a chapter's roster costs `ceil(limit / 25)` units, so a page of 100 members
costs 4:

```bash theme={"system"}
# 150 of these exhaust the read bucket, not 600
curl "https://api.dueflow.co/v1/chapters/$CHAPTER/members?limit=100" \
  -H "Authorization: Bearer $DUEFLOW_TOKEN"
```

That's deliberate: a national syncing 200 chapters at 100 rows a page is 800
units of database work whether or not it looks like 200 requests. Pricing it
this way means the limiter stops a runaway sync, rather than the database.

Requesting smaller pages does *not* save you anything — 4 pages of 25 costs the
same 4 units as 1 page of 100 — so prefer large pages and fewer round trips.

<Note>
  A malformed request still costs a unit. Otherwise sending garbage would be a
  free way to hammer the API.
</Note>

## Backing off

On a `429`, sleep for `Retry-After` seconds and retry — with jitter if you run
more than one worker, or they'll all wake up together and collide again.

```python theme={"system"}
import random, time, requests

def call(url, token, attempt=0):
    r = requests.get(url, headers={"Authorization": f"Bearer {token}"})
    if r.status_code == 429 and attempt < 5:
        time.sleep(int(r.headers.get("Retry-After", 5)) + random.random())
        return call(url, token, attempt + 1)
    r.raise_for_status()
    return r.json()
```

Better still, watch `RateLimit-Remaining` and slow down before you hit zero.

## Seeing your usage

Each token's card in the dashboard shows its requests over the last 14 days, its
busiest endpoint and its failure rate, broken down per day. Usage is counted by
route *template*, so a 200-chapter sync appears as one endpoint with 200
requests rather than 200 separate paths.

That view is the fastest way to answer "what is this integration actually
doing?" before an organization's admins decide whether to keep it.
