Football API 101: Integrating World Cup qualifying data
Contents

Understanding how qualifying competitions are structured

Before touching a single endpoint, it helps to understand the data hierarchy Sportmonks uses for tournaments like World Cup qualifying:

Football API 101_ Integrating World Cup qualifying data-asset

This is different from a simple domestic league where you have one stage, and everything lives in clean matchweeks. World Cup qualifying, especially UEFA, has distinct stages with their own standings and rules. A Group Stage produces a standings table. A Play-offs stage does not. You need to know which is which before requesting standings, or you’ll get empty arrays back and wonder what went wrong.

The six confederations for the 2026 World Cup qualify through their own separate leagues in Sportmonks:

 

Finding your league IDs: Use the ID Finder in your Sportmonks dashboard, or call GET /v3/football/leagues/search/World Cup Qualifying to retrieve the league IDs for each confederation. We’ll reference them as {UEFA_LEAGUE_ID}, {CONMEBOL_LEAGUE_ID}, etc. throughout this guide; swap these with your verified values before going to production.

Step 1: Find the active qualifying season

Every league has an active season. For the 2026 World Cup cycle, you want is_current: true. Use the currentSeason include on the leagues endpoint to get the season ID in a single call:

const API_TOKEN = 'YOUR_API_TOKEN';
const BASE_URL = 'https://api.sportmonks.com/v3/football';

async function getQualifyingSeason(leagueId) {
 const url = `${BASE_URL}/leagues/${leagueId}?api_token=${API_TOKEN}&include=currentSeason`;
 const res = await fetch(url);
 const { data } = await res.json();

 const season = data.currentSeason;
 console.log(`Season: ${season.name}, ID: ${season.id}, Active: ${season.is_current}`);
 return season;
}

// Example: UEFA World Cup Qualifying
getQualifyingSeason('{UEFA_LEAGUE_ID}');

Example response (trimmed):

{
 "id": "{UEFA_WCQ_SEASON_ID}",
 "name": "2024/2025",
 "is_current": true,
 "starting_at": "2024-09-05",
 "ending_at": "2025-11-18",
 "finished": false
}

Save that season.id, you’ll use it in nearly every subsequent call.

Step 2: Discover the stages

This is the step most developers skip, and it causes problems later. World Cup qualifying seasons have multiple stages, each with its own rules. Pull them all before building anything else:

async function getSeasonStages(seasonId) {
 const url = `${BASE_URL}/stages?api_token=${API_TOKEN}&filters=stageSeasons:${seasonId}`;
 const res = await fetch(url);
 const { data } = await res.json();

 data.forEach(stage => {
   console.log(`Stage: "${stage.name}" | ID: ${stage.id} | Finished: ${stage.finished} | Current: ${stage.is_current}`);
 });

 return data;
}

For UEFA qualifying, you’d typically get back something like:

Stage: "Group Stage"     | ID: {GROUP_STAGE_ID}     | Finished: true  | Current: false

Stage: "Play-offs"       | ID: {PLAYOFFS_STAGE_ID}  | Finished: false | Current: true

This tells you immediately which stage to query for group standings (the Group Stage) and which to query for fixture results (the Play-offs). Standings only make sense on table-based stages; if you query standings against a knockout stage ID, you’ll get an empty result.

Step 3: Pull group stage standings

Once you have the group stage stage_id for the season you care about, use the standings by season id endpoint and filter by that stage. The Sportmonks Standings endpoint supports filtering by stage IDs using dynamic filters and also supports includes, such as participant details, to add extra fields like full breakdowns.

async function getGroupStageStandings(seasonId, stageId) {
 const url = [
   `${BASE_URL}/standings/seasons/${seasonId}`,
   `?api_token=${API_TOKEN}`,
   // include team info and detailed standing metrics
   `&include=participant;details`,
   // filter to only return standings for the specific stage
   `&filters=standingStages:${stageId}`
 ].join('');

 const res = await fetch(url);
 const { data } = await res.json();

 // Group rows by group_id (for multi-group stages)
 const groups = {};
 data.forEach(row => {
   const groupKey = row.group_id ?? 'ungrouped';
   if (!groups[groupKey]) groups[groupKey] = [];

   // Build a richer object with key fields
   groups[groupKey].push({
     position: row.position,
     teamName: row.participant?.name,
     points: row.points,
     played: row.overall?.played ?? null,
     won: row.overall?.won ?? null,
     draw: row.overall?.draw ?? null,
     lost: row.overall?.lost ?? null,
     goalsFor: row.overall?.goals_scored ?? null,
     goalsAgainst: row.overall?.goals_against ?? null,
     result: row.result,
     details: row.details // any standing details included
   });
 });

 return groups;
}

