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

# Rate limits

> Two budgets — per-minute burst and monthly quota — both enforced at the edge.

Every API key is constrained by two independent limits. Hitting either one returns a `429 Too Many Requests`.

## The two budgets

|               | Free                     | Partner                 |
| ------------- | ------------------------ | ----------------------- |
| Per-minute    | 60 requests              | 600 requests            |
| Monthly quota | 100,000 requests         | 5,000,000 requests      |
| Window reset  | Sliding 60-second window | First of the month, UTC |

The per-minute limit catches bursts. The monthly quota keeps things fair across the developer pool. Both are enforced at the Cloudflare edge, so a 429 comes back fast — within a few milliseconds — and doesn't burn a database round-trip.

## Headers

Every authenticated response carries the monthly-quota state:

```
X-RateLimit-Limit:     100000
X-RateLimit-Remaining: 99873
X-RateLimit-Reset:     1714521600
```

* `X-RateLimit-Limit` — your tier's monthly quota.
* `X-RateLimit-Remaining` — quota left in the current month.
* `X-RateLimit-Reset` — Unix timestamp (seconds) when the monthly counter rolls.

The per-minute limit doesn't get its own header today; if you hit it, the `429` body and `Retry-After` header tell you the score.

## What a 429 looks like

When you exceed either limit:

```http theme={"dark"}
HTTP/1.1 429 Too Many Requests
Retry-After: 32
Content-Type: application/json
```

```json theme={"dark"}
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded: 60 requests per minute on the free tier.",
    "request_id": "req_01H...",
    "docs_url": "https://docs.elestrals.com/errors/rate_limit_exceeded"
  }
}
```

Two distinct error codes tell you *which* limit you hit:

* `rate_limit_exceeded` — per-minute burst. `Retry-After` is in seconds; the budget refills on a sliding window.
* `quota_exceeded` — monthly quota. `Retry-After` is the seconds until the 1st-of-month UTC rollover. You need to wait, upgrade to Partner, or [request additional capacity](https://developers.elestrals.com).

## Backing off

A polite client respects `Retry-After` and exponentially backs off if it keeps hitting the wall. A naive retry loop is the fastest way to get a key flagged for [abuse](/abuse-policy).

```javascript theme={"dark"}
async function fetchWithBackoff(url, key, attempt = 0) {
  const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` } });
  if (res.status !== 429) return res;

  const retryAfter = Number(res.headers.get('Retry-After') ?? 1);
  const delayMs = Math.min(retryAfter * 1000, 30_000) + Math.random() * 250;
  await new Promise((r) => setTimeout(r, delayMs));
  if (attempt > 4) throw new Error('rate-limit retries exhausted');
  return fetchWithBackoff(url, key, attempt + 1);
}
```

## Stay under the limit

The single biggest lever is [client-side caching](/caching). Most TCG data — sets, series, creature definitions — changes infrequently; caching responses for even a few minutes drops your request volume by 10× or more.

For predictable workloads (e.g., a nightly sync), schedule the work and avoid bursting. For interactive workloads (e.g., a deck builder), cache by card ID and only fetch on demand.
