# SportsAPI — REST + WebSocket sports data API

> Real-time sports data for 20 sports: live scores, fixtures, lineups, statistics, standings, teams, players and leagues, as JSON with our own stable ids. A WebSocket pushes live changes.

Every success is JSON with a `{ data, meta? }` envelope; errors return a real HTTP status with `{ error: { code, message } }`. Every `id` is our own stable id. Every team, league and player carries an `image`: an absolute URL on https://cdn.sportsapi.app, or null when it has none. The full contract is in https://sportsapi.app/markdown/en/reference.md and in https://api.sportsapi.app/v2/openapi.json (OpenAPI 3.1, no key needed).

## Authentication

Base URL: https://api.sportsapi.app. Send your API key as `Authorization: Bearer <key>` on every /v2 request; keys are created in the dashboard (https://sportsapi.app/dashboard).
Keys are shown once at creation; revoking one stops it at once. All keys of an account share its rate limit and quota. Never ship a key in a public web page.

```bash
curl -H "Authorization: Bearer sk_live_…" "https://api.sportsapi.app/v2/livescores?sport=football"
```

## SDKs & CLI

Official clients for TypeScript / JavaScript, Python and Go, and the `sportsapi` command line. They use the same API key (constructor, or the `SPORTSAPI_API_KEY` environment variable; `sportsapi login` for the CLI), retry 429 rate_limited and 503 after Retry-After, page through paged endpoints and include a WebSocket client.

TypeScript:

```ts
$ npm install sportsapi

import { SportsAPI } from 'sportsapi';

const client = new SportsAPI({ apiKey: process.env.SPORTSAPI_API_KEY });
const { data } = await client.livescores.list({ sport: 'football' });

// WebSocket (Business): merged live state, reconnect + replay built in
const stream = client.stream(['livescores']);
stream.on('delta', (frame) => console.log(frame.id, frame.changes));
```

Python:

```python
$ pip install sportsapi

from sportsapi import SportsAPI

client = SportsAPI()  # reads SPORTSAPI_API_KEY
res = client.livescores.list(sport="football")

# WebSocket (Business); AsyncSportsAPI for asyncio
with client.stream(["livescores"]) as stream:
    for frame in stream:
        print(frame["type"])
```

Go:

```go
$ go get github.com/sportsapiapp/sportsapi-go

client := sportsapi.NewClient() // reads SPORTSAPI_API_KEY
res, err := client.Livescores.List(ctx, &sportsapi.LivescoresListParams{Sport: sportsapi.Ptr("football")})

// WebSocket (Business)
stream, err := client.Stream(ctx, []string{"livescores"}, nil)
for frame := range stream.Frames() { … }
```

CLI:

```bash
$ go install github.com/sportsapiapp/sportsapi-go/cmd/sportsapi@latest
$ sportsapi login
$ sportsapi livescores list --sport football -o table
$ sportsapi teams fixtures 42 --type recent --all -o csv
$ sportsapi watch livescores
```

## Rate limits & plans

Two limits per account: a quota (per UTC month on paid plans, per UTC day on Free; headers RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset; going over → 429 quota_exceeded) and a per-minute rate (429 rate_limited with Retry-After, not counted against the quota). Older data than the plan reaches answers 403 history_not_in_plan with `historyFrom`.

| Plan | Price | Quota | Requests / min | History | WebSocket | Premium endpoints |
| --- | --- | --- | --- | --- | --- | --- |
| Free | $0 / month | 1,000 / day | 10 | Last 7 days | no | no |
| Starter | $29 / month | 500,000 / month | 120 | Last 30 days | no | no |
| Pro | $99 / month | 5,000,000 / month | 600 | Last 12 months | no | yes |
| Business | $999 / month | 100,000,000 / month | 6,000 | Full history | Full + replay | yes |

## Errors

