Guides · Rate limits

Rate limits

Three limits protect the API, and they do not all answer the same way. Two are request rates: one on the read endpoints, a separate and faster one on usage ingestion. Both return 429 with a Retry-After header telling you how long to wait. The third is your event credit balance, which is a spending limit rather than a rate, and returns 402. Waiting does not clear a 402.

The three limits

LimitApplies toWhen you hit it
Read request rateEvery read endpoint: cost, usage, attribution, margin, dimensions, insights, rate cards. 100 requests per 15 minutes, per API key.429 with Retry-After and RateLimit-* headers.
Ingestion request rateUsage ingestion (POST /usage-events and /usage-events/batch). 100 requests per minute, per API key. At the 100-event batch maximum that is 10,000 events per minute.429 with Retry-After and RateLimit-* headers.
Event creditsUsage ingestion (POST /usage-events and /usage-events/batch). Each accepted event spends one credit from your organization's event balance (the plan allotment, which resets at each renewal, plus any event packs, which last 12 months). Not a rate limit.Single events: 402 with error.details.actionHint set to BUY_CREDITS; the event is not queued. Batch: per-item rejections inside the 202 (see below).

Batch ingestion never returns a batch-level 402

When your event credits run out, POST /usage-events/batch still returns 202. The rejection surfaces per event, inside the response body, not as a top-level status. Always inspect each item's status in a batch response rather than trusting the 202 alone.

Running out of credits: the 402

A 402 is not retryable on its own. It says the event was rejected because your credit balance reached zero, and the same request will keep failing until the balance changes. Read error.details.actionHint, which is BUY_CREDITS: buy an event pack from the Billing page (Growth and Scale) or move to a plan with a larger event allotment, then resend the rejected events. Resending is safe. Ingestion is idempotent on idempotency_key, and one logical event never spends two credits.

JSON
{
  "success": false,
  "error": {
    "code": "PAYMENT_REQUIRED",
    "message": "Your event credit balance has been depleted. Buy an event pack from the Billing page (Growth and Scale) or move to a plan with a larger event allotment, then resend the event.",
    "correlationId": "OLIVE-DINOSAUR-1b2fE4",
    "details": { "actionHint": "BUY_CREDITS", "retryable": false }
  }
}

Response headers

A rate-limited response carries the state you need to back off precisely.

HeaderMeaning
Retry-AfterSeconds to wait before retrying. Always honor this first.
RateLimit-LimitRequests allowed in the current request-rate window.
RateLimit-RemainingRequests left in the current window.
RateLimit-ResetSeconds until the current request-rate window resets.
RateLimit-PolicyThe window itself, as limit;w=seconds. 100;w=900 on reads, 100;w=60 on ingestion.

The body is the standard error envelope:

JSON
{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Slow down and retry after the current window resets.",
    "correlationId": "OLIVE-DINOSAUR-1b2fE4",
    "details": { "retryable": true }
  }
}

Handling a 429

A 429 is retryable: true. Wait for the Retry-After interval, then retry; if it is absent, fall back to exponential backoff. Never retry in a tight loop, and never retry a 4xx that is not a 429. In particular, do not treat a 402 as a throttle: backing off will not refill your credits. The SDKs implement the 429 case for you.

Reach for the SDK retry helper

The Error handling guide has a ready-made retry-with-backoff wrapper for Node.js and Python that honors Retry-After automatically.

Next steps