Rate limits
Rate limits
Paid applies per-organization rate limits to protect the platform for every
customer. This page explains how limits are counted, how to recognize a
rate-limited request, and how your integration should respond to a 429.
How limits are counted
Limits use a token bucket per organization: a bucket holds a budget of tokens (its burst capacity) and refills continuously at a sustained per-second rate. Short spikes above the sustained rate succeed as long as the bucket holds enough tokens.
Today the per-organization limit applies to signal ingestion
(POST /api/v2/signals/bulk and the legacy v1 usage endpoints). Ingestion
is signal-counted: a request carrying 500 signals consumes 500 tokens,
not 1. Other endpoints are not yet rate limited per organization; as
limits extend to them they will use this same framework, counting one
token per request. Live and test-mode organizations have separate buckets,
so load tests against a test organization never consume your production
budget.
Limits are per organization, not per API key. If your workload needs a higher sustained rate, contact Paid support.
The 429 response
When the bucket cannot cover a request, the API responds with
429 Too Many Requests, a Retry-After header (in seconds), and an error
body:
The 429 status, the RATE_LIMIT_EXCEEDED code, and the Retry-After
header are the complete contract: wait at least Retry-After seconds,
then retry.
A rate-limited signals request is rejected whole: no signals from that
request are ingested. Retry the whole batch after the Retry-After
interval; a stable idempotencyKey on each signal makes the retry safe
(see below).
Two kinds of 429
The API returns 429 for two distinct limits. Discriminate on the body
code, not the status:
Only the rate limiter’s 429 carries Retry-After. A
CONCURRENCY_LIMIT_EXCEEDED response clears as soon as one of your
in-flight analytics queries completes.
Back off correctly
- Respect
Retry-After. Wait at least that many seconds before retrying the request. - Use exponential backoff with jitter for repeated
429s. Never retry in a tight loop; that keeps your bucket empty and delays your own recovery. - Smooth your send rate rather than bursting: a steady stream at your working rate ingests everything a spiky one does, without rejections.
- Batch signals up to the 500-per-request maximum. Fewer, fuller requests reduce overhead, and the signal-counted cost is the same.
- Give every signal a stable
idempotencyKey, and resend exactly the same key on a retry — never generate a fresh key per attempt. Keys are optional, and signals without one are not deduplicated, so retrying a keyless batch can ingest the same signals twice. Duplicate keys within an organization are skipped, which is what makes whole-batch retries safe.