Data model
The things in the API, their ids, their main fields and how they link together.
Everything in Soccer Data hangs off a few core things: competitions, their seasons, the matches in those seasons, and the teams and players who take part. This page explains each one, its id and the fields you'll use most.
IDs
Every id is a string made of two parts:
- a word prefix that says what kind of thing it is, such as
team_; - 12 lowercase letters and digits that say which one.
So Arsenal is team_f0ibhxdw7hs5 and the Premier League is competition_705uv6jm8myn.
Ids are permanent. A team, player or match keeps the same id for good, so you can store ids in your database, put them in config files, and use them in URLs of your own app. Look an id up once and reuse it.
| Prefix | What it names | Example |
|---|---|---|
competition_ |
A league, cup or tournament | competition_705uv6jm8myn (Premier League) |
season_ |
One season of a competition | season_hr1ie7yrsdmt (Premier League 26/27) |
team_ |
A club or national team | team_f0ibhxdw7hs5 (Arsenal men) |
player_ |
A player | player_ul3vc6om97u5 (Bukayo Saka) |
match_ |
A single match | match_yoeml0e6ae37 (Arsenal v Leeds United) |
shot_ |
One shot in a match's shot map | shot_as8bbsa4f1mq |
manager_ |
A manager, as home_manager or away_manager on a match |
manager_1dtehmda8rsp |
referee_ |
A match referee | referee_dxvcpogubown |
A few rules make ids easy to work with:
- Treat them as opaque strings. Don't parse the characters after the prefix or turn them into numbers; they carry no meaning.
- Copy them exactly. Use the ids you get in our responses. An id we don't recognise, whether it has a typo or comes from another data source, returns
404 NOT_FOUND. - Find them by name when you need to. Find any ID shows every way to look them up.
How things connect
Competition
└── Season
└── Match ── home_team / away_team ── Team ── Player
- A competition has many seasons. The competition's
current_season_idpoints to the one running now. - A season has many matches. Each match carries its
competition_idandseason_id. - A match links to its two teams (
home_team,away_team) and managers (home_manager,away_manager). - A player links to the team they play for now through
current_team, which includes theirjersey_number. - Teams and players also show up in season stats, lineups and match stats.
Groups in a tournament aren't separate things with their own ids. A match or table row simply carries a group_label, such as "A".
Competition
A league, a cup or a tournament.
| Field | Meaning |
|---|---|
id, name |
The id and the name. |
type |
league, cup or tournament. |
country, country_code |
Where it's played. Both are null for international competitions. |
subdivision_code |
The UK nation, such as GB-ENG. null outside the UK. |
confederation |
For international competitions, such as UEFA or FIFA. null for domestic ones. |
current_season_id |
The season running now. Returned when you fetch a single competition. |
Season
One edition of a competition.
| Field | Meaning |
|---|---|
id, name |
For example "Premier League 26/27". |
year |
A label for display, such as 26/27 or 2026. |
start_year, end_year |
Numbers you can sort by. |
is_current |
true for the latest season. |
Team
A club side or a national team.
| Field | Meaning |
|---|---|
id, name, short_name |
The id and names. |
country |
The team's country. |
is_mens_team |
true, false, or null when unknown. Clubs often have a men's and a women's team with the same name. |
primary_competition |
The id and name of the competition the team mainly plays in, or null. Useful to tell a first team from a youth side. |
Player
| Field | Meaning |
|---|---|
id, name, first_name, last_name |
The id and names. |
position |
G, D, M or F, or an empty string when unknown. |
date_of_birth, age |
YYYY-MM-DD and whole years. null when unknown. |
nationality |
The country they represent. |
height_cm |
Height, or null when not recorded. |
current_team |
id, name and jersey_number at their current team. |
Match
| Field | Meaning |
|---|---|
id, competition_id, season_id |
The match and where it belongs. |
status |
scheduled, live, finished, postponed or cancelled. |
utc_date |
Kickoff, in ISO 8601 and always UTC. |
matchday |
The league round. Often null in cups and knockouts. |
stage_name |
The round in a knockout, such as quarter_final or final. null for regular league matches. |
group_label |
The tournament group, or null. |
home_team, away_team |
Each with an id and name. |
score |
home and away are normal-time goals. For the overall result, read winner, and after_extra_time and penalty_shootout when present. |
is_neutral |
true when neither side is at home. null means we don't know, not false. |
live |
The match clock while it's in play, on the match list. null otherwise. |
Availability flags
Some data only exists for some competitions or matches. These flags let you check before you spend a request:
| Flag | On | Means |
|---|---|---|
has_team_stats |
Competition | Team stats exist in this competition. |
has_player_stats |
Competition | Player stats exist in this competition. |
xg_available |
Competition, match | Some xG data exists. |
shotmap_available |
Match | The shot map has shots with xG values. |
xg_quality |
Match | full for xG on every shot, otherwise none. |
odds_available |
Competition, match | Pre-match odds exist. |
live_odds_available |
Competition, match | In-play odds exist right now. |
Flags on a competition describe the competition as a whole, not a particular season. For season-by-season answers, use the coverage endpoints.
Dates, times and nulls
- Kickoff times are UTC. Convert them to your users' time zone when you display them.
- Date filters take
YYYY-MM-DD. To get matches in your own time zone's day, addutc_offset, such as-05:00. In URLs, write a plus sign as%2B(utc_offset=%2B02:00), or letURLSearchParamsdo it. nullalways means "unknown" or "doesn't exist yet". It never stands for zero orfalse. For example,score.homeisnullbefore kickoff,liveisnullwhen a match isn't in play, andmatchdayis usuallynullin a cup (checkstage_name).
Common requests
| You want | Ask for |
|---|---|
| A league table | /competitions/{competition_id}/seasons/{season_id}/standings |
| A team's season | /teams/{team_id}/stats?season_id=... |
| A player's season | /players/{player_id}/stats?season_id=..., optionally with competition_id |
| A squad | /teams/{team_id}/players |
| A women's team | /teams?search=Arsenal&is_mens_team=false |
| One nation's competitions | /competitions?country_code=GB-ENG; GB covers all UK nations |
| One day's matches | /matches?date_from=...&date_to=... with the same date twice |
Common mistakes
- Converting ids to numbers or trimming the prefix. Keep them as the strings you received.
- Mixing up teams with the same name. Check
is_mens_teamandprimary_competition. - Reading
nullas zero. It means unknown. - Assuming
is_neutral: nullmeans a home game. It means we don't know. - Calling an optional endpoint without checking its flag. Missing data returns
404or an emptydatalist.
# Data model
The things in the API, their ids, their main fields and how they link together.
Everything in Soccer Data hangs off a few core things: competitions, their seasons, the matches in those seasons, and the teams and players who take part. This page explains each one, its id and the fields you'll use most.
## IDs
Every id is a string made of two parts:
- a **word prefix** that says what kind of thing it is, such as `team_`;
- **12 lowercase letters and digits** that say which one.
So Arsenal is `team_f0ibhxdw7hs5` and the Premier League is `competition_705uv6jm8myn`.
Ids are permanent. A team, player or match keeps the same id for good, so you can store ids in your database, put them in config files, and use them in URLs of your own app. Look an id up once and reuse it.
| Prefix | What it names | Example |
|---|---|---|
| `competition_` | A league, cup or tournament | `competition_705uv6jm8myn` (Premier League) |
| `season_` | One season of a competition | `season_hr1ie7yrsdmt` (Premier League 26/27) |
| `team_` | A club or national team | `team_f0ibhxdw7hs5` (Arsenal men) |
| `player_` | A player | `player_ul3vc6om97u5` (Bukayo Saka) |
| `match_` | A single match | `match_yoeml0e6ae37` (Arsenal v Leeds United) |
| `shot_` | One shot in a match's shot map | `shot_as8bbsa4f1mq` |
| `manager_` | A manager, as `home_manager` or `away_manager` on a match | `manager_1dtehmda8rsp` |
| `referee_` | A match referee | `referee_dxvcpogubown` |
A few rules make ids easy to work with:
- **Treat them as opaque strings.** Don't parse the characters after the prefix or turn them into numbers; they carry no meaning.
- **Copy them exactly.** Use the ids you get in our responses. An id we don't recognise, whether it has a typo or comes from another data source, returns `404 NOT_FOUND`.
- **Find them by name when you need to.** [Find any ID](/docs/guides/finding-ids) shows every way to look them up.
## How things connect
```text
Competition
└── Season
└── Match ── home_team / away_team ── Team ── Player
```
- A **competition** has many **seasons**. The competition's `current_season_id` points to the one running now.
- A **season** has many **matches**. Each match carries its `competition_id` and `season_id`.
- A **match** links to its two teams (`home_team`, `away_team`) and managers (`home_manager`, `away_manager`).
- A **player** links to the team they play for now through `current_team`, which includes their `jersey_number`.
- Teams and players also show up in season stats, lineups and match stats.
Groups in a tournament aren't separate things with their own ids. A match or table row simply carries a `group_label`, such as `"A"`.
## Competition
A league, a cup or a tournament.
| Field | Meaning |
|---|---|
| `id`, `name` | The id and the name. |
| `type` | `league`, `cup` or `tournament`. |
| `country`, `country_code` | Where it's played. Both are `null` for international competitions. |
| `subdivision_code` | The UK nation, such as `GB-ENG`. `null` outside the UK. |
| `confederation` | For international competitions, such as `UEFA` or `FIFA`. `null` for domestic ones. |
| `current_season_id` | The season running now. Returned when you fetch a single competition. |
## Season
One edition of a competition.
| Field | Meaning |
|---|---|
| `id`, `name` | For example "Premier League 26/27". |
| `year` | A label for display, such as `26/27` or `2026`. |
| `start_year`, `end_year` | Numbers you can sort by. |
| `is_current` | `true` for the latest season. |
## Team
A club side or a national team.
| Field | Meaning |
|---|---|
| `id`, `name`, `short_name` | The id and names. |
| `country` | The team's country. |
| `is_mens_team` | `true`, `false`, or `null` when unknown. Clubs often have a men's and a women's team with the same name. |
| `primary_competition` | The `id` and `name` of the competition the team mainly plays in, or `null`. Useful to tell a first team from a youth side. |
## Player
| Field | Meaning |
|---|---|
| `id`, `name`, `first_name`, `last_name` | The id and names. |
| `position` | `G`, `D`, `M` or `F`, or an empty string when unknown. |
| `date_of_birth`, `age` | `YYYY-MM-DD` and whole years. `null` when unknown. |
| `nationality` | The country they represent. |
| `height_cm` | Height, or `null` when not recorded. |
| `current_team` | `id`, `name` and `jersey_number` at their current team. |
## Match
| Field | Meaning |
|---|---|
| `id`, `competition_id`, `season_id` | The match and where it belongs. |
| `status` | `scheduled`, `live`, `finished`, `postponed` or `cancelled`. |
| `utc_date` | Kickoff, in ISO 8601 and always UTC. |
| `matchday` | The league round. Often `null` in cups and knockouts. |
| `stage_name` | The round in a knockout, such as `quarter_final` or `final`. `null` for regular league matches. |
| `group_label` | The tournament group, or `null`. |
| `home_team`, `away_team` | Each with an `id` and `name`. |
| `score` | `home` and `away` are normal-time goals. For the overall result, read `winner`, and `after_extra_time` and `penalty_shootout` when present. |
| `is_neutral` | `true` when neither side is at home. `null` means we don't know, not `false`. |
| `live` | The match clock while it's in play, on the match list. `null` otherwise. |
## Availability flags
Some data only exists for some competitions or matches. These flags let you check before you spend a request:
| Flag | On | Means |
|---|---|---|
| `has_team_stats` | Competition | Team stats exist in this competition. |
| `has_player_stats` | Competition | Player stats exist in this competition. |
| `xg_available` | Competition, match | Some xG data exists. |
| `shotmap_available` | Match | The shot map has shots with xG values. |
| `xg_quality` | Match | `full` for xG on every shot, otherwise `none`. |
| `odds_available` | Competition, match | Pre-match odds exist. |
| `live_odds_available` | Competition, match | In-play odds exist right now. |
Flags on a competition describe the competition as a whole, not a particular season. For season-by-season answers, use the [coverage endpoints](/docs/concepts/coverage).
## Dates, times and nulls
- Kickoff times are UTC. Convert them to your users' time zone when you display them.
- Date filters take `YYYY-MM-DD`. To get matches in your own time zone's day, add `utc_offset`, such as `-05:00`. In URLs, write a plus sign as `%2B` (`utc_offset=%2B02:00`), or let `URLSearchParams` do it.
- `null` always means "unknown" or "doesn't exist yet". It never stands for zero or `false`. For example, `score.home` is `null` before kickoff, `live` is `null` when a match isn't in play, and `matchday` is usually `null` in a cup (check `stage_name`).
## Common requests
| You want | Ask for |
|---|---|
| A league table | [`/competitions/{competition_id}/seasons/{season_id}/standings`](/docs/reference/competitions/standings) |
| A team's season | [`/teams/{team_id}/stats?season_id=...`](/docs/reference/teams/stats) |
| A player's season | [`/players/{player_id}/stats?season_id=...`](/docs/reference/players/stats), optionally with `competition_id` |
| A squad | [`/teams/{team_id}/players`](/docs/reference/teams/players) |
| A women's team | [`/teams?search=Arsenal&is_mens_team=false`](/docs/reference/teams/list) |
| One nation's competitions | [`/competitions?country_code=GB-ENG`](/docs/reference/competitions/list); `GB` covers all UK nations |
| One day's matches | [`/matches?date_from=...&date_to=...`](/docs/reference/matches/list) with the same date twice |
## Common mistakes
- **Converting ids to numbers or trimming the prefix.** Keep them as the strings you received.
- **Mixing up teams with the same name.** Check `is_mens_team` and `primary_competition`.
- **Reading `null` as zero.** It means unknown.
- **Assuming `is_neutral: null` means a home game.** It means we don't know.
- **Calling an optional endpoint without checking its flag.** Missing data returns `404` or an empty `data` list.