Developers
Per-minute burst, monthly quota, the 429 response and how to retry well.
Two per-minute ceilings always apply, different for reads and writes: one per token and one per account, shared by all its tokens.
| Type | Per token | Per account | What counts |
|---|---|---|---|
| Read | 1,200 per minute | 2,400 per minute | Routes whose permission is a read (read). |
| Write | 300 per minute | 600 per minute | Routes whose permission is create, update, delete or a critical action. |
The type is decided by the permission the route requires, not by the HTTP verb. When you create or edit a token you can give it its own ceiling under Limits per minute; the account ceiling still applies. BMC can set an account ceiling other than the default.
The counter is a fixed window per calendar minute: it resets at the start of each minute. A rejected request does not use up quota.
On top of the per-minute burst, each account has a monthly call quota, 50,000 by default. All tokens of the account share it: creating more tokens does not add quota. BMC can raise it for a given account. When you create a token you can give it an Own monthly quota within the account's. The quota resets on the 1st of each month (UTC). The month's consumption is shown in the client area, under Developers › API & MCP › API usage.
Limits are hierarchical. A request passes only if it is within all the levels that apply:
When a limit is reached, the API answers 429 with these headers and a body that says which level and window were hit:
HTTP/1.1 429 Too Many Requests
Retry-After: 23
X-RateLimit-Limit: 1200
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1791195600
X-RateLimit-Window: minute
Content-Type: application/problem+json
{
"type": "https://bm.consulting/en/developers/errors#rate_limited",
"title": "Too Many Requests",
"status": 429,
"detail": "Per-minute limit reached (1200, token). Retry in 23 seconds.",
"code": "rate_limited",
"scope": "token",
"window": "minute",
"error": "Too Many Requests"
} | Header | Meaning |
|---|---|
Retry-After | Seconds until the next window opens. Always honour it. |
X-RateLimit-Limit | The cap of the window that was hit. |
X-RateLimit-Remaining | Requests left in that window. |
X-RateLimit-Reset | Unix time (seconds) when it resets. |
X-RateLimit-Window | minute (burst) or month (monthly quota). |
scope is token, acct (account) or user.
429 and 5xx, double the wait on each attempt and cap the number of attempts.limit up to the route's maximum) to spend fewer requests (Pagination).async function bmcFetch(url: string, init: RequestInit = {}, attempts = 5): Promise<Response> {
for (let i = 0; i < attempts; i++) {
const res = await fetch(url, {
...init,
headers: { ...init.headers, Authorization: `Bearer ${process.env.BMC_TOKEN}` },
});
if (res.status !== 429 && res.status < 500) return res;
const wait = Number(res.headers.get("Retry-After")) || 2 ** i;
await new Promise((r) => setTimeout(r, wait * 1000));
}
throw new Error("Too many retries");
} import os
import time
import requests
def bmc_get(url, params=None, attempts=5):
for i in range(attempts):
res = requests.get(
url,
params=params,
headers={"Authorization": f"Bearer {os.environ['BMC_TOKEN']}"},
timeout=30,
)
if res.status_code != 429 and res.status_code < 500:
return res
time.sleep(int(res.headers.get("Retry-After", 2 ** i)))
raise RuntimeError("Too many retries")