Rate limits

Every key gets an hourly budget. Every response tells you what is left, so you never have to guess.

The headers

These come back on every response, not just the ones that fail:

HeaderMeaning
X-RateLimit-LimitRequests allowed per hour for this key.
X-RateLimit-RemainingHow many are left in the current window.
X-RateLimit-ResetUnix timestamp (seconds) when the window resets.
Retry-AfterOn 429 only. Seconds to wait before trying again.

The budget is per key, not per user or per brand — so a noisy batch job cannot starve your interactive integration if you give them separate keys. The exact limit depends on your plan, and can be tuned per key.

Going over

HTTP/1.1 429 Too Many Requests
Retry-After: 34
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1783252800

{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limited",
    "message": "Too many requests."
  }
}

Nothing was processed. Wait Retry-After seconds and send exactly the same request again — with the same Idempotency-Key if it was a write.

Staying under

Do not wait for the 429. Watch X-RateLimit-Remaining and slow down as it drains:

async function call(req) {
  const res = await fetch(req)

  if (res.status === 429) {
    const wait = Number(res.headers.get('Retry-After') ?? 5)
    await sleep(wait * 1000)
    return call(req)                                   // same request, same idempotency key
  }

  // Ease off before we hit the wall, rather than after.
  const remaining = Number(res.headers.get('X-RateLimit-Remaining') ?? Infinity)
  if (remaining < 20) {
    const reset = Number(res.headers.get('X-RateLimit-Reset')) * 1000
    const perRequest = Math.max(0, (reset - Date.now()) / Math.max(remaining, 1))
    await sleep(Math.min(perRequest, 2000))
  }

  return res
}

Spending less budget

  • Do not poll for things we will tell you about. A webhook costs you zero requests; polling a post's status every second costs 3,600 an hour. See Webhooks.
  • Ask for bigger pages. per_page=100 reads the same data in a fifth of the calls that per_page=20 needs.
  • Cache what does not move. Brand ids and account ids change rarely; fetch them once, not on every publish.
  • Back off on the poll loops you do keep. A report export takes seconds, not milliseconds — poll every few seconds, not continuously.