Two independent limits apply to every billed request:
  1. A rolling-hour rate limit — requests per hour, set by your tier.
  2. A monthly quota — requests per billing period, also set by your tier.
A request that violates either returns 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 or 429 — 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 tier (effective immediately) or wait for resets_at.

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.

Streaming is separate

WebSocket connections are not counted against the REST hourly bucket or monthly quota. They have their own connection and message-rate limits. Contact support@sportquanta.com for streaming access.