Coverage
Find out which competitions and seasons have fixtures, stats, xG, lineups, tables and odds before you build on them.
Coverage differs from one competition to the next, and from one season to the next. A top league might have xG for every match in recent seasons and only results further back; a smaller competition might have fixtures but no player stats. The coverage endpoints tell you exactly what exists, so you can check before you build a feature or promise data to your users.
Every example sends your key in the X-Api-Key header. Keep that key on your server.
The coverage endpoints
| Endpoint | What it answers |
|---|---|
/coverage/summary |
Headline totals: how many competitions, seasons and finished matches we cover. |
/coverage/leagues |
Every competition, with the kinds of data it has and the range of seasons. Filter with data_type and search. |
/coverage/leagues/{competition_id} |
One competition, season by season: match counts and how many matches have each kind of data. |
/coverage/leagues is paged with 100 results per page by default and up to 200. See Pagination.
Data types
| Key | What it covers | Where you get the data |
|---|---|---|
fixtures |
The matches themselves. | /matches |
team_stats |
Team stats for each match. | /matches/{match_id}/stats |
xg |
Expected goals. | expected_goals in /matches/{match_id}/stats |
player_stats |
Player stats for each match. | /matches/{match_id}/player-stats |
lineups |
Starting line-ups and formations. | /matches/{match_id}/lineups |
standings |
Whether a league table exists. | /competitions/{competition_id}/seasons/{season_id}/standings |
closing_odds |
The last prices before kickoff. | last_seen in /matches/{match_id}/odds |
opening_odds |
The first prices we saw. | opening in /matches/{match_id}/odds |
odds |
The same as closing_odds, kept for older code. |
Each type says whether it's available. Most also give covered_events, the number of finished matches that have it, and coverage_pct, the share of finished matches that have it, to one decimal place. We may add new keys over time, so skip any you don't recognise.
Find competitions that have what you need
Filter by data type, and by name if you like. This finds competitions with "Premier" in the name that have xG:
curl "https://api.soccer-data.co/v1/coverage/leagues?data_type=xg&search=Premier" \
-H "X-Api-Key: YOUR_API_KEY"
const res = await fetch("https://api.soccer-data.co/v1/coverage/leagues?data_type=xg&search=Premier", {
headers: { "X-Api-Key": process.env.SOCCER_DATA_KEY },
});
const { data } = await res.json();
for (const league of data) {
console.log(league.id, league.name, league.seasons.first?.year, "to", league.seasons.last?.year);
}
Each competition in the list looks like this (trimmed):
{
"id": "competition_705uv6jm8myn",
"name": "Premier League",
"country": "England",
"seasons": {
"count": 13,
"first": { "id": "season_yf8xfclqrgom", "year": "14/15" },
"last": { "id": "season_hr1ie7yrsdmt", "year": "26/27" }
},
"data_types": {
"fixtures": { "available": true },
"xg": { "available": true },
"opening_odds": { "available": true },
"closing_odds": { "available": true },
"player_stats": { "available": true }
},
"latest_season": {
"id": "season_hr1ie7yrsdmt",
"year": "26/27",
"status": "in_progress",
"finished_events": 50,
"total_events": 100
},
"total_finished_events": 4610
}
seasons.first and seasons.last give you a rough range. For the exact list of seasons, use the per-competition endpoint below.
Check season by season
curl "https://api.soccer-data.co/v1/coverage/leagues/competition_705uv6jm8myn" \
-H "X-Api-Key: YOUR_API_KEY"
const res = await fetch("https://api.soccer-data.co/v1/coverage/leagues/competition_705uv6jm8myn", {
headers: { "X-Api-Key": process.env.SOCCER_DATA_KEY },
});
const { data } = await res.json();
for (const season of data.seasons) {
if (season.status === "not_loaded") continue;
console.log(season.year, "xG:", season.data_types.xg?.coverage_pct ?? 0, "%");
}
Each season looks like this (trimmed):
{
"id": "season_98l95mxugqs9",
"name": "Premier League 25/26",
"year": "25/26",
"status": "complete",
"events": { "finished": 380, "total": 380 },
"data_types": {
"xg": { "available": true, "covered_events": 380, "coverage_pct": 100 },
"opening_odds": { "available": false, "covered_events": 0, "coverage_pct": 0 },
"closing_odds": { "available": true, "covered_events": 380, "coverage_pct": 100 },
"player_stats": { "available": true, "covered_events": 380, "coverage_pct": 100 },
"standings": { "available": true }
}
}
standings only says whether a table exists, so it has no counts.
Season status
| Status | Means |
|---|---|
complete |
Every loaded match has been played. |
in_progress |
Some matches are still to come. |
not_loaded |
We know the season exists, but have no matches for it. Leave it out of your coverage figures. |
The season ids here are the same ones you use with /matches, standings and the stats endpoints, so you can go straight from a coverage check to the data.
Quick checks on a competition
For a yes-or-no answer about a whole competition, fetch it with /competitions/{competition_id} and read its flags:
curl "https://api.soccer-data.co/v1/competitions/competition_705uv6jm8myn" \
-H "X-Api-Key: YOUR_API_KEY"
The flags are has_team_stats, has_player_stats, xg_available, odds_available and live_odds_available. They say whether the data exists anywhere in the competition, not which seasons have it. For that, use the coverage endpoints above.
How far back the data goes
It depends on the competition and the kind of data. Some examples:
| Competition | Matches from |
|---|---|
| Premier League | 2014/15 |
| LaLiga, Bundesliga, Serie A, Ligue 1, UEFA Champions League | 2020/21 |
| MLS | 2020 |
Even within one competition, data types start at different points. In the Premier League, fixtures, lineups, team stats, player stats and tables go back to 2014/15, closing odds to 2015/16, xG to 2022/23, and opening odds to 2026/27.
Odds history
- Up to the 2026/27 season, most matches have closing prices only, mostly from Bet365.
- From 2026/27, matches can also have opening prices, from Bet365, Paddy Power, BetMGM UK, Pinnacle and Betfair Exchange.
Check opening_odds and closing_odds for each season you need. Odds has more.
Ask your AI assistant
If you build with an AI coding tool, a prompt like this gets you a coverage report in one go:
Using the Soccer Data API (reference: https://soccer-data.co/llms.txt), call
GET /coverage/leagues/competition_ozwhqjc3z9ic from my server and print a table of
LaLiga seasons with coverage_pct for xg, player_stats and closing_odds.
Skip seasons whose status is not_loaded.
Good to know
- Only finished matches count towards
covered_events,coverage_pctandfinished_events. - In a season that's still running,
totalcan be lower than the full number of matches, because later fixtures haven't been added yet. - Coverage figures are refreshed every few hours, so a match that has just finished may take a while to show up in them.
- The per-competition endpoint doesn't always list seasons in date order. Sort by
yearyourself if order matters.
Common mistakes
- Assuming a fixed number of years. Check each competition and each data type.
- Counting
not_loadedseasons. Skip them. - Relying on competition flags for a particular season. They describe the whole competition.
- Expecting deep odds history. Before 2026/27 it's mostly closing prices from one bookmaker.
# Coverage
Find out which competitions and seasons have fixtures, stats, xG, lineups, tables and odds before you build on them.
Coverage differs from one competition to the next, and from one season to the next. A top league might have xG for every match in recent seasons and only results further back; a smaller competition might have fixtures but no player stats. The coverage endpoints tell you exactly what exists, so you can check before you build a feature or promise data to your users.
Every example sends your key in the `X-Api-Key` header. Keep that key on your server.
## The coverage endpoints
| Endpoint | What it answers |
|---|---|
| [`/coverage/summary`](/docs/reference/coverage/summary) | Headline totals: how many competitions, seasons and finished matches we cover. |
| [`/coverage/leagues`](/docs/reference/coverage/leagues) | Every competition, with the kinds of data it has and the range of seasons. Filter with `data_type` and `search`. |
| [`/coverage/leagues/{competition_id}`](/docs/reference/coverage/league) | One competition, season by season: match counts and how many matches have each kind of data. |
`/coverage/leagues` is paged with 100 results per page by default and up to 200. See [Pagination](/docs/concepts/pagination).
## Data types
| Key | What it covers | Where you get the data |
|---|---|---|
| `fixtures` | The matches themselves. | [`/matches`](/docs/reference/matches/list) |
| `team_stats` | Team stats for each match. | [`/matches/{match_id}/stats`](/docs/reference/matches/stats) |
| `xg` | Expected goals. | `expected_goals` in [`/matches/{match_id}/stats`](/docs/reference/matches/stats) |
| `player_stats` | Player stats for each match. | [`/matches/{match_id}/player-stats`](/docs/reference/matches/player-stats) |
| `lineups` | Starting line-ups and formations. | [`/matches/{match_id}/lineups`](/docs/reference/matches/lineups) |
| `standings` | Whether a league table exists. | [`/competitions/{competition_id}/seasons/{season_id}/standings`](/docs/reference/competitions/standings) |
| `closing_odds` | The last prices before kickoff. | `last_seen` in [`/matches/{match_id}/odds`](/docs/reference/odds/prematch) |
| `opening_odds` | The first prices we saw. | `opening` in [`/matches/{match_id}/odds`](/docs/reference/odds/prematch) |
| `odds` | The same as `closing_odds`, kept for older code. | |
Each type says whether it's `available`. Most also give `covered_events`, the number of finished matches that have it, and `coverage_pct`, the share of finished matches that have it, to one decimal place. We may add new keys over time, so skip any you don't recognise.
## Find competitions that have what you need
Filter by data type, and by name if you like. This finds competitions with "Premier" in the name that have xG:
```bash
curl "https://api.soccer-data.co/v1/coverage/leagues?data_type=xg&search=Premier" \
-H "X-Api-Key: YOUR_API_KEY"
```
```javascript
const res = await fetch("https://api.soccer-data.co/v1/coverage/leagues?data_type=xg&search=Premier", {
headers: { "X-Api-Key": process.env.SOCCER_DATA_KEY },
});
const { data } = await res.json();
for (const league of data) {
console.log(league.id, league.name, league.seasons.first?.year, "to", league.seasons.last?.year);
}
```
Each competition in the list looks like this (trimmed):
```json
{
"id": "competition_705uv6jm8myn",
"name": "Premier League",
"country": "England",
"seasons": {
"count": 13,
"first": { "id": "season_yf8xfclqrgom", "year": "14/15" },
"last": { "id": "season_hr1ie7yrsdmt", "year": "26/27" }
},
"data_types": {
"fixtures": { "available": true },
"xg": { "available": true },
"opening_odds": { "available": true },
"closing_odds": { "available": true },
"player_stats": { "available": true }
},
"latest_season": {
"id": "season_hr1ie7yrsdmt",
"year": "26/27",
"status": "in_progress",
"finished_events": 50,
"total_events": 100
},
"total_finished_events": 4610
}
```
`seasons.first` and `seasons.last` give you a rough range. For the exact list of seasons, use the per-competition endpoint below.
## Check season by season
```bash
curl "https://api.soccer-data.co/v1/coverage/leagues/competition_705uv6jm8myn" \
-H "X-Api-Key: YOUR_API_KEY"
```
```javascript
const res = await fetch("https://api.soccer-data.co/v1/coverage/leagues/competition_705uv6jm8myn", {
headers: { "X-Api-Key": process.env.SOCCER_DATA_KEY },
});
const { data } = await res.json();
for (const season of data.seasons) {
if (season.status === "not_loaded") continue;
console.log(season.year, "xG:", season.data_types.xg?.coverage_pct ?? 0, "%");
}
```
Each season looks like this (trimmed):
```json
{
"id": "season_98l95mxugqs9",
"name": "Premier League 25/26",
"year": "25/26",
"status": "complete",
"events": { "finished": 380, "total": 380 },
"data_types": {
"xg": { "available": true, "covered_events": 380, "coverage_pct": 100 },
"opening_odds": { "available": false, "covered_events": 0, "coverage_pct": 0 },
"closing_odds": { "available": true, "covered_events": 380, "coverage_pct": 100 },
"player_stats": { "available": true, "covered_events": 380, "coverage_pct": 100 },
"standings": { "available": true }
}
}
```
`standings` only says whether a table exists, so it has no counts.
### Season status
| Status | Means |
|---|---|
| `complete` | Every loaded match has been played. |
| `in_progress` | Some matches are still to come. |
| `not_loaded` | We know the season exists, but have no matches for it. Leave it out of your coverage figures. |
The season ids here are the same ones you use with `/matches`, standings and the stats endpoints, so you can go straight from a coverage check to the data.
## Quick checks on a competition
For a yes-or-no answer about a whole competition, fetch it with [`/competitions/{competition_id}`](/docs/reference/competitions/show) and read its flags:
```bash
curl "https://api.soccer-data.co/v1/competitions/competition_705uv6jm8myn" \
-H "X-Api-Key: YOUR_API_KEY"
```
The flags are `has_team_stats`, `has_player_stats`, `xg_available`, `odds_available` and `live_odds_available`. They say whether the data exists anywhere in the competition, not which seasons have it. For that, use the coverage endpoints above.
## How far back the data goes
It depends on the competition and the kind of data. Some examples:
| Competition | Matches from |
|---|---|
| Premier League | 2014/15 |
| LaLiga, Bundesliga, Serie A, Ligue 1, UEFA Champions League | 2020/21 |
| MLS | 2020 |
Even within one competition, data types start at different points. In the Premier League, fixtures, lineups, team stats, player stats and tables go back to 2014/15, closing odds to 2015/16, xG to 2022/23, and opening odds to 2026/27.
### Odds history
- Up to the 2026/27 season, most matches have closing prices only, mostly from Bet365.
- From 2026/27, matches can also have opening prices, from Bet365, Paddy Power, BetMGM UK, Pinnacle and Betfair Exchange.
Check `opening_odds` and `closing_odds` for each season you need. [Odds](/docs/guides/odds) has more.
## Ask your AI assistant
If you build with an AI coding tool, a prompt like this gets you a coverage report in one go:
```text
Using the Soccer Data API (reference: https://soccer-data.co/llms.txt), call
GET /coverage/leagues/competition_ozwhqjc3z9ic from my server and print a table of
LaLiga seasons with coverage_pct for xg, player_stats and closing_odds.
Skip seasons whose status is not_loaded.
```
## Good to know
- Only finished matches count towards `covered_events`, `coverage_pct` and `finished_events`.
- In a season that's still running, `total` can be lower than the full number of matches, because later fixtures haven't been added yet.
- Coverage figures are refreshed every few hours, so a match that has just finished may take a while to show up in them.
- The per-competition endpoint doesn't always list seasons in date order. Sort by `year` yourself if order matters.
## Common mistakes
- **Assuming a fixed number of years.** Check each competition and each data type.
- **Counting `not_loaded` seasons.** Skip them.
- **Relying on competition flags for a particular season.** They describe the whole competition.
- **Expecting deep odds history.** Before 2026/27 it's mostly closing prices from one bookmaker.