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. 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, a404, a missing scope) all count, and so does a429. A request with no key or a bad key isn’t counted, and neither is/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:
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. and 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.
{
"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.
| 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:
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}` },
}),
);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.
- Read 100 at a time. A list with
limit=100costs one request where the default of 25 costs four. - 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.
- 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.
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. covers the pace, quiet hours and daily limits.
If you need more than 120 requests a minute for a good reason, write to and say what your program does.