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

# Rate Limits - Examino API Quotas and Best Practices

> Examino enforces 600 requests per minute per API key. Learn how the limit works, what a 429 looks like, and how to stay well within budget.

The Examino API enforces a rate limit to ensure fair usage and platform stability. Understanding how the counter works, and how to design around it, will help you run bulk imports and high-frequency reads without interruption.

## Limit & Window

The API allows **600 requests per minute** per API key, measured on a **sliding window**. The counter resets continuously as the window slides, not at a fixed clock boundary.

<Note>
  The rate limit counter is tracked **per API key**, not per IP address or per
  team. Two keys that belong to the same team each have their own independent
  600 req/min budget.
</Note>

## 429 Response

When you exceed the limit, the API responds immediately with HTTP `429` and the following body:

```json title="429 - rate_limited response" theme={null}
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please slow down and retry with exponential backoff."
  },
  "requestId": "c2e8a14f-3b09-4d72-a001-7f6e930bc441"
}
```

The request is **not** queued or delayed server-side, it is rejected outright. Your client is responsible for backing off and retrying.

## Best Practices

<AccordionGroup>
  <Accordion title="Batch instead of looping">
    The `POST /exams/{examId}/copies` endpoint accepts **up to 50 copies in a
    single call**. If you're creating copies one by one in a loop, you're
    spending 50× more of your rate-limit budget than necessary.

    ```json title="Batch copy creation - request body (abridged)" theme={null}
    {
      "copies": [
        { "studentName": "Alice Martin", "files": ["<fileId1>"] },
        { "studentName": "Bob Dupont",   "files": ["<fileId2>"] }
      ]
    }
    ```

    Batch where possible across all endpoints that support it.
  </Accordion>

  <Accordion title="Exponential backoff on 429">
    When you receive a `429`, wait before retrying. Start with a 1-second delay
    and double on each subsequent failure, adding random jitter to avoid
    thundering-herd problems:

    ```text theme={null}
    delay = min(base * 2^attempt, max_delay) + random_jitter
    ```

    A reasonable schedule: **1 s → 2 s → 4 s → 8 s → 16 s**, with ±20 % jitter
    applied to each value. Most backpressure clears within the first one or two
    retries.
  </Accordion>

  <Accordion title="Don't poll in tight loops">
    AI correction is an asynchronous process that typically takes **several
    minutes** per copy. Polling the Corrections endpoint every second burns your
    rate-limit budget with no benefit.

    Poll at a **15–30 second interval** instead. If your infrastructure
    supports it, use webhook notifications to eliminate polling entirely and
    receive results the moment they are ready.
  </Accordion>

  <Accordion title="Separate keys by use case">
    Because each key has its own independent budget, you can prevent a burst of
    write traffic from starving your read traffic by assigning different keys to
    different workloads:

    | Key                 | Purpose             | Scopes                            |
    | ------------------- | ------------------- | --------------------------------- |
    | `"LMS bulk import"` | Nightly copy upload | `copies:write`, `exams:read`      |
    | `"Dashboard reads"` | Live result fetches | `corrections:read`, `copies:read` |

    A bulk import job that hits its limit will not affect a concurrent dashboard
    query running under a different key.
  </Accordion>
</AccordionGroup>

## Request Logs

Every API call is logged server-side with the following details:

* API key used (by name, not secret)
* HTTP method and path
* Response status code
* Response duration
* Error code (if applicable)

You can review your team's full call history from **Administration → API** in the Examino web app. Use the logs to audit usage patterns, spot unexpected spikes, and correlate `requestId` values with entries in your own application logs.

<Tip>
  If you're approaching your rate limit regularly, the logs in **Administration
  → API** will show which endpoints are being called most frequently. That is
  usually the best place to start optimizing, either by batching, caching
  responses, or switching to a webhook-based flow.
</Tip>
