Building with AI? Give your coding tool the whole reference in one file: https://soccer-data.co/llms.txt
SOCCER DATA Docs
Sign in Get API key
// Core concepts

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_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.