Rate limits

Two independent buckets cap traffic: a per-minute sliding window and a per-day fixed UTC window. Either exhausting returns 429.

A 429 has two possible causes: quota exhaustion (the buckets above) and the per-key 4xx circuit breaker (abuse protection, described below). Tell them apart by the headers: a circuit-breaker 429 carries X-RateLimit-4xx-Cooldown, a quota 429 does not. Either way, honour Retry-After.

Tier limits

PlanPer minutePer day (UTC)
Free100250 (500 with GitHub)
Solo30010,000
Trio50025,000
Edge1,500100,000
Pro3,000200,000
EnterpriseUnlimitedUnlimited

Webhook deliveries do not count against the request quota. WebSocket message receives do not count; explicit room subscribe/unsubscribe operations count as one request each.

GET /v1/sports and GET /v1/coverage are public and work without a key. Called without one, they share a separate, smaller per-IP limit (30/minute by default) instead of a key's plan tier — worth knowing if you are scripting against them before you have a key in hand. Every other endpoint always requires a key and uses the tier limits above.

Response headers

Every 2xx and 4xx response includes the current quota state:

httphttp
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1763510400
X-RateLimit-Limit-Minute: 100
X-RateLimit-Limit-Day: 250

X-RateLimit-Limit and X-RateLimit-Remaining track the tighter of the two buckets; X-RateLimit-Limit-Minute and X-RateLimit-Limit-Dayexpose each bucket's cap. X-RateLimit-Reset is a Unix epoch (seconds) marking when the limiting window resets.

429: quota exhaustion

httphttp
HTTP/1.1 429 Too Many Requests
Retry-After: 23
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1763510400
Content-Type: application/json
jsonjson
{
  "error": {
    "code": "rate_limited",
    "message": "Daily quota exhausted (250 of 250 requests used)"
  },
  "suggested_fix": "You've hit your free tier daily cap. Upgrade to solo at https://bigballsdata.com/pricing to lift this limit to 10,000/day.",
  "docs_url": "https://bigballsdata.com/docs/rate-limits",
  "support": {
    "discord": "https://discord.gg/H2WJBQurbq",
    "email": "[email protected]"
  },
  "meta": {
    "request_id": "...",
    "timestamp": "2026-06-23T00:00:00Z",
    "current_usage": {
      "minute": { "used": 12, "limit": 100, "resets_at": "2026-06-23T05:01:00Z" },
      "day": { "used": 250, "limit": 250, "resets_at": "2026-06-24T00:00:00Z" }
    },
    "upgrade_path": {
      "current_tier": "free",
      "recommended_tier": "solo",
      "url": "https://bigballsdata.com/pricing"
    }
  }
}

Both 429 types share error.code: rate_limited; the error.message names the exhausted bucket and its usage. On a quota 429 the body also carries suggested_fix (the next tier and the cap it lifts you to), meta.current_usage (live per-bucket usage with reset times), and meta.upgrade_path (recommended_tier is null once you're on the top tier, where the fix points you at sales instead). All of this is additive; error.code, docs_url, support, and meta.request_id are unchanged. The per-day window does not recover until the next UTC midnight; honour Retry-After for the exact wait.

429: per-key 4xx circuit breaker

Separate from the quota buckets, each key has a circuit breaker that watches its client-error rate. Two patterns push it: a client hammering the API with malformed requests (a bad key format, an invalid path param, a wrong enum value in a tight retry loop), or a client that ignores Retry-After and keeps firing after it has exhausted its quota. Both are broken clients, and both burn capacity. The breaker protects the platform from them. It is abuse protection, not a punitive throttle: it never triggers on healthy traffic, and it clears itself automatically once you stop and wait out the cooldown.

Over a rolling 1-hour window, the breaker trips when a key crosses either threshold:

  • 500 client-error (4xx) responses in the window (the absolute gate), or
  • a 4xx rate of 80% or higher once at least 200 requests have been seen in the window (the ratio gate)

Quota 429s do count toward the 4xx tally, so a client stuck in a retry loop past its cap will trip the breaker rather than spin forever. A well-behaved client never does: if you honour Retry-After you back off and stay under the request floor. Successful (2xx) responses count toward the volume, not the error rate, so healthy traffic never moves you toward a trip; the breaker's own cooldown 429s are the only responses excluded entirely (so the cooldown cannot perpetuate itself).

Paid keys get a wider berth, not a different rule. A key on any plan other than Free gets 10× the absolute gate and the ratio gate's minimum sample — 5,000 4xx responses and a 2,000-request floor instead of 500 and 200. The 80% ratio gate itself is unchanged for every plan, including Free: a paid key running 80%+ unexplained 4xx over a large enough sample still trips. The wider berth exists because a paid key belongs to someone we can reach; it is not an exemption from the breaker.

On a trip, every request from that key returns 429 for a 10-minute cooldown, regardless of how much quota the key has left. A circuit-breaker 429 carries these headers (this is how you distinguish it from a quota 429):

httphttp
HTTP/1.1 429 Too Many Requests
Retry-After: 600
X-RateLimit-4xx-Cooldown: 600
X-RateLimit-4xx-Reset: 1763510520
Content-Type: application/json
jsonjson
{
  "error": {
    "code": "rate_limited",
    "message": "Too many failed (4xx) requests. Fix your request and retry after the cooldown."
  },
  "docs_url": "https://bigballsdata.com/docs/rate-limits",
  "support": {
    "discord": "https://discord.gg/H2WJBQurbq",
    "email": "[email protected]"
  },
  "meta": { "request_id": "...", "timestamp": "2026-05-15T20:18:00Z" }
}

Retry-After and X-RateLimit-4xx-Cooldown are the same value (seconds remaining on the cooldown). X-RateLimit-4xx-Reset is the Unix epoch (seconds) when the cooldown lifts. The breaker is keyed on error rate, not request volume, so it can trip a key on any plan, including Enterprise and other unlimited keys — but not identically: the 80% ratio gate is the same for everyone, while the absolute gate and the minimum sample are 10× higher on paid plans (see above).

To avoid it: read the error.message and suggested_fix on your 4xx responses and fix the request rather than blindly retrying. A client that handles its own errors will never see this breaker.

Recovery

Honour Retry-After exactly. Do not retry sooner, the gateway returns 429 for the full window even if your token bucket would technically have refilled. Clients should:

  • Read X-RateLimit-Remaining after every request
  • Self-throttle below the limit rather than rely on 429 recovery
  • When the error.message reports a (.../day) exhaustion, queue work until the next UTC midnight (or honour Retry-After)
  • Never retry a non-idempotent POST automatically, 429 on a webhook subscription create can mean the resource was created right before the throttle landed
  • On a circuit-breaker 429 (the X-RateLimit-4xx-Cooldown header is present), do not just wait and retry the same call, fix the failing requests first or you will re-trip the breaker as soon as the cooldown lifts

Burst handling

The per-minute bucket is a sliding window. Bursting up to the cap is fine; sustained traffic above the cap returns 429 on the first request past the limit. There is no hidden burst credit.