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:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix 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.


Did this page help you?