Public API

Rate Limits

The request limits per token, per organization and per address, the RateLimit and Retry-After headers, the failed-authentication lockout, and how to back off.

Rate limits keep the API responsive for every organization and stop a runaway script from affecting your colleagues' integrations. They are applied in layers, and a request must fit within every layer.

The limits

LayerLimitApplies to
Per token, burst20 requests per secondEach token
Per token, sustained120 requests per minuteEach token
Per organization600 requests per minuteAll of an organization's tokens together
Per address600 requests per minuteEvery request from one IP address, before the token is checked, including the documentation endpoints. IPv6 addresses are counted per /64 network.
Network edge20 requests per second per IP address, with a short burst allowanceApplied before the request reaches the API

The windows are sliding: a limit of 120 per minute means at most 120 requests in any 60-second span, not 120 per calendar minute. A request that is refused does not count against your quota.

Because the organization limit is shared, several busy integrations in one organization can reach it together even when each stays under its own token limit. Spread heavy jobs out, or schedule them at different times.

Most requests count as one. Downloading a document file (GET /documents/{id}/content) counts as 10 against the token and organization limits.

Rate limit headers

Authenticated responses report the window you are closest to exhausting:

HeaderMeaning
RateLimit-LimitThe request limit of that window
RateLimit-RemainingRequests left in that window
RateLimit-ResetSeconds until a slot in that window frees up
Retry-AfterSent with 429 only: seconds to wait before retrying
HTTP/1.1 429 Too Many Requests
RateLimit-Limit: 120
RateLimit-Remaining: 0
RateLimit-Reset: 14
Retry-After: 14
Content-Type: application/json; charset=utf-8

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Too many requests. Retry after the time given in Retry-After."
  },
  "meta": { "request_id": "..." }
}

Failed-authentication lockout

To stop token guessing, an IP address (for IPv6, a /64 network) that sends more than 20 requests with an invalid token within 10 minutes is blocked for 15 minutes. During the block, any request from that address with a missing, malformed or invalid token receives 429 RATE_LIMITED with Retry-After set to the seconds remaining. Requests with a valid token are never blocked by it, so an integration platform, office network or CI service that shares one outbound address with someone else's misconfigured script keeps working. Only missing, malformed, unknown, revoked and expired tokens count towards the block. A real token refused for another reason, for example because its owner was deactivated or the API is switched off, does not.

The usual cause is a script that keeps running with a revoked or expired token. Stop on the first 401 AUTH_INVALID rather than retrying it.

Handling limits well

  1. Honour Retry-After. On a 429, wait at least that many seconds before the next request.
  2. Pace yourself with RateLimit-Remaining. When it gets low, slow down rather than waiting for a 429.
  3. Back off exponentially on repeated failures, with random jitter, so many workers do not retry at the same instant. For example, wait 1, 2, 4, 8 seconds and so on, up to a minute, adding a random fraction each time.
  4. Use the largest page size (limit=100) for bulk reads, so you make fewer requests.
  5. Do not poll more often than you need. For changes you need to hear about quickly, use webhooks instead of polling.
  6. Treat any 429 the same way. A 429 from the network edge may not include the JSON body or the RateLimit headers; if there is no Retry-After, back off exponentially.
async function callApi(url, init, attempt = 0) {
  const res = await fetch(url, init)
  if (res.status !== 429 && res.status < 500) return res
  if (attempt >= 6) return res
  const retryAfter = Number(res.headers.get('retry-after'))
  const backoff = Math.min(60, 2 ** attempt) + Math.random()
  const waitSeconds = Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter : backoff
  await new Promise((resolve) => setTimeout(resolve, waitSeconds * 1000))
  return callApi(url, init, attempt + 1)
}

Retry a write only with an idempotency key, so a request that did succeed is not applied twice.