Rate limits
Every token has its own budget of requests per minute. You can see where you stand on every response, and when you run out you're told exactly how long to wait.
The limit
| Default | 60 requests per minute, per token |
| Configurable | 1 to 10,000 requests per minute |
| Window | A fixed 60 seconds, starting with the token's first request in the window |
| Counts | Every authenticated REST call and every MCP request |
The limit that applies to a token is the first of these that's set:
- The token's own limit — set when the token is created in Settings → API Suite.
- The organisation's default — set by an API Suite administrator in Settings → API Suite, and applied to every token without its own limit.
- 60 per minute.
If an integration needs more, ask an administrator to raise the token's limit rather than spreading traffic across extra tokens — one token per integration keeps the audit log readable.
What counts
- Each token has its own budget. Two integrations with two tokens never slow each other down. A personal token's budget is tracked against the token and its owner together.
- REST and MCP share it. An AI assistant connected with a token draws from the same budget as REST calls made with that token — and each MCP message, including
tools/list, counts as one request. - Requests rejected before the limiter don't count. An unknown, expired or revoked token, an organisation with the API switched off, or a blocked IP address is turned away first, so it never uses up budget. Requests rejected afterwards — a missing scope, a validation error — do count.
Headers on every response
| Header | Meaning |
|---|---|
X-RateLimit-Limit | The token's requests per minute. |
X-RateLimit-Remaining | Requests left in the current window. |
X-RateLimit-Reset | Unix time (seconds) by which the window will have reset. On a success it's an upper bound — now plus 60 seconds; on a 429 it's exact. |
Retry-After | On a 429 only: seconds until you can call again. |
HTTP/1.1 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 41
X-RateLimit-Reset: 1781258460
X-Request-Id: 9a9d1c5e-4f2b-4c8e-9a7d-2f1e6b3c4d5a
When you hit the limit
You get a 429 with the standard error envelope and error.code set to rate_limited:
HTTP/1.1 429 Too Many Requests
Retry-After: 17
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1781258417
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded.",
"request_id": "0c5e7a3b-2f4d-4a1e-9b8c-6d5e4f3a2b1c",
"details": []
}
}
Nothing was done, so it's always safe to retry — wait Retry-After seconds first.
Tighter limits on some operations
A few operations carry their own, lower limit on top of the token's budget, because each call reaches a person or an outside service:
| Operation | Limit |
|---|---|
POST /support/feedback | 10 per minute, per user |
On those operations the X-RateLimit-* headers describe the operation's own limit, and its 429 says "Too many requests to this operation." Because the count is per user, it's shared across all of that user's tokens.
The OAuth token endpoint
POST /oauth/token is called before you have a token, so it's limited by IP address instead: 20 requests per minute per IP. Over that you get a 429 in the OAuth error format with Retry-After:
{
"error": "slow_down",
"error_description": "Too many token requests. Try again shortly."
}
Access tokens last an hour — cache them and you'll never come close.
Staying within the limit
/** fetch() that waits out 429s and slows down as the budget runs low. */
export async function helmFetch(url: string, init: RequestInit = {}, attempt = 0): Promise<Response> {
const res = await fetch(url, {
...init,
headers: { Authorization: `Bearer ${process.env.HELM_API_KEY}`, ...init.headers },
})
if (res.status === 429 && attempt < 5) {
const wait = Number(res.headers.get('retry-after') ?? 1)
await sleep(wait _ 1000 + Math.random() _ 250) // jitter so workers don't stampede
return helmFetch(url, init, attempt + 1)
}
// Spread the rest of the window's budget evenly instead of bursting.
const remaining = Number(res.headers.get('x-ratelimit-remaining'))
const reset = Number(res.headers.get('x-ratelimit-reset'))
if (remaining <= 5 && reset) {
await sleep(Math.max(0, reset * 1000 - Date.now()) / Math.max(remaining, 1))
}
return res
}
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))Ways to need fewer requests:
- Use
limit=100on lists — one page of 100 costs the same as one page of 25. - Ask for less with
fields, and embed related records withincludeinstead of fetching them one by one. See Requests. - Use bulk operations (
…/bulk/…) for many records at once; big batches run as a background job you poll. - Subscribe to webhooks rather than polling for changes.
- Sync incrementally with
filter[updated_at][gte]instead of re-reading everything. - Keep concurrency low. A handful of parallel workers sharing one token will reach the limit together; a single queue that respects the headers is usually faster overall.