The result field on each standing row is particularly useful for qualifying contexts: “up” means the team is in a qualifying position, “down” means they are not, and “equal” means they’re on the boundary. You can use this directly to colour-code qualification zones in your UI without having to hardcode position thresholds yourself.

Step 4: Fetch qualifying fixtures

Once you have your stage IDs, pull fixtures filtered to that stage. The participants include both team names, and the venue include gives you the match location:

async function getFixturesByStage(seasonId, stageId) {
 const url = [
   `${BASE_URL}/fixtures`,
   `?api_token=${API_TOKEN}`,
   `&include=participants;venue;scores`,
   `&filters=fixtureSeasons:${seasonId};fixtureStages:${stageId}`
 ].join('');

 const res = await fetch(url);
 const { data } = await res.json();

 return data.map(fixture => ({
   id: fixture.id,
   name: fixture.name,
   kickoff: fixture.starting_at,
   venue: fixture.venue?.name ?? 'TBC',
   result: fixture.result_info,
   scores: fixture.scores,
 }));
}

Heads up on pagination: The fixtures endpoint defaults to 25 results per page. For a full qualifying campaign (UEFA alone has over 200 group-stage fixtures), make sure you handle pagination using the has_more flag in the response meta’s pagination object. Pass &per_page=50&page=2 etc. until has_more is false.

Step 5: Get match-level statistics

For any individual fixture, you can pull detailed match statistics (team stats for that match) using the fixture ID. Sportmonks returns all match statistics for both teams when you include statistics on a fixture request. You can optionally filter those statistics by specific statistic types (like shots, corners, possession, etc) using the dynamic filter syntax.

async function getFixtureStats(fixtureId) {
 const url = [
   `${BASE_URL}/fixtures/${fixtureId}`,
   `?api_token=${API_TOKEN}`,
   // include team statistics for this match
   `&include=statistics`,
   // filter only specific stats types (if desired)
   `&filters=fixtureStatisticTypes:42,49,51,52,56,84`
 ].join('');

 const res = await fetch(url);
 const { data } = await res.json();

 // 'data.statistics.data' contains team statistics for this fixture
 return data;
}
Key details

– Use the GET /fixtures/{fixtureId} endpoint with include=statistics to return match-level team statistics.
– The statistics include returns statistics for both teams in the fixture. Each statistic entry includes a type_id representing what the stat is.
– You can apply a filter such as fixtureStatisticTypes:{id1,id2,…} to return only the statistics for specific types you care about.
– The type IDs come from the general Types endpoint (GET /v3/core/types?api_token=YOUR_TOKEN) and represent specific stats (shots on target, possession, corners, fouls, etc.). It’s recommended to fetch and store these once rather than include the type object on every request.

Filtering specific stat types

To keep your response focused, add a filter using the dynamic filter format:

&filters=fixtureStatisticTypes:42,49,51,52,56,84

Replace the IDs with the statistic types you want. You can find valid statistic type IDs from the Types endpoint.

Step 6: Handle the playoff stages

Qualifying playoffs are knockout rounds, not group tables. They have aggregate_id values (for two-legged ties) and no standings. Here’s how to pull playoff fixtures and check which leg each match is:

async function getPlayoffFixtures(seasonId, stageId) {
 const url = [
   `${BASE_URL}/fixtures`,
   `?api_token=${API_TOKEN}`,
   `&include=participants;scores;aggregate`,
   `&filters=fixtureSeasons:${seasonId};fixtureStages:${stageId}`
 ].join('');

 const res = await fetch(url);
 const { data } = await res.json();

 return data.map(fixture => ({
   id: fixture.id,
   name: fixture.name,
   leg: fixture.leg,               // "1/2" or "2/2" for two-legged ties
   aggregate_id: fixture.aggregate_id,
   kickoff: fixture.starting_at,
   result: fixture.result_info,
 }));
}

Grouping by aggregate_id gives you the two legs of a tie together, which you need to determine who advances on aggregate. If aggregate_id is null, it’s a single-leg match.

Step 7: Covering all six confederations

The same pattern above applies to every confederation; you repeat the flow with each league’s ID. Here’s a clean wrapper that runs the full chain for any qualifying league:

async function buildQualifyingData(leagueId) {
 // 1. Get the active season
 const season = await getQualifyingSeason(leagueId);

 // 2. Discover stages
 const stages = await getSeasonStages(season.id);

 // 3. Identify group vs playoff stages
 const groupStage = stages.find(s => s.name.toLowerCase().includes('group'));
 const playoffStages = stages.filter(s => !s.name.toLowerCase().includes('group'));

 // 4. Pull group standings if applicable
 const standings = groupStage
   ? await getGroupStageStandings(season.id, groupStage.id)
   : null;

 // 5. Pull fixtures per stage
 const allFixtures = await Promise.all(
   stages.map(s => getFixturesByStage(season.id, s.id))
 );

 return {
   league_id: leagueId,
   season,
   stages,
   standings,
   fixtures: allFixtures.flat(),
 };
}

