Errors
Every error returns the standard envelope with errorpopulated and data set to null. The HTTP status code reflects the error class.
Error shape
{
"error": {
"code": "bad_request",
"message": "invalid match id \"12345\""
},
"suggested_fix": "IDs are Big Balls UUIDs you get from the corresponding list endpoint, not from another source. Call GET /v1/matches?sport=football and copy the `id` field of any result into this path.",
"example": "https://api.bigballsdata.com/v1/matches?sport=football&limit=3",
"docs_url": "https://bigballsdata.com/docs/matches",
"support": {
"discord": "https://discord.gg/H2WJBQurbq",
"email": "[email protected]"
},
"meta": {
"request_id": "375110c3-c77a-4b26-b0d1-dfa8e8085611",
"timestamp": "2026-06-16T15:08:07.218Z"
}
}The error object carries code and message, plus an optional validation_errors map on validation failures. Alongside it the envelope returns up to five top-level fields: suggested_fix (a concrete corrected request, included only when the fix is unambiguous, omitted otherwise), example (a full copy-paste URL that works today, on the errors where we can build one), docs_url, support, and meta. Always log meta.request_id. When you email [email protected], include it: with the request_id we pull the exact gateway trace instantly, no database search required. Without it, the same request needs a manual lookup.
Error codes
These are the eleven codes defined for error.code. They are part of the public contract and will not be renamed without a version bump. Every one of them is emitted by the gateway today.
| Status | Code | Cause | Retry? |
|---|---|---|---|
| 400 | bad_request | Malformed or missing query / body parameter | No, fix the request |
| 401 | unauthorized | Missing, invalid, or revoked API key | No |
| 403 | forbidden | Key's plan tier too low, or an IP-restricted route | No, upgrade |
| 404 | not_found | A real endpoint, but the resource id does not exist or is not yet ingested | No |
| 404 | route_not_found | The path is not a route we serve (a bad URL or a guessed endpoint). The body names the nearest real endpoint in meta.did_you_mean and a corrected, copy-paste request in suggested_fix. Distinct from not_found, which means a real endpoint's id missed. | No, fix the URL |
| 403 | key_paused | Key was paused by an operator. Reversible — reactivate it from the dashboard, or email support. Not an upgrade prompt. | No, reactivate |
| 409 | conflict | A concurrent request with the same Idempotency-Key is still resolving | Yes, after a moment |
| 422 | unprocessable | Value rejected by the canonical schema | No |
| 429 | rate_limited | Per-minute or per-day quota exceeded | After Retry-After |
| 500 | internal | Unhandled exception. A bare 502/503 with no JSON body is the edge, not the API — retry it the same way. | Yes, backoff |
| 503 | upstream_unavailable | A transient upstream or catalog read failed. Your request is fine — this one is on us. | Yes, backoff |
Common 400s
Two requests cause most 400s. Both return error.code: "bad_request" with a message that names the fix.
A non-UUID id on /v1/matches/:id
Single-resource routes take a Big Balls UUID that you read off the matching list endpoint. A numeric or third-party id is rejected immediately, before any upstream lookup, and the suggested_fix names the list call that hands you a real one. A bare /v1/matches with no filter is not an error — it returns live and upcoming matches across every sport.
# 400 - 12345 is not a Big Balls UUID
curl "https://api.bigballsdata.com/v1/matches/12345?sport=football" -H "Authorization: Bearer $KEY"
# 200 - no filter at all: live + upcoming across every sport
curl "https://api.bigballsdata.com/v1/matches" -H "Authorization: Bearer $KEY"
# 200 - list first, then copy an id from the response
curl "https://api.bigballsdata.com/v1/matches?sport=football&limit=3" -H "Authorization: Bearer $KEY"A provider id on /v1/players/:id (the id came from a different response)
The most common shape of this mistake isn't a made-up id, it's a real id from our own API — just from the wrong field. Match lineups and WC2026 squads return a provider id (bdl_player_id / af_player_id) next to the player's name, not our UUID. Pasting that provider id into /v1/players/:id 400s immediately, before any upstream lookup:
# 400 - 12345 is some other system's id, not a Big Balls UUID
curl "https://api.bigballsdata.com/v1/players/12345?sport=basketball" -H "Authorization: Bearer $KEY"
# 200 - search by name, then use the id THAT response returns
curl "https://api.bigballsdata.com/v1/players?name=lebron" -H "Authorization: Bearer $KEY"
curl "https://api.bigballsdata.com/v1/players/{id}?sport=basketball" -H "Authorization: Bearer $KEY"The real suggested_fix on this error names the exact mechanism: "This is often OUR OWN response: match lineups and WC2026 squads return a provider id next to the player's name, not our UUID. Use that name at /v1/players?name=<query> instead, then use the id it returns." If you got the bad id from a Big Balls response rather than a third party, this is why.
Bad value on /v1/stored/matches
Every filter on /v1/stored/matches is optional, so a 400 means a value is malformed, never a missing param. date is YYYY-MM-DD, status is one of scheduled, live, finished, or cancelled, and limit is 1 to 200.
# 400 - "upcoming" is not a valid status
curl "https://api.bigballsdata.com/v1/stored/matches?status=upcoming" -H "Authorization: Bearer $KEY"
# 200 - valid status + date
curl "https://api.bigballsdata.com/v1/stored/matches?status=finished&date=2026-06-08" -H "Authorization: Bearer $KEY"Retry strategy
5xxwithRetry?: Yes, exponential backoff starting at 1s, capped at 60s, with full jitter. Stop after 5 attempts.429, sleep exactlyRetry-Afterseconds before retrying. Never sooner.4xxother than429, do not retry. Fix the request first.- Idempotency keys are required on
POSTif you intend to retry, see webhooks.