Rate limits and retries
The two buckets every request clears, the per-tier ceilings, the rate-limit headers, the 429 and how to back off.
The ceilings
An authenticated request clears two buckets: the key's own per-minute allowance, and the workspace's plan allowance, which every key in the workspace shares (where the plan's limits are enforced; otherwise only the key's applies). Exceeding either returns 429 Too Many Requests with the rate_limited error code.
The shared bucket is the one that surprises people: minting more keys does not buy more throughput. The per-key bucket exists to stop one runaway credential starving the others, not to multiply your ceiling. error.details.scope on a 429 tells you which bucket refused the call, "key" or "organization", so you know whether to slow one worker down or the whole fleet.
The workspace allowance is set by your plan. A key's own allowance is configurable (minimum 10) but is clamped to the plan ceiling at request time, so a downgrade applies immediately to keys minted before it:
| Plan | Requests / minute (whole workspace) |
|---|---|
| Starter | 60 |
| Growth | 120 |
| Business | 300 |
| Enterprise / Platform | 600 / 1 200 |
| API plan | 300 |
| Sandbox | 60 |
Read the headers rather than hard-coding a number: the value you see is the live ceiling for the call you just made.
Limit headers
Successful and rate-limited responses both carry the current window state, so you can pace yourself before you ever hit a 429. When both buckets pass, the headers report whichever has less headroom left, so pacing against them always obeys the binding ceiling:
| Header | Meaning |
|---|---|
x-ratelimit-limit | The maximum number of requests allowed in the window. |
x-ratelimit-remaining | Requests left in the current window. |
x-ratelimit-reset | When the window resets, as an ISO-8601 UTC timestamp, not a unix epoch. Parse it as a date. |
A 429 from /api/v1 also carries retry-after: the whole number of seconds to wait before retrying. It names the same moment as x-ratelimit-reset (also repeated as error.details.reset_at), so a client that waits on retry-after by default retries at the right time.
The 429 response
HTTP/1.1 429 Too Many Requests
x-ratelimit-limit: 120
x-ratelimit-remaining: 0
x-ratelimit-reset: 2026-08-19T10:31:00.000Z
retry-after: 37
{
"ok": false,
"error": {
"code": "rate_limited",
"message": "API rate limit exceeded for this workspace. Your plan's per-minute API limit is shared by every key in the workspace, so minting another key does not raise it.",
"details": {
"limit": 120,
"reset_at": "2026-08-19T10:31:00.000Z",
"scope": "organization"
}
},
"request_id": "req_abc123"
}The standard next_steps array is elided above for brevity; every error carries one; see Error codes for the full envelope.
A 429 is always safe to retry: you were rejected before any work happened, so there is nothing to duplicate. That is not true of the other retryable statuses; see the SDK's idempotency rules.
The SDK retries for you
The SDK retries a 429 after the retry-after the API sends (up to 30 seconds), and a 500, 502, 503 or 504 with exponential backoff and jitter (roughly 0.3s, then 0.6s). It never retries a POST the route does not deduplicate: that throws retry_unsafe, since the first attempt may have succeeded. It also stops at once when the error says a person must act (a 503 channel_unavailable, say). It retries twice by default; tune it with maxRetries (set 0 to disable):
const bt = new Blueticked({
apiKey: process.env.BLUETICKED_API_KEY!,
maxRetries: 4, // default is 2; set 0 to handle retries yourself
});If retries are exhausted, the SDK throws a typed BluetickedError with code: "rate_limited" and status: 429; see Error codes.
Backoff guidance (raw HTTP)
- On a
429, sleep untilx-ratelimit-reset, or back off exponentially (for example 0.3s, 0.6s, 1.2s) with a little random jitter to avoid thundering-herd retries. - Use
x-ratelimit-remainingto slow down before you are blocked: pause when it approaches zero rather than sending until you are rejected. - Branch on
error.details.scope. A"key"refusal means this worker is too fast; an"organization"refusal means the whole workspace is at its plan ceiling and adding workers or keys will make it worse. - Spread bulk work over time. For large fan-outs, prefer a campaign: it queues and paces sends server-side instead of you looping over
messages.send. - Cap total retries so a persistently throttled caller fails loudly rather than retrying forever.