Getting started · Errors
Error codes you can switch on
A failed request comes back with a real HTTP status and a small JSON body. The status tells you the family; the code tells you precisely what happened, and it is the only part your logic should depend on.
The error body
HTTP/1.1 404 Not Found
Content-Type: application/json
{ "error": { "code": "not_found", "message": "resource not found" } }- code
- Stable, lowercase, snake_case. Codes are part of the contract: they don't change meaning, and new ones are announced in the changelog.
- message
- A readable sentence for logs and support tickets. Its wording can improve over time, so never parse it.
- extras
- Some codes add a field. history_not_in_plan carries historyFrom, the first day your plan can read.
- Retry-After
- A response header on 429 and 503 answers: the seconds to wait before trying again.
Every code
Grouped by status. Each entry has its own anchor, so you can link straight to it from a log line or an alert.
What happened
Part of the request could not be accepted: an id that is not a number, a sport slug we do not serve, a missing or malformed date, season, round or search query, or a type or page outside the allowed values.
What to do
The message lists the values the parameter accepts. Correct the call; sending it again unchanged will get the same answer.
What happened
A stream ticket was requested by an account whose plan has no WebSocket access.
What to do
The stream is part of Business. Until then, poll REST at the pace the freshness guide suggests.
What happened
A publishable widget key was used from a site missing from its list — or without any Origin header, since these keys are made for browsers.
What to do
Add the exact origin, or a https://*.example.com wildcard for subdomains, to the key in the dashboard's Widgets tab.
What happened
The call came from an IP address outside your account's allowlist (Business).
What to do
Add the address in the dashboard, or empty the list to allow every address again.
What happened
The endpoint sits above your key's plan — usually a premium endpoint (win probability, AI commentary, odds, match and player analytics) called with a Free or Starter key. A widget key calling something widgets do not use gets it too.
What to do
Upgrade to Pro or Business, or drop the call. Premium endpoints wear a Pro + Business badge; the limits guide lists them all.
What happened
You asked for a fixture, race, day or season from before the start of your plan's history window (Free 7 days, Starter 30 days, Pro 6 months, Business unlimited).
What to do
The error includes historyFrom, the earliest day you can read. Clamp your date range to it, or move to a plan with a deeper window.
What happened
There is nothing at that address. Typical causes: an id that did not come from us, a ranking type that does not exist, a session or season that belongs to another race or series, or a path outside the API.
What to do
Make sure the id came from our API — ids from other providers will not match — and that the path is spelled as documented.
What happened
The ids are valid, but the source has nothing for this combination: standings, team or player season statistics, the player-statistics explorer, or the seasons of a league or series.
What to do
Show an empty state. Your request is fine; another season or competition may well have data.
What happened
The endpoint exists, just not for this sport: win probability or AI commentary for a fixture that is not football, or the player-statistics explorer for a non-football league.
What to do
Check the fixture's or league's sport before calling. These endpoints are football-only.
What happened
More requests than your plan allows inside the current one-minute window.
What to do
Wait the seconds in Retry-After — it points at the next minute — and carry on. Nothing was charged.
What happened
The request quota for the period is spent: the UTC month on Starter, Pro and Business, the UTC day on Free.
What to do
Nothing will succeed before the reset — 00:00 UTC on the 1st for monthly plans, 00:00 UTC each day on Free. The message names the period and reset time; Retry-After counts the seconds. If it keeps happening, it is time for a bigger plan.
What happened
Something broke on our side. The message is intentionally generic.
What to do
Safe to retry with exponential backoff. If it persists, tell us the time and path at contact@noventra.dev.
Retry rules
Only three codes are worth retrying automatically. Everything else needs a different request, a different key or a different plan.
Retry after the header
rate_limited · upstream_unavailable
Sleep for Retry-After seconds, then repeat the same request.
Retry with backoff
internal_error
Double the wait on each attempt and give up after a handful.
Stop
quota_exceeded · every 4xx else
A repeat gets the same answer. Alert a human or change the request.
const API = "https://api.datasportsapi.com";
const headers = { Authorization: `Bearer ${process.env.DATASPORTSAPI_API_KEY}` };
const RETRYABLE = new Set(["rate_limited", "upstream_unavailable", "internal_error"]);
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
export async function request<T>(path: string, tries = 4): Promise<T | null> {
for (let attempt = 1; ; attempt++) {
const res = await fetch(API + path, { headers });
if (res.ok) return (await res.json()).data as T;
const { error } = await res.json();
if (error.code === "no_data") return null; // valid ids, nothing published
if (RETRYABLE.has(error.code) && attempt < tries) {
const retryAfter = Number(res.headers.get("Retry-After"));
await sleep(retryAfter > 0 ? retryAfter * 1000 : 500 * 2 ** attempt);
continue;
}
throw Object.assign(new Error(error.message), { status: res.status, code: error.code });
}
}Codes outside the common set
The table above covers what any /v2 call can answer. A few places have codes of their own:
- Season exports answer
429 export_limitwhen an account has used up its daily allowance of exports — see exports. - The WebSocket reports problems as
errorframes on an open connection (for exampleunknown_fixtureortoo_many_channels) and uses close codes 1008 and 1013 — see WebSocket errors. - Webhooks flip the direction: your endpoint's status code decides whether we retry — see webhook retries.