# Rate limits

> Per-key limits, the headers that report them, and how to back off.

Limits are counted per API key in a fixed window. They are generous enough that a well-behaved integration never sees one, and low enough that a runaway loop cannot take the service down for everyone else.

Every key gets **600 requests per 15-minute window**, counted per key rather than per account — two integrations on one account do not spend each other's allowance, and one running hot does not throttle the other.
| Enterprise | Agreed per contract | Agreed per contract |

## Headers

Every response carries the current state of your window, whether or not you are near it.

```http
HTTP/1.1 200 OK
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 597
X-RateLimit-Reset: 1786100460
```

These come back on **every** response, not only on a `429` — you cannot pace against a budget you are only told about once you have already overrun it.

When the window is spent the API returns `429` with `Retry-After` in seconds.

**Note: A 503 means the limiter itself is down**

If rate limiting is unavailable, this API answers `503` with `Retry-After: 5` rather than letting the request through. An unmetered public endpoint is worse than a brief outage, and your client is a program that can wait five seconds.

```json Response 429
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Too many requests. Retry in 12 seconds."
  }
}
```

## Backing off

Read `Retry-After` and wait exactly that long. Retrying sooner counts against the next window and pushes the recovery further out.

```ts
async function call(url: string, attempt = 0): Promise<Response> {
  const response = await fetch(url, { headers });
  if (response.status !== 429 || attempt >= 5) return response;

  const wait = Number(response.headers.get("Retry-After") ?? 1);
  const jitter = Math.random() * 250;

  await new Promise((resolve) => setTimeout(resolve, wait * 1000 + jitter));
  return call(url, attempt + 1);
}
```

**Tip: Stop polling**

Most `429`s come from polling for a meeting that is still processing. A [webhook](/docs/api/webhooks) tells you the moment it is ready, and costs no requests at all.
