World Cup 2026 Group Stage API: Standings, Tiebreakers & Qualification
World Cup 2026 group stage API: live standings for all 12 groups, full FIFA tiebreakers, third-place qualification logic, and code examples.
The group stage of the 2026 FIFA World Cup is bigger than ever: 12 groups, 48 teams, 72 matches, and the new 24+8 qualification format that sends 32 teams into the Round of 32. Standings change literally every 90 minutes during match-days, and you need an API that keeps up.
This guide covers how the standings endpoint works, how FIFA tiebreakers are applied, and how to handle the new "best third-placed teams" qualification logic in your app.
The 2026 group stage format
| Element | Count |
|---|---|
| Groups | 12 (A–L) |
| Teams per group | 4 |
| Matches per group | 6 |
| Total group-stage matches | 72 |
| Days | June 11 – June 28, 2026 |
| Teams advancing | 32 (top 2 from each group + 8 best 3rd-placed) |
So the third-placed teams matter — across all 12 groups, the top 8 by points/GD/GS join the 24 group winners and runners-up in the Round of 32.
Get live standings for all 12 groups
curl 'https://api.thestatsapi.com/api/football/standings?competition_id={COMPETITION_ID}&season=2026' \
-H 'Authorization: Bearer YOUR_API_KEY'
Returns 12 group tables. Each row:
{
"group": "A",
"position": 1,
"team": { "id": "tm_mex", "name": "Mexico", "slug": "mexico" },
"played": 3,
"won": 2,
"drawn": 1,
"lost": 0,
"goals_for": 5,
"goals_against": 2,
"goal_difference": 3,
"points": 7,
"qualification": "winner" // "winner" | "runner_up" | "best_third" | "third" | "eliminated"
}
The qualification flag is the killer field — it tells you whether each team has advanced, is in the best-third race, or is eliminated. No need to recompute the logic client-side.
Filter to a single group
curl 'https://api.thestatsapi.com/api/football/standings?competition_id={COMPETITION_ID}&group=A' \
-H 'Authorization: Bearer YOUR_API_KEY'
Useful for group-specific pages or widgets.
FIFA tiebreakers explained
When teams are level on points, FIFA applies tiebreakers in strict order:
- Points in all group matches
- Goal difference in all group matches
- Goals scored in all group matches
- Points in head-to-head matches between tied teams
- Goal difference in head-to-head matches
- Goals scored in head-to-head matches
- Fair play points (yellow/red card deductions)
- Drawing of lots by FIFA
The standings API applies all of these automatically. The position field reflects the post-tiebreaker order.
The best third-placed teams logic
Across the 12 third-placed teams, the top 8 advance. The ranking criteria mirror the group tiebreakers:
- Points
- Goal difference
- Goals scored
- Fewest disciplinary points (yellow / red card deductions)
- Drawing of lots
The standings endpoint flags each third-placed team's status:
"qualification": "best_third"— confirmed Round of 32 qualifier"qualification": "third"— still in the race"qualification": "eliminated"— confirmed out
JavaScript: render a live standings table
import useSWR from 'swr';
function GroupStandings({ group }) {
const { data } = useSWR(
`/api/standings?group=${group}`,
(url) => fetch(url).then((r) => r.json()),
{ refreshInterval: 30000 } // poll every 30s during matches
);
if (!data) return <div>Loading...</div>;
return (
<table>
<thead>
<tr>
<th>#</th><th>Team</th><th>P</th><th>W</th><th>D</th><th>L</th>
<th>GF</th><th>GA</th><th>GD</th><th>Pts</th>
</tr>
</thead>
<tbody>
{data.map((row) => (
<tr key={row.team.slug} className={qualClass(row.qualification)}>
<td>{row.position}</td>
<td>{row.team.name}</td>
<td>{row.played}</td>
<td>{row.won}</td>
<td>{row.drawn}</td>
<td>{row.lost}</td>
<td>{row.goals_for}</td>
<td>{row.goals_against}</td>
<td>{row.goal_difference > 0 ? '+' : ''}{row.goal_difference}</td>
<td><strong>{row.points}</strong></td>
</tr>
))}
</tbody>
</table>
);
}
function qualClass(q) {
if (q === 'winner' || q === 'runner_up') return 'bg-emerald-50';
if (q === 'best_third') return 'bg-blue-50';
if (q === 'eliminated') return 'bg-red-50';
return '';
}
Python: detect the best third-placed teams
If you need to compute the ranking yourself (e.g. for predictions before all matches are played):
def rank_third_placed(standings_by_group):
third_placed = []
for group_letter, table in standings_by_group.items():
if len(table) >= 3:
third = table[2]
third_placed.append({
'group': group_letter,
'team': third['team']['name'],
'points': third['points'],
'gd': third['goal_difference'],
'gf': third['goals_for'],
})
# FIFA criteria: pts, gd, gf, alphabetic
third_placed.sort(
key=lambda x: (-x['points'], -x['gd'], -x['gf'], x['team'])
)
return third_placed[:8] # top 8 advance
For the actual production game, use the API's qualification flag instead of recomputing — fair play points are hard to get right.
Standings during a match
Standings update as soon as a match finishes — typically within seconds of the final whistle. During a match, the table reflects the pre-match state (not "what if the current live score holds"). To project standings live, combine /standings with the live /matches?status=in_progress and recompute client-side.
Our Group Stage Simulator tool does exactly this — pure client-side projection from user-entered scores. Inspect the source for a reference implementation.
Caching guidance
- Before match-day: cache standings for 1 hour
- During match-day: cache for 30-60 seconds
- During a match: poll directly without cache, or cache for 10 seconds
The standings payload is small (12 groups × 4 rows × ~12 fields) so caching is more about reducing rate-limit pressure than payload size.
Frequently Asked Questions
How are World Cup 2026 group stage ties broken?
In order: points, goal difference, goals scored, head-to-head points, head-to-head goal difference, head-to-head goals scored, fair play points, drawing of lots. The standings API applies these automatically.
How are the 8 best third-placed teams ranked?
Points first, then goal difference, then goals scored, then fewest disciplinary points (fair play), then drawing of lots. The standings endpoint flags each third-placed team's qualification status.
How fast do standings update during matches?
Within seconds of a match's final whistle. During matches, the standings reflect the pre-match state (not projected live).
Can I get standings for a single group?
Yes — filter ?group=A (or B, C, etc.) on the standings endpoint.
What's the difference between the standings API and the matches API?
/matches returns individual fixtures and results. /standings returns the aggregated league table for each group with tiebreakers applied. Use both together for a complete group view.
How do I visualise qualification status?
Use the qualification flag on each standings row: winner, runner_up, best_third, third, eliminated. Colour-code rows or add an icon to give fans an at-a-glance read.
Does standings include xG and shot data?
The standings endpoint focuses on classic table stats. For xG-based analysis (expected points vs actual, shooting accuracy), use /teams/{id}/stats?competition_id={COMPETITION_ID} to get the deeper metrics.
Ready to Power Your Sports App?
Start your 7-day free trial. All endpoints included on every plan.