TsTypeScript SDK
Typed calls for anywhere JavaScript runs.
One dependency-free package with types for every object in the contract. ESM and CommonJS builds, so it drops into a Node service, an edge function or a Bun script without a bundler dance.
- Package
- datasportsapi
- Runs on
- Node 22+ · Deno · Bun · browsers · edge
- Key
- DATASPORTSAPI_API_KEY
- Status
- Not published yet
- Install
- $ npm install datasportsapi
The install line goes live with the first release; nothing is on a public registry yet. Everything on this page describes the client as it will ship.
Install
A single package with nothing else pulled in. Use whichever package manager the project already has.
shell$ npm install datasportsapi # or $ pnpm add datasportsapi $ bun add datasportsapiAuthenticate
Create a key in your dashboard and keep it on the server. Leave apiKey out and the client reads DATASPORTSAPI_API_KEY; pass it in when it comes from a secret manager instead.
client.tsimport { DataSportsAPI } from "datasportsapi"; // Picks up DATASPORTSAPI_API_KEY from the environment export const client = new DataSportsAPI(); // …or hand it over yourself export const scoped = new DataSportsAPI({ apiKey: await secrets.get("sports-key") });First calls
Each endpoint is a method grouped the way the docs are. Path parameters go first, in the order the path lists them; query parameters follow as one object, using the API's own names. You get the { data, meta } envelope back, fully typed.
first-calls.ts// Everything in play, one sport const { data: live } = await client.livescores.list({ sport: "football" }); // A league table for one season const { data: table } = await client.leagues.standings(17, { season: 76986 }); // Both sides' lineups for a fixture const { data: lineups } = await client.fixtures.lineups(480112);Errors
Every HTTP status has its own error class, and each one carries the API's code, message and retryAfter. Decide what to do from the code; the message is for humans and can change.
errors.tsimport { DataSportsAPIError, NotFoundError, PermissionDeniedError, RateLimitError } from "datasportsapi"; try { const { data } = await client.fixtures.winProbability(480112); drawCurve(data); } catch (err) { if (err instanceof PermissionDeniedError && err.code === "endpoint_not_in_plan") { showUpgradeHint(); // Free and Starter keys } else if (err instanceof NotFoundError && err.code === "not_supported") { hidePanel(); // not a football fixture } else if (err instanceof RateLimitError && err.code === "quota_exceeded") { pauseFor(err.retryAfter); // seconds until the quota resets } else if (err instanceof DataSportsAPIError) { log(err.status, err.code, err.message); } else { throw err; } }TypeScript error type for each HTTP status, and the codes each status can carry Status Error class error.code 400 BadRequestError bad_request401 AuthenticationError unauthorized403 PermissionDeniedError endpoint_not_in_planhistory_not_in_planws_not_availableorigin_not_allowedip_not_allowed404 NotFoundError not_foundno_datanot_supported429 RateLimitError rate_limitedquota_exceeded500 InternalServerError internal_error503 ServiceUnavailableError upstream_unavailablePaging
Any endpoint that takes a page parameter also has a …Pages method: an async iterator that fetches the next page only when you ask for it and stops on the last one.
paging.tsfor await (const page of client.teams.fixturesPages(42, { type: "recent" }, { maxPages: 4 })) { console.log("page", page.meta.page); for (const fixture of page.data) archive(fixture); }WebSocket stream
Businessclient.stream() handles the connection end to end: it requests a single-use ticket, subscribes to your channels, reconnects when the link drops, replays the frames you missed and applies each delta to its local copy of the fixtures.
stream.tsconst stream = client.stream(["livescores", "fixture:480112"]); stream.on("delta", (frame) => { console.log(frame.id, frame.changes); // a partial Fixture, already merged }); stream.on("error", (err) => console.error(err.code)); // The merged state of a channel, current at any moment const inPlay = stream.fixtures("livescores"); stream.close();Channels, frames and replay rules are in the WebSocket guide. Without a Business key the stream answers 403 ws_not_available.
Options
Defaults suit most apps. When the API answers 429 rate_limited or 503, the client waits for the Retry-After it was given and tries again, up to twice by default. A 429 quota_exceeded is never retried: waiting a few seconds would not help.
options.tsconst client = new DataSportsAPI({ apiKey: process.env.DATASPORTSAPI_API_KEY, // the default baseUrl: "https://api.datasportsapi.com", // or DATASPORTSAPI_BASE_URL timeout: 30_000, // ms, per attempt maxRetries: 2, // rate_limited and 503 only });Base URL resolution: the option or flag first, then
DATASPORTSAPI_BASE_URL, thenhttps://api.datasportsapi.com.
From the endpoint pages
Every endpoint shows its TypeScript call.
- GET
/v2/livescoresconst { data, meta } = await client.livescores.list(); - GET
/v2/fixtures/{id}const { data, meta } = await client.fixtures.get(id); - GET
/v2/fixtures/{id}/lineupsconst { data, meta } = await client.fixtures.lineups(id); - GET
/v2/leagues/{id}/standingsconst { data, meta } = await client.leagues.standings(id, { season }); - GET
/v2/teams/{id}/fixturesconst { data, meta } = await client.teams.fixtures(id);
Other clients
Your key is one form away.
1,000 free requests a day during the beta. No card, no sales call.