Skip to the page

API basics

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 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
HeaderWhat it says
RateLimit-LimitRequests the key may make in a minute.
RateLimit-RemainingRequests left in this minute.
RateLimit-ResetSeconds until the next minute starts and the count goes back to zero.
RateLimit-PolicyThe 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 and Python SDK 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.

Response429Over the limit
{
  "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

You gotIt meansDo next
RateLimit-Remaining near 0This key is close to its limit for the minute.Slow down, or wait RateLimit-Reset seconds.
429 rate_limitedThis key made more than 120 requests this minute.Wait Retry-After seconds, then send the same request again.
429 again and againA 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:

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}` },
    }),
);
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
  • Read 100 at a time. A list with limit=100 costs one request where the default of 25 costs four. 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
  • 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

Requests and texts

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

The rate limitYour sending pace
What it countsRequests to the APITexts your Mac sends
How much120 a minute per keyOne text every 20 seconds by default, which you can change under Settings → Sending rules
What happens over it429 rate_limited, and nothing happensThe text is queued with a pacing hold and goes when its turn comes
Why it’s thereTo keep BlueBot fast for everyoneTo 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 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 and say what your program does.