- A rolling-hour rate limit — requests per hour, set by your tier.
- A monthly quota — requests per billing period, also set by your tier.
429 with a code that tells you which
limit was hit. Both limits are keyed on your organization, not your token —
rotating or adding tokens doesn’t reset or multiply them.
What counts
Quota is deducted only after a real data-layer lookup. A request rejected
before reaching the data layer (bad token, over quota, rate-limited) does not
consume monthly quota.
Response headers
Every billed response — success or429 — carries the current limit state, so
you can implement adaptive backoff without provoking a 429:
Hourly rate limit
The hourly limit is a token bucket: capacity equals your tier’s hourly cap, refilling smoothly. This tolerates bursts — a quiet client can briefly exceed the steady-state rate (useful for backfills and overnight batch pulls). When the bucket is empty:Retry-After is the whole number of seconds until the bucket has a token again.
Honor it before retrying.
Monthly quota
The monthly quota is a hard counter — there’s no overage. When it’s exhausted:Retry-After is not set on quota_exceeded — the wait is typically days.
Either upgrade your plan or wait for resets_at.
An upgrade takes effect within about a minute: the API caches the plan
attached to each token for up to 60 seconds, so a request issued immediately
after checkout can still be rejected against the old quota. Retry rather than
treating that as a failed upgrade.
When a plan does not cover an endpoint
Quota and rate limits answer “how much”; your plan also decides “what”. An endpoint your plan does not include returns 403 witherror.code = "feature_not_in_plan":
details.feature names the capability and details.required_tier
names the plan that sells it.
These refusals do not consume monthly quota, though they do draw down the
hourly bucket — the plan check runs after the rate limiter.
One case is deliberately not an error. Odds responses omit bookmakers your plan
does not cover and return 200; the response is correct, just narrower. Asking
for only books you do not have returns 403 feature_not_in_plan rather than
an empty result, because an empty market set is indistinguishable from an
outage. Check class on /v1/bookmakers before concluding a
book is missing.
Handling 429s
1
Distinguish the two codes
Branch on
error.code: rate_limited is short-lived; quota_exceeded needs an upgrade or a
wait until resets_at.2
Respect Retry-After
On
rate_limited, wait Retry-After seconds before retrying. Don’t retry quota_exceeded in a
tight loop.3
Back off proactively
Watch
X-RateLimit-Remaining and X-Requests-Remaining-Month on successful responses and slow
down before you hit zero.