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

# Rate Limiting

> Understand rate limit tiers, headers, and how to handle 429 responses.

## Overview

Every API request is rate-limited based on the type of operation. Limits are applied per authenticated user (or per IP for unauthenticated requests) using a sliding window algorithm.

## Rate Limit Tiers

| Tier     | Limit        | Window   | Typical Endpoints                |
| -------- | ------------ | -------- | -------------------------------- |
| `read`   | 300 requests | 1 minute | `GET` endpoints (list, retrieve) |
| `write`  | 30 requests  | 1 minute | `POST`, `PUT`, `PATCH`, `DELETE` |
| `ai`     | 5 requests   | 1 minute | AI quiz generation               |
| `render` | 10 requests  | 1 hour   | Video render requests            |

## Response Headers

Every successful response includes rate limit headers:

| Header                  | Description                                     |
| ----------------------- | ----------------------------------------------- |
| `X-RateLimit-Limit`     | Maximum requests allowed in the current window  |
| `X-RateLimit-Remaining` | Requests remaining in the current window        |
| `X-RateLimit-Reset`     | Unix timestamp (seconds) when the window resets |

**Example response headers:**

```
HTTP/1.1 200 OK
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 297
X-RateLimit-Reset: 1742083260
```

## Handling 429 Responses

When you exceed the rate limit, the API returns a `429 Too Many Requests` response in [RFC 9457 Problem Details](/errors) format with a `Retry-After` header:

```json theme={null}
{
  "type": "https://quizquail.com/problems/rate-limit-exceeded",
  "title": "Too Many Requests",
  "status": 429,
  "detail": "Rate limit exceeded. Try again in 12 seconds."
}
```

**Response headers on 429:**

```
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 12
```

## Best Practices

### Use the `Retry-After` Header

Always respect the `Retry-After` header value. It tells you exactly how many seconds to wait before retrying.

### Implement Exponential Backoff

For robust integrations, combine `Retry-After` with exponential backoff:

```javascript theme={null}
async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (response.status !== 429) {
      return response;
    }

    const retryAfter = parseInt(response.headers.get("Retry-After") || "1", 10);
    const backoff = retryAfter * 1000 * Math.pow(2, attempt);

    console.log(`Rate limited. Retrying in ${backoff / 1000}s...`);
    await new Promise((resolve) => setTimeout(resolve, backoff));
  }

  throw new Error("Max retries exceeded");
}
```

### Monitor Your Usage

Check `X-RateLimit-Remaining` proactively. When it drops below 10% of your limit, slow down requests to avoid hitting the ceiling.

```javascript theme={null}
function shouldThrottle(response) {
  const limit = parseInt(response.headers.get("X-RateLimit-Limit"), 10);
  const remaining = parseInt(response.headers.get("X-RateLimit-Remaining"), 10);
  return remaining < limit * 0.1;
}
```

### Batch Where Possible

Reduce request count by using list endpoints with appropriate `limit` values instead of fetching resources one at a time. See [Pagination](/pagination) for details on efficient data retrieval.
