Skip to content
datasportsAPI

Get startedErrors

Documentation

/

Type to filter the docs by title or path.

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.

14 REST codes7 statuses
01

The error body

A failed request
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.
02

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.

  1. 400

    bad_request

    Fix, don't retry

    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.

  2. 401

    unauthorized

    Not chargedFix, don't retry

    What happened

    No API key arrived, the key is not one we issued, or it has been revoked.

    What to do

    Check the header reads Authorization: Bearer followed by the full key, and that the key is still active in your dashboard.

  3. 403

    ws_not_available

    Fix, don't retry

    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.

  4. 403

    origin_not_allowed

    Fix, don't retry

    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.

  5. 403

    ip_not_allowed

    Not chargedFix, don't retry

    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.

  6. 403

    endpoint_not_in_plan

    Not chargedFix, don't retry

    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.

  7. 403

    history_not_in_plan

    Fix, don't retry

    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.

  8. 404

    not_found

    Fix, don't retry

    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.

  9. 404

    no_data

    Fix, don't retry

    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.

  10. 404

    not_supported

    Fix, don't retry

    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.

  11. 429

    rate_limited

    Not chargedRetry after Retry-After

    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.

  12. 429

    quota_exceeded

    Wait for the reset

    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.

  13. 500

    internal_error

    Retry with backoff

    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.

  14. 503

    upstream_unavailable

    Retry after Retry-After

    What happened

    The data is temporarily unavailable from upstream.

    What to do

    Retry once the Retry-After seconds have passed.

03

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 });
  }
}
04

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_limit when an account has used up its daily allowance of exports — see exports.
  • The WebSocket reports problems as error frames on an open connection (for example unknown_fixture or too_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.