Rate limits

Two different refusals, and the API says which. 429 rate_limited means slow down — nothing was consumed and the same request will succeed shortly. 429 quota_exceeded means you are out — an allowance is spent and time or an operator is what changes it. A client that treats them alike will retry a spent quota until its own timeout.

The ceilings

A token bucket per key and per account, refilling continuously. A request costs one token.

| Scope | Burst | Sustained | |---|---|---| | Per key, all endpoints | 120 | 60 / minute | | Per key, POST /v1/runs | 20 | 10 / minute — submission spends GPU seconds | | Per key, POST /v1/destinations/{id}/test | — | 6 / minute | | Per account, all endpoints | 300 | 180 / minute — five keys cannot multiply the footprint by five | | Per account, POST /v1/uploads/{id}/parts | 600 | 600 / minute — a recorder draining a backlog is a legitimate burst | | Unauthenticated, per IP (GET /v1/health) | 60 | 30 / minute |

Both the key and the account ceiling are checked and the tighter one answers; detail.scope says which, so a program that hit the account ceiling does not spend an afternoon looking at its own key.

The headers, on every response

curl -sS -i https://<host>/v1/runs?limit=1 -H "Authorization: Bearer $SCRIPTRIP_KEY"
HTTP/1.1 200 OK
X-Request-Id: req_0195c8e4-9a10-7d3e-8f21-0b6c4d2e7a55
RateLimit-Limit: 60
RateLimit-Remaining: 41
RateLimit-Reset: 37
Cache-Control: no-store
Content-Type: application/json; charset=utf-8

RateLimit-Limit is the sustained rate per window, RateLimit-Remaining the tokens left — which can exceed the limit, because every bucket holds a burst of up to twice its sustained rate — RateLimit-Reset the seconds until the bucket is full. When two ceilings apply the headers describe the one with fewer tokens remaining — the one that will refuse first. Pace on these and you will never see a 429.

When you are refused

curl -sS -i -X POST https://<host>/v1/runs -H "Authorization: Bearer $SCRIPTRIP_KEY" \
  -H "Content-Type: application/json" -d '{"source":{"type":"youtube","url":"https://youtu.be/dQw4w9WgXcQ"}}'
HTTP/1.1 429 Too Many Requests
Retry-After: 6
RateLimit-Limit: 10
RateLimit-Remaining: 0
RateLimit-Reset: 6

{"error":{"code":"rate_limited","message":"This key has submitted more than 10 runs in the last minute.","detail":{"scope":"key","limit":10,"window_seconds":60},"request_id":"req_0195c8e4-9a10-7d3e-8f21-0b6c4d2e7a55"}}

Retry-After is always delta seconds, never a date, and never more than the window. Honour it.

A recommended backoff

The generated TypeScript client does exactly this.

Quotas

| Ceiling | Refused with | |---|---| | Monthly transcribed seconds | 429 quota_exceeded at POST /v1/runs, detail: {limit_seconds, used_seconds} | | Concurrent open uploads (5) | 429 quota_exceeded at POST /v1/uploads | | Bytes per day (20 GiB) | 429 quota_exceeded at POST /v1/uploads | | Bytes per media object (2 GiB) | 413 payload_too_large | | Seconds per run (8 hours) | 422 unprocessable_source, or a run failed too_long | | Destinations per account (20) | 429 quota_exceeded at POST /v1/destinations |

The monthly check is against seconds already recorded, not the run being submitted; a run that crosses the ceiling mid-flight finishes, and the next submission is refused. Cached seconds do not count.