Branch on `error.code`, never on the message text.

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `bad_request` | invalid id, unknown sport, missing or invalid date, season, round or query, invalid type or page (the message names the allowed values) |
| 401 | `unauthorized` | missing, invalid or revoked API key |
| 403 | `ws_not_available` | stream ticket requested on a plan without WebSocket access |
| 403 | `origin_not_allowed` | a widget key used from a site its list does not include (or without an Origin header — widget keys are for browsers) |
| 403 | `ip_not_allowed` | the call came from an address outside your account’s IP allowlist (Business; set in the dashboard — an empty list allows every address); not counted against the quota |
| 403 | `endpoint_not_in_plan` | the endpoint is outside your key’s plan: the premium endpoints (win probability, AI commentary, match and player analytics — marked Pro + Business) on Free and Starter |
| 403 | `history_not_in_plan` | the fixture, race, day or season is older than your plan’s history (Free 7 days, Starter 30 days, Pro 12 months, Business no limit); historyFrom in the error is the first day you can read |
| 404 | `not_found` | no such resource (an id we never issued, an unknown ranking type, a session or season of another race or series, or a route that does not exist) |
| 404 | `no_data` | the ids are ours but the source has no such data: standings, team / player season statistics, the player-statistics explorer, a league’s or series’ seasons (since 2026-10-23; not_found before) |
| 404 | `not_supported` | win probability or AI commentary of a fixture that is not football; the player-statistics explorer of a league that is not football |
| 429 | `rate_limited` | per-minute rate exceeded — wait Retry-After seconds |
| 429 | `quota_exceeded` | request quota used up — per UTC month on Starter, Pro and Business (resets 00:00 UTC on the 1st), per UTC day on Free (resets 00:00 UTC); the message names the period and the reset time, Retry-After gives the seconds until the reset |
| 500 | `internal_error` | our fault; safe to retry (the message is always generic) |
| 503 | `upstream_unavailable` | data temporarily unavailable; retry after the Retry-After seconds (503 since 2026-10-27 — it was 502, which Cloudflare replaced with its own page, so the JSON never arrived) |

## Data freshness

How old a response can be while the data is changing, at worst, with the source healthy (the live state of a match is current to the second):

| Endpoint | How current |
| --- | --- |
| WebSocket, /v2/livescores, /v2/sports | Real time: pushed from the source’s live feed within seconds. Esports period scores (League of Legends kills per game), which the feed sends only every few minutes, within about 15 seconds while the fixture has a fixture:{id} subscriber. |
| Status, score, clock and winner of a fixture in play or just finished — wherever a Fixture appears | Seconds. Every response that embeds fixtures (fixture detail, livescores by sport, fixtures by date, team / player / round fixtures) is brought up to the live feed when it is served. |
| /v2/livescores?sport= | A match that ended leaves at once; a match that just started joins within about a minute. |
| /v2/fixtures/{id} (other fields) | About a minute while in play; final once finished. Before kick-off: a new start time or a postponement is picked up on the first request after the source announces it. |
| /v2/fixtures/{id}/lineups, statistics, events, play-by-play, best-players, momentum, shotmap, points, scorecard, average-positions, heatmap, form, streaks | About 30 seconds while in play (at most 90); settled once finished. On a fixture:{id} WebSocket channel (Business) events, statistics and lineups are pushed when they change: events about 20–45 seconds after a goal, red card or period change (when the source’s REST lists it), otherwise within about 30 seconds; statistics and lineups within about 30–60 seconds. |
| fixture:{id} WebSocket play by play, momentum, shot map (Business) | Play by play about 20 seconds after the source lists an entry (football, basketball); momentum and shot map about 30 seconds (football); a last push after full time. |
| /v2/fixtures/{id}/umpires, weather, games | About 30 seconds (at most 90) while the match is in play; before it, up to 13 minutes; once finished, up to 30 hours. |
| /v2/fixtures/{id}/h2h, /h2h/fixtures | Refreshed after the fixture ends; otherwise up to 30 hours. Status and score of the listed fixtures are brought up to the live feed when served. |
| /v2/fixtures/{id}/win-probability | Recomputed at most every 15 seconds while in play, from the events, shot map and momentum above (about 30 seconds old); final and kept once finished. The pre-match estimate is fixed at kick-off. |
| /v2/fixtures/{id}/commentary | An entry usually appears within a minute of the event (the events above are about 30 seconds old, then a few seconds to write it); written while someone follows the match — and always for the featured top-league matches shown on sportsapi.app/live — and never rewritten. |
| /v2/fixtures?date= (which fixtures a day holds) | A fixture moved to another day, postponed or ended is refreshed on the next request, on both days. Otherwise a few minutes for today and ahead (at most 13); hours for past days, never once every fixture is finished. |
| /v2/teams/{id}/fixtures | A match joins recent on the first request after kick-off; both lists refresh on the first request after one of the team’s matches ends, is moved or is postponed; otherwise up to 13 minutes (upcoming) or 2.5 hours (recent). |
| /v2/leagues/{id}/fixtures | Which fixtures a round lists: refreshed after one of them ends, is moved or is postponed; otherwise up to 13 minutes. |
| Start time and postponement of a fixture not yet started — wherever a Fixture appears | A new start time or a postponement we have seen (on the live feed or in any response from the source) is applied to every list holding the fixture when it is served. |
| /v2/players/{id}/fixtures, /seasons, /statistics | Refreshed after a match the player was in the lineup — or the squad — of ends (the request after the next one sees it; the source updates season statistics with a delay); the fixtures list also when such a match is moved or postponed. A new match in the fixtures list otherwise up to 13 minutes; seasons and statistics up to 6 hours. |
| /v2/referees/{id}, /v2/managers/{id}, /v2/teams/{id}/trophies, /v2/teams/{id}/career-statistics | Up to 6 hours, rarely up to 30. |
| /v2/referees/{id}/fixtures, /v2/managers/{id}/fixtures | Up to 13 minutes; the status, score and clock of a match in play are current to the second. |
| /v2/teams/{id}/position-history, the form column of /v2/leagues/{id}/standings | Refreshed on the first request after a match of the season ends; otherwise up to 2.5 hours. |
| /v2/leagues/{id}/standings | Refreshed on the first request after a match of the season ends, and again 2 minutes later (the source can lag the final whistle); otherwise up to 13 minutes. |
| /v2/leagues/{id}/cup-trees | Refreshed on the first request after a match of the season ends; otherwise up to 13 minutes. |
| /v2/leagues/{id}/top-players, top-teams, /v2/teams/{id}/statistics | Refreshed after a match of the season ends (the request after the next one sees it); the source itself updates these with a delay. |
| /v2/teams/{id}/squad, /v2/players/{id} (the current team) | Refreshed on the next request once a move shows anywhere we look: a lineup naming a player the squad lacks, a squad or profile we refetch, or a transfers list. The transfers lists of teams read in the last 7 days are checked once a day, so their squads and profiles follow a move within about a day. Otherwise up to 6 hours, rarely up to 30 — the source does not announce transfers. |
| /v2/rankings/{type}, /v2/categories, /v2/leagues (directory), /v2/series (directory), /v2/teams/{id}/leagues, /v2/leagues/{id}/player-statistics | Up to 6 hours, rarely up to 30. The player-statistics explorer is not refreshed after a match — the source updates season statistics with a delay anyway. |
| /v2/races, /v2/races/{id} | On a race:{id} WebSocket channel (Business, since 2026-10-24) changes are pushed: the race within about 60 seconds, a running session’s results within about 30. Over REST: while a session runs or starts within 15 minutes: about 30 seconds (at most 90); before: up to 13 minutes; once every session is over: up to 30 hours. |
| /v2/races/{id}/results | Follows the session it classifies (without session=, the race): while it runs or starts within 15 minutes, about 30 seconds (at most 90); finished: up to 13 minutes while rows still change, up to 30 hours once nothing changed for 6 hours; not started yet: up to 13 minutes. |
| /v2/series/{id}/standings, /v2/series/{id}/races | Up to 13 minutes while rows still change; up to 30 hours once nothing changed for 6 hours (a season’s race list: once every race is over). |
| Transfers, seasons, rounds, search | Up to 6 hours, rarely up to 30 (a key nobody asked for in a day is served once more while it refreshes); search up to 13 minutes. The source’s transfer lists are current to the day. |

