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)
Free1001,000 (2,000 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: 1000

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 (1000 of 1000 requests used)"
  },
  "suggested_fix": "You've hit your free tier daily cap. Upgrade to starter 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": 1000, "limit": 1000, "resets_at": "2026-06-24T00:00:00Z" }
    },
    "upgrade_path": {
      "current_tier": "free",
      "recommended_tier": "starter",
      "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 200-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).

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 applies to every plan, including Enterprise and other unlimited keys.

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.