Growing Discord community — direct access to the developer, live coverage & picks. Join the Discord Join now →
Matches Leagues Predictions The Edge Money News Stats Coverage
Docs / Football API free

Competition structure#

A domestic league is a flat list of matchdays. A cup is not: it has qualifying rounds, then a group or league phase, then knockouts — and round numbers restart in each of them.

In the 2026/27 Champions League, round_number 5 means two different things: matchday 5 of the league phase (18 matches in November) and the Round of 16 (16 matches in March). Same number, different competition stage.

That is why every event carries a stage.

The three main stages#

Most of a competition's matches sit in one big stage where every team plays numbered matchdays. It has three shapes:

stage When Example
regular-season League with no knockouts Premier League, Bundesliga
league-phase Single table, no groups, feeds a knockout Champions League, Europa League
group-stage Split into groups, feeds a knockout World Cup, Copa Libertadores

Everything else — qualifying and knockout ties — uses its own slug, built from the round's name: qualification-round-1, playoff-round, round-of-32, round-of-16, quarterfinals, semifinals, final.

Every match tells you where it sits#

{
  "round_number": 5,
  "round_name": "",
  "stage": "league-phase",
  "stage_name": "League phase",
  "round_label": "League phase · Matchday 5"
}

round_label is the one to display. round_number and round_name are the raw upstream values and are kept for compatibility — but on their own they are ambiguous, because round_name is empty for every main-stage match and round_number repeats across stages.

Filtering by stage#

GET /api/v2/events/?league_id=7&season_id=1112&stage=league-phase

Returns the 144 Champions League league-phase fixtures — the 8 rounds, 18 matches each. Add round to narrow to one matchday:

GET /api/v2/events/?league_id=7&season_id=1112&stage=league-phase&round=5

round only works together with stage. On its own it would mix a matchday with a knockout tie, which is the exact confusion this field exists to remove.

An unknown stage returns 400 listing the stages that season actually has, rather than an empty list that looks like "no matches yet".

Discovering the stages of a season#

You do not have to guess the slugs. Every season lists its own structure, in the order it is played:

GET /api/v2/leagues/7/seasons/
{
  "id": 1112,
  "name": "UEFA Champions League 26",
  "stages": [
    {"stage": "qualification-round-1", "stage_name": "Qualification Round 1",
     "matches": 28, "rounds": 1, "start_date": "2026-07-07", "end_date": "2026-07-15"},
    {"stage": "playoff-round", "stage_name": "Playoff round",
     "matches": 14, "rounds": 1, "start_date": "2026-08-18", "end_date": "2026-08-26"},
    {"stage": "league-phase", "stage_name": "League phase",
     "matches": 144, "rounds": 8, "start_date": "2026-09-08", "end_date": "2027-01-27"}
  ]
}

rounds is how many matchdays that stage has — 8 for a Champions League league phase, 3 for a World Cup group stage, 1 for a final. Stages appear as they are scheduled, so a knockout stage shows up once its draw is published.

The Champions League has no group stage#

Since 2024/25 there are no groups: 36 teams, one table, each playing 8 different opponents. If you are looking for group-stage in a recent Champions League season you will not find it — the fixtures you want are league-phase, and group_name is null on all of them.

The World Cup and Copa Libertadores still have real groups, and there group_name is populated ("Group A") and round_label reads Group A · Matchday 3.

Building a fixtures widget#

Group by stage first, then by round_number within it:

from collections import defaultdict

rounds = defaultdict(list)
for match in events:
    rounds[(match["stage"], match["round_number"])].append(match)

Grouping by round_number alone merges matchday 5 with the Round of 16.

View this page as Markdown · Found a mistake? Tell us