For AI agents: a documentation index is available at /llms.txt. A markdown version of this page is available at /guide/api/rate-limits.md.

APIIntroduction

Rate limits

120 requests per minute and 5,000 per day per key, with headers telling you where you stand.

The Developer API enforces two limits at once. Exceeding either returns 429 Too Many Requests.

WindowLimit
Per minute120 requests
Per day5,000 requests

Both limits are counted per API key. Requests without an X-API-KEY header are counted per source IP instead, which matters when a misconfigured client starts failing authentication, because those attempts still consume quota.

#Response headers

Every response carries your current standing, so you can throttle before you get blocked rather than after:

less
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 43

When you are blocked, a third header tells you how long to wait:

makefile
Retry-After: 60

The value is in seconds. Honor it rather than guessing. Retrying early just burns the daily allowance.

#Staying under the limits

  1. Batch with filters, not loops

    One tasks/list call with a filters object beats fetching every project and looping. Most list endpoints filter server-side.

  2. Cache what does not change

    The user directory and project list change rarely. Cache them for minutes, not seconds. This alone usually removes most of the traffic.

  3. Back off on 429

    Sleep for Retry-After, then retry with exponential backoff and jitter so a fleet of workers does not resynchronize into a thundering herd.

  4. Use a separate key per integration

    Limits are per key, so a nightly bulk sync on its own key cannot starve your interactive integrations.

Generate fresh signing values on every retry

A retry must carry a new X-TIMESTAMP, a new X-NONCE and a recomputed X-SIGNATURE. Replaying the original headers is rejected as a reused nonce, and if the wait exceeded five minutes, as an expired timestamp too.

#A worked backoff

js
async function callWithRetry(path, payload, attempt = 0) {
  const response = await call(path, payload); // re-signs on every invocation

  if (response.status !== 429 || attempt >= 4) return response;

  const retryAfter = Number(response.headers.get('Retry-After') ?? 60);
  // Exponential growth plus jitter, so parallel workers don't resynchronize.
  const backoff = retryAfter * 1000 * 2 ** attempt + Math.random() * 1000;

  await new Promise((resolve) => setTimeout(resolve, backoff));
  return callWithRetry(path, payload, attempt + 1);
}

#MCP traffic is limited separately

The MCP server has its own budget: the same 120/minute and 5,000/day figures, but a separate bucket, because it authenticates with OAuth rather than a partner key. Tool calls from Claude, Cursor or Copilot do not eat into your REST allowance.

The MCP bucket is counted per IP

MCP clients do not always present a credential during the initialize handshake, so that limiter keys on the calling IP address rather than on a key or a user. Everyone connecting from the same office NAT or VPN egress shares one bucket. See MCP rate limits.

#Need more headroom?

The limits are configurable per deployment. If a legitimate integration needs more, contact your Orangescrum account manager with the expected request volume and pattern.