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

Default60 requests per minute, per token
Configurable1 to 10,000 requests per minute
WindowA fixed 60 seconds, starting with the token's first request in the window
CountsEvery authenticated REST call and every MCP request

The limit that applies to a token is the first of these that's set:

  1. The token's own limit — set when the token is created in Settings → API Suite.
  2. The organisation's default — set by an API Suite administrator in Settings → API Suite, and applied to every token without its own limit.
  3. 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

HeaderMeaning
X-RateLimit-LimitThe token's requests per minute.
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetUnix 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-AfterOn 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:

OperationLimit
POST /support/feedback10 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=100 on lists — one page of 100 costs the same as one page of 25.
  • Ask for less with fields, and embed related records with include instead 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.