> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clarky.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate Limits

> Per-key request budgets and recommended client behavior

Every API key has its own request budget, applied independently per key. Limits are split between read and write operations to keep heavy reads from starving writes (and vice versa).

## Limits

| Operation  | Methods                   | Limit                           |
| ---------- | ------------------------- | ------------------------------- |
| **Reads**  | `GET`, `HEAD`             | **120 requests/minute** per key |
| **Writes** | `POST`, `PATCH`, `DELETE` | **60 requests/minute** per key  |

Limits are enforced on a rolling 60-second window. When you hit the limit, subsequent requests return `429 Too Many Requests` until the window resets.

## Rate limit headers

Every response — successful or rate-limited — includes headers that describe your current budget:

<ResponseField name="X-RateLimit-Limit" type="integer">
  Maximum requests allowed in the current window for this method class (read or write).
</ResponseField>

<ResponseField name="X-RateLimit-Remaining" type="integer">
  Requests remaining in the current window.
</ResponseField>

<ResponseField name="X-RateLimit-Reset" type="integer">
  Unix timestamp (seconds since epoch) when the current window resets.
</ResponseField>

Example response headers:

```http theme={null}
HTTP/1.1 200 OK
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117
X-RateLimit-Reset: 1735689600
```

## What a 429 looks like

When you exceed the limit, you'll get a `429` with a standard error envelope and a `Retry-After` header (in seconds):

```http theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1735689600
Content-Type: application/json

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Retry after 30 seconds."
  }
}
```

## Recommended client behavior

<Steps>
  <Step title="Respect Retry-After">
    On a `429`, wait at least the number of seconds in the `Retry-After` header before retrying. This is the simplest correct strategy.
  </Step>

  <Step title="Use exponential backoff with jitter">
    For longer-running jobs, layer exponential backoff on top of `Retry-After`. Add random jitter so a fleet of workers doesn't synchronize and thunder back together.
  </Step>

  <Step title="Throttle proactively">
    Watch `X-RateLimit-Remaining`. If it's getting close to zero, slow down before you hit the limit — it's much cheaper than recovering from a `429`.
  </Step>

  <Step title="Parallelize across keys for very large jobs">
    If you genuinely need throughput beyond the per-key limit, create multiple keys (one per worker) and shard your work across them. Limits are per-key, so independent keys give you linear scaling. Reach out to support if you need a higher per-key limit.
  </Step>
</Steps>

## Example: handling 429 in Node.js

```javascript theme={null}
async function fetchWithBackoff(url, options, attempt = 0) {
  const res = await fetch(url, options);

  if (res.status === 429) {
    const retryAfter = parseInt(res.headers.get("Retry-After") ?? "5", 10);
    const jitter = Math.random() * 500;
    const delayMs = retryAfter * 1000 + jitter;

    if (attempt >= 5) throw new Error("Too many retries");

    await new Promise((r) => setTimeout(r, delayMs));
    return fetchWithBackoff(url, options, attempt + 1);
  }

  return res;
}
```

<Warning>
  Don't retry every error blindly — only retry `429` and `5xx` responses. Retrying `4xx` errors (other than `429`) wastes budget and won't change the outcome.
</Warning>

<Tip>
  For bulk loads, the [`POST /contacts/upsert`](/api-reference/contacts#bulk-upsert-contacts) endpoint accepts up to 200 contacts in a single request. Prefer it over a loop of individual `POST /contacts` calls — one upsert costs one write against your budget.
</Tip>
