- A rolling-hour rate limit — requests per hour, set by your tier.
- A monthly quota — requests per UTC calendar month, set by your tier. Trial allowances use a lifetime budget instead of a monthly refill.
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
The gateway counts accepted metered reads, including response-cache hits and
data-layer failures. It excludes 401/403/429 responses and requests explicitly
marked non-billable, such as pre-read validation or route misses. Authentication
failures do not enter the rate limiter; later validation/plan failures may have
already spent an hourly token.
Response headers
Responses that pass through the metering chain expose limit headers when the limiter/quota state is available. Catalog responses do not use that chain. Use the returned headers to implement adaptive backoff: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.
Trial budgets do not refill. An exhausted trial uses the same quota_exceeded
code, but omits details.resets_at and the monthly reset header. Waiting for a
calendar rollover will not restore the allowance.
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.
Core, Sharp, and Max include odds, stats, and sharp-book prices. Sharp and Max
include WebSocket access. Max includes signals for settlement grades,
durable closing lines, closing-anchored CLV, and divergence. Active trial grants
and legacy subscriptions can differ; capability checks and returned headers
are authoritative for the token in use.
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.