## WebSocket (Business)

Mint a ticket with POST /v2/stream/ticket, connect to the returned url, then subscribe to channels. Details: https://sportsapi.app/markdown/en/docs/websocket/connect.md.

| Channel | Snapshot | Then |
| --- | --- | --- |
| `livescores` | Fixture[] — every in-progress fixture | fixture frames (id, sport, data: Fixture) when a fixture enters; deltas (id, sport, changes) for the fixtures you hold |
| `sport:{slug}` | Fixture[] — that sport’s in-progress fixtures | fixture frames when a fixture enters; deltas (id, sport, changes) for the fixtures you hold |
| `fixture:{id}` | Fixture, or null if unavailable (an id we never issued → error unknown_fixture, not counted toward maxChannels) | id, sport, changes — plus, while the fixture is in play: events frames (id, sport, events: the whole keyed list, changes: added / updated / removed) when its match events change, statistics frames (id, sport, statistics) and lineups frames (id, sport, lineups) when those change — fetched from REST (the live feed carries none of them): events ~20 s and ~45 s after each score / red-card / status change and every 30 s, statistics and lineups every 30 s, pushed only on a change. Since 2026-10-24 also playByPlay frames (id, sport, start, total, entries: the list from index start on — keep your first start entries and append; start beyond what you hold → reread REST; football and basketball, polled every 20 s), momentum frames (id, sport, momentum: the whole object) and shotmap frames (id, sport, shots: the whole list) — football, every 30 s; pushed only on a change, to at most 20 subscribed fixtures at once (the most-subscribed). For football also winProbability frames (id, sport, model, point) when an outcome moves ≥ 1 pp, and commentary frames (id, sport, entry) for each new AI commentary entry |
| `league:{id}` | Fixture[] — that league’s in-progress fixtures (our league id; unknown id → error unknown_league) | fixture frames when a fixture enters; deltas (id, sport, changes) for the fixtures you hold |
| `race:{id}` | RaceSnapshot — { race: Race, session, results: RaceResult[] }: the race with its sessions, the session running (else the last finished) and its classification (an id that is no race → error unknown_race) | race frames (id, sport, data: Race) when a session starts / ends or the race changes, and raceResults frames (id, sport, session, results) when the running session’s classification changes — polled from REST (race sports have no live feed): the race every 60 s while a session runs or starts within 15 min, results every 30 s, pushed only on a change |

