# Rate limits

> Each key may make 120 requests a minute. How the minute is counted, the headers that say where you stand, what a 429 looks like, and how to wait it out.

Each API key may make 120 requests a minute. The limit is there to keep one runaway loop from slowing BlueBot down for everyone, and most programs never come near it: a program that sends a text and listens for webhooks makes one request a text.

The limit counts requests, not texts. It doesn’t decide how fast your texts go out. That is your sending pace, and a send over the pace is held in the queue, never refused. [How fast texts go](#requests-and-texts) explains the difference.

## How the minute is counted

- **Per key.** Each key has its own count. Two programs on two keys each get 120 a minute; two programs sharing one key share it.
- **In fixed minutes.** The count starts again at the top of each minute on BlueBot’s clock, not 60 seconds after your first request. A burst at 10:41:58 and another at 10:42:01 fall in different minutes.
- **Every request with a working key counts.** Reads, sends, lookups, and requests BlueBot refuses (a `400`, a `404`, a missing scope) all count, and so does a `429`. A request with no key or a bad key isn’t counted, and neither is `GET /v1/status`, which needs no key.
- **Sign-ins have their own count.** An AI assistant that signs in to BlueBot’s MCP server with your account instead of a key gets 120 a minute of its own.

Webhook deliveries are BlueBot’s requests to you, so they never count.

## The headers

Every answer to a request with a working key says where the key stands:

```http
RateLimit-Limit: 120
RateLimit-Remaining: 87
RateLimit-Reset: 23
RateLimit-Policy: 120;w=60
```

| Header                | What it says                                                          |
| --------------------- | --------------------------------------------------------------------- |
| `RateLimit-Limit`     | Requests the key may make in a minute.                                |
| `RateLimit-Remaining` | Requests left in this minute.                                         |
| `RateLimit-Reset`     | Seconds until the next minute starts and the count goes back to zero. |
| `RateLimit-Policy`    | The rule itself: 120 requests in a window of 60 seconds.              |

These follow the IETF’s draft for rate limit headers, without the `X-` prefix some APIs use. A program that does a lot of reading can watch `RateLimit-Remaining` and slow down before it reaches zero, rather than waiting to be refused.

To read them from the SDKs, use `.withResponse()` in TypeScript or `with_raw_response` in Python. [TypeScript SDK](https://usebluebot.com/docs/sdks/typescript) and [Python SDK](https://usebluebot.com/docs/sdks/python) show both.

## Over the limit

The request after the 120th in a minute is refused with `429 rate_limited`. Nothing in it happens: a send isn’t queued and a change isn’t made.

The answer, `429`:

```json
{
  "error": {
    "code": "rate_limited",
    "message": "This key made more than 120 requests in a minute.",
    "hint": "Wait the number of seconds in `retry_after`, then try again.",
    "doc_url": "https://usebluebot.com/docs/errors/rate_limited",
    "retry_after": 23,
    "request_id": "req_1Hq2LxW9cVbN3mKpRtYs0a"
  }
}
```

The `Retry-After` header and `error.retry_after` give the same number: the seconds until the next minute starts. Wait that long and send the same request again, with the same `Idempotency-Key` if it’s a `POST`. A refused request never used the key, so the retry runs as new. [Idempotency](https://usebluebot.com/docs/concepts/idempotency)

| You got                                                     | It means                                                                      | Do next                                                       |
| ----------------------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------- |
| `RateLimit-Remaining` near `0`                              | This key is close to its limit for the minute.                                | Slow down, or wait `RateLimit-Reset` seconds.                 |
| `429 rate_limited`                                          | This key made more than 120 requests this minute.                             | Wait `Retry-After` seconds, then send the same request again. |
| `429` again and again                                       | A loop is asking faster than 120 a minute, or several programs share the key. | Find the loop. Give each program its own key.                 |
| A send answered `201` with `"hold": { "reason": "pacing" }` | Not a rate limit. The text is queued and waits its turn in your sending pace. | Nothing. It goes when its turn comes.                         |

## Waiting it out in your own code

The SDKs retry a `429` for you: they wait what `Retry-After` says and try again, up to two more times. When you call the API without them, do the same:

```ts title="with-retry.ts"
async function withRetry(request: () => Promise<Response>): Promise<Response> {
    for (let attempt = 0; ; attempt++) {
        const response = await request();
        if (response.status !== 429 || attempt === 3) return response;
        const wait = Number(response.headers.get('Retry-After') ?? '1');
        await new Promise((resolve) => setTimeout(resolve, wait * 1000));
    }
}

const response = await withRetry(() =>
    fetch('https://usebluebot.com/api/v1/messages?direction=in&limit=100', {
        headers: { Authorization: `Bearer ${process.env.BLUEBOT_API_KEY}` },
    }),
);
```

```python title="with_retry.py"
import os
import time
import httpx

def with_retry(send):
    for attempt in range(4):
        response = send()
        if response.status_code != 429 or attempt == 3:
            return response
        time.sleep(int(response.headers.get("Retry-After", "1")))

response = with_retry(lambda: httpx.get(
    "https://usebluebot.com/api/v1/messages",
    params={"direction": "in", "limit": 100},
    headers={"Authorization": f"Bearer {os.environ['BLUEBOT_API_KEY']}"},
))
```

Wait the number BlueBot gives rather than a guess. Retrying sooner gets another `429`, and each one counts as a request.

## Staying under it

- **Use webhooks instead of polling.** Asking for a text’s state every few seconds spends requests on answers that haven’t changed. A webhook tells you when it changes. [Webhooks](https://usebluebot.com/docs/webhooks)
- **Read 100 at a time.** A list with `limit=100` costs one request where the default of 25 costs four. [Pagination](https://usebluebot.com/docs/concepts/pagination)
- **Give each program its own key.** Your booking form and your nightly sync then can’t use up each other’s minute, and you can revoke one without the other. [API keys](https://usebluebot.com/docs/concepts/api-keys)
- **Look up a few people at a time.** A lookup can hold a request for up to 15 seconds while your Mac answers. Run a handful at once, not hundreds. [Lookups](https://usebluebot.com/docs/sending/lookups)

## Requests and texts

The rate limit and your sending pace are two different things:

|                      | The rate limit                          | Your sending pace                                                                             |
| -------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------- |
| What it counts       | Requests to the API                     | Texts your Mac sends                                                                          |
| How much             | 120 a minute per key                    | One text every 20 seconds by default, which you can change under **Settings → Sending rules** |
| What happens over it | `429 rate_limited`, and nothing happens | The text is queued with a `pacing` hold and goes when its turn comes                          |
| Why it’s there       | To keep BlueBot fast for everyone       | To keep your number looking like a person to Apple and the carriers                           |

So you can queue 100 texts in a minute with 100 requests, and at the default pace they go out one at a time over the next 33 minutes or so. [Sending rules](https://usebluebot.com/docs/concepts/sending-rules) covers the pace, quiet hours and daily limits.

If you need more than 120 requests a minute for a good reason, write to [support@usebluebot.com](mailto:support@usebluebot.com) and say what your program does.