// Run for all confederations
const CONFEDERATION_LEAGUE_IDS = [
 '{UEFA_LEAGUE_ID}',
 '{CONMEBOL_LEAGUE_ID}',
 '{CAF_LEAGUE_ID}',
 '{CONCACAF_LEAGUE_ID}',
 '{AFC_LEAGUE_ID}',
 '{OFC_LEAGUE_ID}',
];

Promise.all(CONFEDERATION_LEAGUE_IDS.map(buildQualifyingData))
 .then(results => console.log(JSON.stringify(results, null, 2)))
 .catch(console.error);

A note on rate limits and caching

Qualifying data doesn’t change minute-to-minute. Group standings and completed fixture results are static once a matchday finishes. Cache aggressively:
– Static data (season info, stage structure, confirmed results): cache for 24 hours or more.
– Standings mid-matchday: refresh every 5–10 minutes while games are live.
– Live fixture events: use the livescores endpoint (GET /v3/football/livescores) during matchdays; it’s designed for polling.

Your rate limit depends on your plan. Keep an eye on the X-RateLimit-Remaining header in every response and back off if you’re approaching the limit before a matchday window closes.

What you can build with this

The data available across all six qualifying campaigns unlocks more than score trackers. A few ideas worth exploring:
– Qualification probability dashboards: combine standings data with the Predictions endpoint (/v3/football/predictions/fixtures/{fixture_id}) to show each nation’s projected final position and likelihood of reaching the tournament.
– Cross-confederation comparison tools: CONMEBOL has 10 nations in a single group; AFC runs multiple rounds, with group stages feeding into knockout rounds. With all six datasets normalised, you can build a unified tracker showing live qualification status worldwide.
– Historical campaign archives: call GET /v3/football/seasons?filters=seasonLeagues:{league_id} to retrieve all past qualifying seasons and build a searchable archive of every World Cup qualifying campaign in the database.

Summary

Here’s the full call sequence when building a qualifying integration from scratch:

  1. GET /v3/football/leagues/{league_id}?include=currentSeason : get the active season ID
  2. GET /v3/football/stages?filters=stageSeasons:{season_id} : discover all stages
  3. GET /v3/football/standings/seasons/{season_id}?filters=standingStages:{stage_id} : group stage tables
  4. GET /v3/football/fixtures?filters=fixtureSeasons:{season_id};fixtureStages:{stage_id} : fixtures per stage
  5. GET /v3/football/fixtures/{fixture_id}?include=statistics;events : match-level detail

The key insight is treating stages as first-class objects rather than an afterthought. Pull them early, classify them, and use them to gate your standings and fixture queries accordingly. Get that right, and the rest falls into place cleanly.

Ready to start building? Check out the full Sportmonks Football API documentation for more information.

Unlock World Cup qualifying data with Sportmonks Football API

Ready to power your app or website with reliable World Cup qualifying data? Sportmonks’ Football API gives developers fast access to structured, live and historical football data from over 2,300 leagues globally, including all six World Cup qualifying confederations.

With detailed fixtures, standings, statistics, and flexible filtering options, you can build scoreboards, probability dashboards, analytics tools, and fan experiences with confidence. Start with a free trial, explore our comprehensive docs, and integrate advanced football data into your project today. Visit the Sportmonks Football API documentation to get started now.

Frequently asked questions

What is the Sportmonks Football API?
The Sportmonks Football API is a RESTful data service that provides comprehensive football data, including livescores, fixtures, standings, player statistics, events, odds, and more. It supports advanced query options, such as includes and filters, to tailor responses to your needs.
How do I start using the Sportmonks Football API?
Sign up for a Sportmonks account, generate your API token from the dashboard, and explore the API endpoints in the documentation. You can begin with basic calls, such as retrieving league or fixture data, and then expand into standings, events, and statistics.
Do you offer a free trial for the Football API?
Yes. Sportmonks offers a free trial on its Football API plans so you can test the endpoints, explore data coverage, and begin integrating without commitment.
What data can I access with the API?
You can pull live scores, match fixtures, team and player details, group standings, knockout stages, match statistics, odds, predictions, and historical records across global competitions.

Written by David Jaja

David Jaja is a technical content manager at Sportmonks, where he makes complex football data easier to understand for developers and businesses. With a background in frontend development and technical writing, he helps bridge the gap between technology and sports data. Through clear, insightful content, he ensures Sportmonks' APIs are accessible and easy to use, empowering developers to build standout football applications