Rate limits
Request and import limits, the limit headers and what to do on a 429.
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.
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_exceededwithdetails.policy = concurrency; GET /mehas its own limit (themepolicy): 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_unavailablewithRetry-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.
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 with a Retry-After header (whole seconds, at least 1, until the next token). error.details.policy names the exceeded policy:
{
"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:
- Wait
Retry-Afterseconds and add a little random jitter so several clients do not retry at the same moment. - 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.
- Read lists in large pages (
limit=100) and followhas_moreinstead of fetching items one by one. - Instead of polling, use webhooks or
GET /events.
The retry helper code is on the Errors 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 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.
{
"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"
}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.