Rate limiting
Per-key rate limits, standard headers, and how to handle 429s.
Requests are rate limited per API key, using a fixed one-minute window. The
default allowance is 600 requests per minute; your agreement may set a
different figure, so read X-RateLimit-Limit rather than hard-coding 600.
The window is fixed, not sliding: it resets on the minute boundary reported by
X-RateLimit-Reset, and the full allowance becomes available at once. Two
separate keys have two separate budgets, so isolating a bulk backfill onto its
own key keeps it from starving your interactive traffic.
Headers
Every response includes:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the window |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp (seconds) when the window resets |
429 responses additionally carry Retry-After, in seconds.
Handling 429
{ "error": { "code": "rate_limited", "message": "Rate limit exceeded..." } }Wait at least Retry-After seconds. Retrying sooner does not succeed early - it
just consumes the next window as soon as it opens.
async function withRetry(fn, max = 5) {
for (let i = 0; i < max; i++) {
const res = await fn();
if (res.status !== 429) return res;
const wait = Number(res.headers.get("Retry-After") || 1) * 1000;
await new Promise((r) => setTimeout(r, wait));
}
throw new Error("Rate limit: retries exhausted");
}Better still, avoid the 429. Watch X-RateLimit-Remaining and slow down as it
approaches zero, rather than sprinting into the wall and backing off.
Staying under the limit
Page with a sensible limit. limit=100 costs the same one request as
limit=10 and returns ten times the data.
Prefer webhooks to polling. A webhook subscription costs nothing against
your budget. Polling GET /jobs/{id} in a tight loop is the most common way
integrations exhaust their allowance; poll only to reconcile a delivery you
believe you missed, and space attempts out.
Use /media/stats for counts. Fetching totals is one request; paging the
whole library to count it can be hundreds.
Narrow facet computation. On /media/filters/counts, counts_bundle and
facet_load_more compute only the buckets you need, which is both faster and
fewer follow-up calls.
Retrying writes safely
Combine backoff with an Idempotency-Key on every POST. A 429 retry, or a
retry after a timeout, then cannot create a duplicate clip, publish or job: the
original response is replayed with Idempotent-Replayed: true. Use a fresh key
per logical operation - reusing one with a different body returns
409 idempotency_conflict.
Updated about 2 months ago