Client frames:

| Frame | Meaning |
| --- | --- |
| `{ "type": "subscribe", "channel": "…", "lastSeq"?: n }` | Start a channel; answered with a snapshot. On the Full grade, passing the last seq you hold replays the missed frames instead, then a { "type": "subscribed", "channel", "seq", "ts", "replayed" } frame (the server keeps 5 minutes of frames per channel; older gaps get a fresh snapshot). A subscribe to a channel you are already on is answered with subscribed (replayed 0). |
| `{ "type": "unsubscribe", "channel": "…" }` | Stop a channel; answered with { "type": "unsubscribed", "channel", "ts" } — or error not_subscribed when you are not on it. |
| `{ "type": "resync", "channel": "…" }` | Request a fresh snapshot of a channel you are subscribed to (error not_subscribed otherwise). |
| `{ "type": "ping" }` | Application-level ping; answered with { "type": "pong", "ts": <ms> }. |

Close codes:

| Code | Meaning |
| --- | --- |
| 1008 | invalid, expired or already used ticket — mint a new one; on an open connection: the API key was revoked, the account suspended, or the plan no longer includes this WebSocket grade (reason “plan changed; reconnect” = reconnect with a new ticket) |
| 1013 | your plan’s concurrent-connection limit is reached — close another connection, retry later |

## Endpoints

### Catalog

