# Rate limits

> Request and import limits, the limit headers and what to do on a 429.

Source: https://diko.app/en/api-docs/rate-limits

## How limits work

Limits protect the API and other users from a client that has gone wrong. Every policy is a token bucket: the **capacity** says how many requests you can send at once and the **refill** says how many come back over time. For example, the `read` policy lets you send 10 read requests at once and then about 5 per second.

| Policy | Applies to | Capacity | Refill |
|---|---|---|---|
| `read` | the API key | 10 | 5 / s |
| `write` | the API key | 5 | 1 / s |
| `imports` | the API key | 5 | 30 / min |
| `account` | the billing tenant: the paying team on a team plan, otherwise the user (all keys and members together) | 20 | 10 / s |
| `export` (`format=text`, `srt`, `vtt`) | the billing tenant, as for `account` | 10 | 1 / s |
| `me` (`GET /me` only) | the API key | 5 | 1 / s |
| `auth_failures` | a trusted IP (IPv6: the /64 network) | 20 | 20 / min |

The *billing tenant* is the team that pays for the plan when you use a team plan, otherwise it is you. So on a team plan the `account` and `export` buckets are shared by every key of every team member, and creating more keys or inviting more members does not give you more capacity. The `read`, `write`, `imports` and `me` policies stay per key.

The `export` policy is charged before the transcript is read, so a request that ends in 404 also uses one of its tokens.

One request can fall under several policies. A read counts against both `read` and `account`; `GET /me` counts against `read`, `account` and `me`; `POST /library/imports` counts against `write`, `imports` and `account`. The policies of every endpoint are in the [API reference](https://diko.app/en/api-docs/reference.md).

In addition:

- up to **2 concurrent requests** per account (all your keys together); on a team plan the whole team shares a limit of **3 concurrent requests**; beyond that you get 429 [`rate_limit_exceeded`](https://diko.app/en/api-docs/errors.md#rate_limit_exceeded) with `details.policy = concurrency`;
- `GET /me` has its own limit (the `me` policy): a burst of 5, then **1 request per second**;
- database work goes through a shared guard, so under load the API can return 503 [`service_unavailable`](https://diko.app/en/api-docs/errors.md#service_unavailable) with `Retry-After: 2`. That is not your mistake: just retry.

The `limits.rate` field of the `GET /me` response lists the policies that apply to the key (`read`, `write`, `imports`, `account`, `me` and `export`) with their burst and refill. The numbers are the same for every key; whose bucket they are counted against is in the table above. The `auth_failures` policy and the concurrency limit are the ones in the table and the list above.

## Limit headers

Every response to an authenticated request that reached the limiter reports the limit state in two ways: following the IETF `RateLimit` draft and with the widely used `X-RateLimit-*` headers. Responses that are rejected before the limits are applied (a missing or invalid key, an IP that is not allowed, an unknown route, a wrong method, a body that is too large, of the wrong type or not received in time) carry no limit headers.

```http
RateLimit-Policy: "read";q=10;w=2, "account";q=20;w=2
RateLimit: "read";r=7;t=1
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1
```

| Header | Meaning |
|---|---|
| `RateLimit-Policy` | the policies that apply: `q` is the capacity, `w` the window in seconds |
| `RateLimit` | the **most limiting** policy now: `r` is the remaining requests, `t` the seconds until the next token |
| `X-RateLimit-Limit` | the capacity of that policy |
| `X-RateLimit-Remaining` | requests left |
| `X-RateLimit-Reset` | seconds until the next token |

## When you get a 429

When a limit is exceeded the API returns 429 [`rate_limit_exceeded`](https://diko.app/en/api-docs/errors.md#rate_limit_exceeded) with a `Retry-After` header (whole seconds, at least 1, until the next token). `error.details.policy` names the exceeded policy:

```json
{
  "type": "error",
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "message": "Too many requests. Slow down and retry after the time in Retry-After.",
    "doc_url": "https://diko.app/en/api-docs/errors#rate_limit_exceeded",
    "details": { "policy": "read", "retry_after": 1 }
  },
  "request_id": "req_QBPIQ5QTIWAUYTMDNCLD7CMKBE"
}
```

What to do:

1. Wait `Retry-After` seconds and add a little random jitter so several clients do not retry at the same moment.
2. Keep at most 2 requests in flight at once for the whole account: the limit counts all your keys together, and on a team plan all team members' keys.
3. Read lists in large pages (`limit=100`) and follow `has_more` instead of fetching items one by one.
4. Instead of polling, use [webhooks](https://diko.app/en/api-docs/webhooks.md) or `GET /events`.

The retry helper code is on the [Errors](https://diko.app/en/api-docs/errors.md#retry-recipe) page.

## Import capacity

Imports have an extra guard because they use credits and the processing queue. Each decision is made when the import is created:

| Limit | Value |
|---|---|
| Active imports of a user (`queued`, `processing`, `canceling`) | up to 10 |
| Active imports across all users | up to 200 |
| Imports of a user per day (the last 24 hours) | up to 500 |

The daily limit counts **every** import created in the last 24 hours, including imports that were answered from your library or from Diko's shared cache (`reused: true`). Those cost no credits, but they are still imports.

When any limit is exceeded the API returns 429 [`queue_limit_exceeded`](https://diko.app/en/api-docs/errors.md#queue_limit_exceeded) with `Retry-After: 60`. `error.details.limit` says which limit it was: `user_active_imports`, `capacity` (all users) or `user_daily_imports`. Wait until some imports finish instead of sending more requests.

```json
{
  "type": "error",
  "error": {
    "type": "rate_limit_error",
    "code": "queue_limit_exceeded",
    "message": "Too many imports are queued or running. Retry later.",
    "doc_url": "https://diko.app/en/api-docs/errors#queue_limit_exceeded",
    "details": { "limit": "user_active_imports", "retry_after": 60 }
  },
  "request_id": "req_QBPIQ5QTIWAUYTMDNCLD7CMKBE"
}
```

> **Note:**
> Creating an import never waits for a download, so the request returns quickly. If the internal processing queue is busy, the import stays `queued` and is tried again later; it does not turn into an error. A video without captions is transcribed by speech recognition in Diko's shared queue. Those jobs use the same per-user transcription slots as your own uploads in the app, but never the last free one, so your uploads and recordings are not blocked by an API client.

## Authentication failure limit

The `auth_failures` policy limits attempts with bad keys from the same trusted IP. A shared budget also applies to checking keys never seen before, so attempts to guess keys get `invalid_api_key` with no extra information. Sending the same bad key again is pointless.
