Errors & limits
Every non-2xx response uses one envelope with a stable machine-readable code, and every authenticated response carries rate-limit headers.
The error envelope
{
"error": {
"code": "insufficient_credits",
"message": "insufficient credits",
"details": { "balance": 120, "cost": 636 }
}
}code is stable, so branch on it. message is human-readable and may change. details appears only when there's structured context to give.
Error codes
| Code | Status | Meaning | Retry? |
|---|---|---|---|
unauthorized |
401 | Missing, invalid or revoked API key. | Don't retry: fix the key. |
invalid_request |
400 | Malformed JSON or invalid fields (bad inputs, oversized schema, bad webhookUrl, bad limit/cursor…). The message says which. | Don't retry: fix the request. |
invalid_url |
400 | The url field isn't a parseable URL. | Don't retry: fix the URL. |
unsupported_url |
400 | The URL parses but isn't a supported video platform link. | Don't retry with the same URL. |
insufficient_credits |
402 | Your balance can't cover the quoted price. details.balance and details.cost are in credits. | Retry after topping up credits. |
rate_limited |
429 | Per-minute request window exceeded for this endpoint. | Retry after the Retry-After header (seconds). |
not_found |
404 | No such extraction/file/schema belongs to your account. | Don't retry: check the id. |
conflict |
409 | Idempotency-Key reused with a different request body. | Don't retry: use a new key or the original body. |
internal_error |
500 / 502 | Something failed on our side (502 when the video couldn't be fetched right now). | Safe to retry with backoff; use an Idempotency-Key on submits. |
Rate limits
Limits are per account, per endpoint, on a fixed one-minute window. Every authenticated response includes:
X-RateLimit-Limit: 60 # ceiling for this window
X-RateLimit-Remaining: 58 # requests left
X-RateLimit-Reset: 1765459200 # unix seconds when the window resets| Endpoint | Limit |
|---|---|
| POST /v1/extractions (and /v1/extract) | 6 / minute |
| POST /v1/probe | 10 / minute |
| GET /v1/extractions/:id and /:id/result (shared bucket) | 60 / minute |
| All other endpoints | 60 / minute each |
On 429 rate_limited a Retry-After header (seconds) tells you exactly how long to wait. Separately from request limits, at most 2 extractions run per account at once. Extra submissions are accepted with status queued and start automatically when a slot frees.
Idempotency
Submits are not safely retryable by default: a retried POST /v1/extractions creates (and charges) a second extraction. Send an Idempotency-Key header (any unique string, e.g. a UUID) to make them safe:
- First request with a key: normal behaviour,
201. - Retry with the same key and same body: the original extraction is returned with
200, and nothing new is created or charged. - Same key, different body:
409 conflict.
Pagination
List endpoints use cursor pagination. Pass limit (1–100, default 20) and the next_cursor from the previous page as cursor:
{
"data": [ ... ],
"has_more": true,
"next_cursor": "eyJ0IjoiMjAyNi0wNi0xMVQ..."
}Cursors are opaque, so never construct or modify them. Results are ordered newest first.