- [GET /v2/sports](https://sportsapi.app/markdown/en/docs/catalog/sports-list.md): Every supported sport with its current in-progress match count (matches the source never closed — no change for 45 min and started longer ago than the sport can last — are not counted).

### Live scores

In-progress fixtures. For a live layer, prefer the WebSocket — it pushes the same objects plus deltas.

- [GET /v2/livescores](https://sportsapi.app/markdown/en/docs/livescores/list.md): All in-progress fixtures across every sport, or one sport’s in-play board with ?sport= (each listed match as the live index holds it — the same state as the no-sport form and the WebSocket).

### Fixtures

Fixtures by date, a single match, and its detail sub-resources.

- [GET /v2/fixtures](https://sportsapi.app/markdown/en/docs/fixtures/list.md): Fixtures kicking off on one UTC calendar day — for one league, or for a whole sport ten leagues per page (in the order the leagues are ranked).
- [GET /v2/fixtures/{id}](https://sportsapi.app/markdown/en/docs/fixtures/get.md): Full fixture: teams, score, status, match clock, league, season, round, venue, winner, the referee (with career cards per game), both managers (homeManager / awayManager), previousLegFixtureId (our i…
- [GET /v2/fixtures/{id}/lineups](https://sportsapi.app/markdown/en/docs/fixtures/lineups.md): Lineups, formations and missing players, with each player’s match rating and statistics.
- [GET /v2/fixtures/{id}/statistics](https://sportsapi.app/markdown/en/docs/fixtures/statistics.md): Match statistics grouped by period and category.
- [GET /v2/fixtures/{id}/events](https://sportsapi.app/markdown/en/docs/fixtures/events.md): Incidents — goals, cards, substitutions, period markers, added-time boards — oldest first (chronological).
- [GET /v2/fixtures/{id}/play-by-play](https://sportsapi.app/markdown/en/docs/fixtures/play-by-play.md): The source’s text commentary, one entry per action, oldest first: football (cards, shots, corners, substitutions, … by minute) and basketball (every basket, free throw, rebound, foul, turnover — with…
- [GET /v2/fixtures/{id}/h2h](https://sportsapi.app/markdown/en/docs/fixtures/h2h.md): Head-to-head record between the two teams (from this fixture’s home/away perspective), as the source counts it: their recent meetings — usually about the last ten — and the fixture itself once it is …
- [GET /v2/fixtures/{id}/h2h/fixtures](https://sportsapi.app/markdown/en/docs/fixtures/h2h-fixtures.md): The two teams’ meetings as fixtures, oldest first by kick-off, without the fixture asked about — scheduled meetings included.
- [GET /v2/fixtures/{id}/best-players](https://sportsapi.app/markdown/en/docs/fixtures/best-players.md): Player of the match and the best-rated players of each side.
- [GET /v2/fixtures/{id}/momentum](https://sportsapi.app/markdown/en/docs/fixtures/momentum.md): Attack-momentum graph: one point per minute, positive = home pressure, negative = away.
- [GET /v2/fixtures/{id}/win-probability](https://sportsapi.app/markdown/en/docs/fixtures/win-probability.md): Our own win-probability model for a football fixture: the pre-match estimate, the current home / draw / away probabilities and the curve — a point at kick-off, every minute, and right after each goal…
- [GET /v2/fixtures/{id}/commentary](https://sportsapi.app/markdown/en/docs/fixtures/commentary.md): AI match commentary for a football fixture, oldest first (by seq; the newest 100): one short entry per goal, red card, missed penalty, VAR decision, period start / end, big chance and sustained momen…
- [GET /v2/fixtures/{id}/shotmap](https://sportsapi.app/markdown/en/docs/fixtures/shotmap.md): Every shot with its outcome and expected goals, oldest first: football (expectedGoals / expectedGoalsOnTarget, situation, body part, pitch and goal-mouth coordinates), ice hockey (technique, strength…
- [GET /v2/fixtures/{id}/form](https://sportsapi.app/markdown/en/docs/fixtures/form.md): Both sides’ form going into the fixture: last results (oldest first), league position, the table figure as numbers (football: points; W-L / W-D-L sports: wins, draws, losses) and average rating.
- [GET /v2/fixtures/{id}/streaks](https://sportsapi.app/markdown/en/docs/fixtures/streaks.md): Team streaks before the fixture — each side’s recent run (wins, unbeaten, over 2.5 goals, …) and streaks over their meetings.
- [GET /v2/fixtures/{id}/heatmap/{playerId}](https://sportsapi.app/markdown/en/docs/fixtures/heatmap.md): Where one player was active during the fixture: every position sample, in pitch percentages.
- [GET /v2/fixtures/{id}/average-positions](https://sportsapi.app/markdown/en/docs/fixtures/average-positions.md): Each player’s average position in the fixture, per side.
- [GET /v2/fixtures/{id}/points](https://sportsapi.app/markdown/en/docs/fixtures/points.md): The match point by point.
- [GET /v2/fixtures/{id}/scorecard](https://sportsapi.app/markdown/en/docs/fixtures/scorecard.md): Cricket: the full scorecard, innings by innings — totals, extras, the batting card (state, dismissal, bowler, fielder, runs, balls, fours, sixes, strike rate), the bowling card (overs, maidens, runs,…
- [GET /v2/fixtures/{id}/umpires](https://sportsapi.app/markdown/en/docs/fixtures/umpires.md): Baseball: the umpire crew — name, position (homePlate, firstBase, secondBase, thirdBase, leftField, rightField, other) and years of experience.
- [GET /v2/fixtures/{id}/weather](https://sportsapi.app/markdown/en/docs/fixtures/weather.md): Baseball: the weather at the ballpark — the observation, then the forecast — in metric units (°C, %, km/h).
- [GET /v2/fixtures/{id}/games](https://sportsapi.app/markdown/en/docs/fixtures/games.md): Esports: the series game by game — status, map, start and duration, winner, and per side the score, players (role, champion / hero, kills, deaths, assists, then the title’s own figures — League of Le…

### Teams

- [GET /v2/teams/{id}](https://sportsapi.app/markdown/en/docs/teams/get.md): Team profile: country, sport, venue, colours, logo, gender, national team or club, kind (team / individual / pair), manager — and, for an individual competitor the source profiles (tennis, MMA), pers…
- [GET /v2/teams/{id}/fixtures](https://sportsapi.app/markdown/en/docs/teams/fixtures.md): A page of the team’s recent or upcoming fixtures.
- [GET /v2/teams/{id}/squad](https://sportsapi.app/markdown/en/docs/teams/squad.md): The current squad: every player’s profile plus contract end and current injury.
- [GET /v2/teams/{id}/transfers](https://sportsapi.app/markdown/en/docs/teams/transfers.md): The team’s moves in (arrivals) and out (departures), about the last year, newest first: transfers with their fee, loans and loan ends, and the US leagues’ roster moves — signings, trades, the draft, …
- [GET /v2/teams/{id}/statistics](https://sportsapi.app/markdown/en/docs/teams/statistics.md): The team’s season statistics in one league: totals and averages (goals scored and conceded, shots, possession, rating, clean sheets, …).
- [GET /v2/teams/{id}/leagues](https://sportsapi.app/markdown/en/docs/teams/leagues.md): Every competition the team plays in or has played in — domestic league, cups, European competitions, friendly tournaments.
- [GET /v2/teams/{id}/trophies](https://sportsapi.app/markdown/en/docs/teams/trophies.md): The team’s trophy cabinet: every competition it has won, the competition type (domestic league, domestic cup, international cup, other), whether it is a major title, how often, and the seasons (newes…
- [GET /v2/teams/{id}/position-history](https://sportsapi.app/markdown/en/docs/teams/position-history.md): The team’s league position at the end of each week of one season, week 1 first, with the fixtures it played that week.
- [GET /v2/teams/{id}/career-statistics](https://sportsapi.app/markdown/en/docs/teams/career-statistics.md): An MMA fighter’s career statistics: strikes landed and absorbed per round by target (head, body, legs) and position (distance, clinch, ground), accuracy and defence shares, takedowns and submissions …

### Players

- [GET /v2/players/{id}](https://sportsapi.app/markdown/en/docs/players/get.md): Player profile: position, detailed positions (goalkeeper, centreBack, leftWinger, striker, …; [] when the source lists none), team, nationality, physicals, market value, contract end, current injury …
- [GET /v2/players/{id}/transfers](https://sportsapi.app/markdown/en/docs/players/transfers.md): The player’s career moves, newest first — the same Transfer objects as a team’s list.
- [GET /v2/players/{id}/seasons](https://sportsapi.app/markdown/en/docs/players/seasons.md): The leagues and seasons the player has statistics for, with the statistics types available in each (overall, home, away, regularSeason, playoffs).
- [GET /v2/players/{id}/fixtures](https://sportsapi.app/markdown/en/docs/players/fixtures.md): A page of the player’s most recent fixtures (the teams he played for), newest first on every page (since 2026-10-24).
- [GET /v2/players/{id}/statistics](https://sportsapi.app/markdown/en/docs/players/statistics.md): The player’s season statistics in one league: totals and averages (goals, minutes, averageRating, expected goals, passes, duels, …).

### Leagues

- [GET /v2/leagues/{id}](https://sportsapi.app/markdown/en/docs/leagues/get.md): League profile: level in its country’s pyramid (tier), gender, the title holder and how often it won, the record title holders, the divisions above and below, and the current season’s first and last …
- [GET /v2/leagues/{id}/seasons](https://sportsapi.app/markdown/en/docs/leagues/seasons.md): Available seasons, newest first.
- [GET /v2/leagues/{id}/seasons/{seasonId}](https://sportsapi.app/markdown/en/docs/leagues/season.md): Season facts: totals so far (goals, home / away wins, draws, cards, …) and the newcomers — relegated from above, promoted from below.
- [GET /v2/leagues/{id}/standings](https://sportsapi.app/markdown/en/docs/leagues/standings.md): Standings for one season.
- [GET /v2/leagues/{id}/rounds](https://sportsapi.app/markdown/en/docs/leagues/rounds.md): The rounds of a season and the current one.
- [GET /v2/leagues/{id}/fixtures](https://sportsapi.app/markdown/en/docs/leagues/fixtures.md): The fixtures of one round of a season.
- [GET /v2/leagues/{id}/top-players](https://sportsapi.app/markdown/en/docs/leagues/top-players.md): Season leaderboards: the top players per statistic (rating, goals, xG, assists, tackles, saves, … — up to 50 each at the source), paged: limit players per leaderboard (default 10), or one leaderboard…
- [GET /v2/leagues/{id}/top-teams](https://sportsapi.app/markdown/en/docs/leagues/top-teams.md): Season team leaderboards: the top teams per statistic (average rating, goals scored, clean sheets, possession, …), paged like top-players.
- [GET /v2/leagues/{id}/player-statistics](https://sportsapi.app/markdown/en/docs/leagues/player-statistics.md): The season’s player-statistics explorer (football): every player of the league, 20 a page, sorted by any statistic, with a group of columns — summary, attack, defense, passing, goalkeeping or other —…
- [GET /v2/leagues/{id}/cup-trees](https://sportsapi.app/markdown/en/docs/leagues/cup-trees.md): The season’s knockout brackets — a cup’s knockout stage, playoffs, a tennis draw: rounds → ties → the two participants (team, seed, winner, score) → our fixture ids.

### Discovery

Find the ids to start from: world rankings, and every league of a sport by country or category.

- [GET /v2/rankings](https://sportsapi.app/markdown/en/docs/discovery/rankings-list.md): The rankings we serve: FIFA (men), UEFA country and club coefficients, World Rugby (men), ATP and WTA singles (official and live) and the UFC divisions.
- [GET /v2/rankings/{type}](https://sportsapi.app/markdown/en/docs/discovery/rankings-get.md): One ranking table, best first: position and previous position, points and previous points, best position, tournaments played.
- [GET /v2/categories](https://sportsapi.app/markdown/en/docs/discovery/categories-list.md): A sport’s categories: the source’s grouping of leagues — countries, regions (Europe, International), tours (ATP), esports games.
- [GET /v2/leagues](https://sportsapi.app/markdown/en/docs/discovery/leagues-list.md): The league directory: every league of one category, or of one sport in one country.

### Race sports

Motorsport and cycling (kind races in /v2/sports). A series is the league of race sports (Formula 1, MotoGP; in cycling the whole men’s or women’s calendar), a race is a Grand Prix weekend or a cycling race, a session one timed part of it (practice, qualifying, sprint, the race; a cycling stage). Drivers, riders and their teams are TeamRefs, so /v2/teams/{id} serves them. Times are milliseconds, distances metres. Not on the WebSocket yet — poll /v2/races/{id} and /results while a session runs.

- [GET /v2/races](https://sportsapi.app/markdown/en/docs/races/list.md): Races with a session starting on one UTC day, each with all its sessions.
- [GET /v2/races/{id}](https://sportsapi.app/markdown/en/docs/races/get.md): One race: series, season, round, status, days, host country, circuit (motorsport), laps and distance, category (cycling), winner — and its sessions in start order, each with type, status, start time,…
- [GET /v2/races/{id}/results](https://sportsapi.app/markdown/en/docs/races/results.md): The race’s classification — motorsport: the main race session’s (the last session of type race: grid, pit stops, tyres, best laps, points; meta.session names it); a cycling stage race: the general cl…
- [GET /v2/series](https://sportsapi.app/markdown/en/docs/races/series-list.md): The race sports’ directory: the series of one category — Nascar → NASCAR Cup Series, Truck Series, Xfinity; Formula 1 → Formula 1; cycling’s International → Cycling Men, Cycling Women.
- [GET /v2/series/{id}](https://sportsapi.app/markdown/en/docs/races/series-get.md): A racing series: name, slug, sport.
- [GET /v2/series/{id}/seasons](https://sportsapi.app/markdown/en/docs/races/series-seasons.md): The series’ seasons, newest first.
- [GET /v2/series/{id}/races](https://sportsapi.app/markdown/en/docs/races/series-races.md): The races of one season of the series (without sessions — /v2/races/{id} has them).
- [GET /v2/series/{id}/standings](https://sportsapi.app/markdown/en/docs/races/series-standings.md): The championship table of a season: the drivers’ / riders’ (type=competitors, default) or the teams’ (type=teams) — points, wins, podiums, pole positions, fastest laps, races started and races with p…

### Referees & managers

Referee ids are on Fixture.referee, manager ids on TeamDetail.manager and Fixture.homeManager / awayManager; both are also in /v2/search.

- [GET /v2/referees/{id}](https://sportsapi.app/markdown/en/docs/people/referees-get.md): A referee: country, sport, career totals (matches, yellow, red and second-yellow cards) and the figures per competition (appearances, cards, penalties awarded).
- [GET /v2/referees/{id}/fixtures](https://sportsapi.app/markdown/en/docs/people/referees-fixtures.md): A page of the matches the referee officiated (recent) or is appointed to (upcoming) — the same list rule as team fixtures.
- [GET /v2/managers/{id}](https://sportsapi.app/markdown/en/docs/people/managers-get.md): A manager / head coach: country, date of birth, preferred formation, current team, career record (games, wins, draws, losses, goals, points) and every job with its own record, newest first.
- [GET /v2/managers/{id}/fixtures](https://sportsapi.app/markdown/en/docs/people/managers-fixtures.md): A page of the manager’s recent or upcoming matches — the same list rule as team fixtures.

### Search

- [GET /v2/search](https://sportsapi.app/markdown/en/docs/search/query.md): Unified search across teams (and individual competitors), players, leagues, managers, referees, racing series, races, fixtures and venues — each hit’s type says which endpoint its id belongs to (/v2/…

### Widgets

Pro and Business. Live scores, a match centre and league tables you drop into any web page with two lines of HTML — no backend of yours, no SportsAPI branding. Create a widget key in the dashboard (Widgets tab) and list the sites that may use it.

- [GET https://sportsapi.app/widgets/v1.js](https://sportsapi.app/markdown/en/docs/widgets/script.md): The widget script.

### Bulk exports

Business plan. One league season as files: start an export, poll it, download. Built in the background from the same data as the endpoints above; at most 20 exports per account per UTC day, kept 7 days.

- [POST /v2/exports](https://sportsapi.app/markdown/en/docs/exports/create.md): Start an export of one league season.
- [GET /v2/exports/{id}](https://sportsapi.app/markdown/en/docs/exports/get.md): One export.
- [GET /v2/exports](https://sportsapi.app/markdown/en/docs/exports/list.md): Your last 20 exports, newest first, with fresh download links for the ready ones.

### Webhooks

Business plan. Register https:// endpoints in the dashboard (Webhooks tab) and we POST each event to them as JSON — no socket to keep open. Filters: event types, sports, league ids and fixture ids (all optional, combined with AND).

- [POST {your endpoint URL}](https://sportsapi.app/markdown/en/docs/webhooks/event.md): One request per event and endpoint.

### WebSocket

Real-time score and status deltas. Mint a single-use ticket over REST, then connect — your API key never goes into a WebSocket URL. Business plan (Full grade) only — up to five concurrent connections per account.

- [POST /v2/stream/ticket](https://sportsapi.app/markdown/en/docs/websocket/stream-ticket.md): Mint a single-use connection ticket, valid for 30 seconds.
- [WS wss://api.sportsapi.app/?ticket={ticket}](https://sportsapi.app/markdown/en/docs/websocket/connect.md): Connect to the url from the ticket.

## Changelog

### 2026-11-04 — Manager and referee photos

Managers and referees carry a photo on our CDN, like players. One new field, always present and nullable, on every manager and referee object; nothing else changes, nor does the WebSocket protocol.

| What | Before | After |
| --- | --- | --- |
| ManagerRef.image (Fixture.homeManager / awayManager, TeamDetail.manager, H2H managerDuel) | absent | string \| null — the photo on cdn.sportsapi.app; null when the source has none or it is not uploaded yet |
| Referee.image (Fixture.referee) | absent | string \| null — as ManagerRef.image |
| ManagerProfile.image, RefereeProfile.image (/v2/managers/{id}, /v2/referees/{id}) | absent | string \| null — as ManagerRef.image |
| SearchResult.image on manager and referee hits | always null | the photo, as ManagerRef.image |

### 2026-11-04 — Search finds fixtures and venues; player hits carry their team (breaking)

GET /v2/search returns two new hit types — fixture and venue — and three new fields on every hit. SearchType is a closed enum, so a client that rejects unknown values must accept the two new ones; the existing fields and hit types do not change.

| What | Before | After |
| --- | --- | --- |
| SearchResult.type (SearchType) | team \| player \| league \| manager \| referee \| series \| race | the same + fixture (a match: /v2/fixtures/{id}) + venue (a stadium / arena; no endpoint of its own — the id is the one Fixture.venue and TeamDetail.venue carry) |
| SearchResult.team | absent | TeamRef \| null — a player hit’s current team; null on every other type and for a player without one |
| SearchResult.fixture | absent | SearchFixture \| null — { home, away (TeamRef \| null), league (LeagueRef \| null), startTime, status (StatusType \| null) } on a fixture hit; null on every other type |
| SearchResult.venue | absent | Venue \| null — { id, name, city, country, capacity } on a venue hit; null on every other type |
| SearchResult.sport | a sport slug or null | the same; always null on a venue hit (a venue is not tied to one sport) |

### 2026-11-02 — AI commentary names SportsAPI AI (three values change)

The AI commentary is now named after us, not the language model provider behind it. Only these values change; the shapes, the text and the WebSocket protocol do not.

| What | Before | After |
| --- | --- | --- |
| CommentaryEntry.model (REST and the commentary WebSocket frame) | the underlying provider model id | "sportsapi-ai" |
| GET /v2/fixtures/{id}/commentary meta.generatedBy | the provider name | "sportsapi-ai" |

### 2026-11-01 — v2 baseline

The starting point: the v2 contract as documented on this page — REST, WebSocket protocol 4, webhooks, season exports and widgets. Earlier pre-launch changes are not listed; every change from here on is, field by field.

HTML: https://sportsapi.app/docs
