Coverage is the same on every plan
Free, Pro, Ultra and Mega all call the same football endpoints against the same data feed. No competition, league or endpoint is restricted by plan — the only difference between plans is the daily request allowance:
You can therefore validate coverage for your project on the Free plan before upgrading.
Current catalogue size
Verified live on 3 September 2026 by walking every category:- 6,473 football competitions across 272 country and regional categories (including international and continental groups, and the separate
... Amateurcategories)
1. Full flat catalogue (partial — see warning above)
id here is the canonical uniqueTournament.id — store this, never the per-season tournament.id. See Canonical IDs.Dump the catalogue to CSV
2. Browse country by country
2b. Amateur and lower tiers live in separate categories
Amateur and semi-professional competitions are not returned under a country’s main category. Each country has a distinct... Amateur category:
Retry once on
503 / 504. Uncached calls to heavy endpoints can exceed the 30-second gateway timeout on the first request and succeed immediately afterwards. Verified 3 September 2026: /api/tournaments, /api/tournament/37/info and /api/tournaments/42/seasons each returned 504 cold and 200 in under a second on retry; /api/categories/1587/tournaments returned 503 once, then 200. In a full 272-category sweep, 5 categories failed on the first attempt and all 5 succeeded on retry. A rare variant returns 200 with an empty array (/api/tournaments/493/seasons → seasons: [], then 19 seasons on retry) — treat an unexpectedly empty list as a retryable response, not as “no coverage”.Archive depth shrinks as you go down the pyramid
Breadth reaches district level, but historical depth does not. Verified live on 1 September 2026 via/api/tournaments/{id}/seasons:
There is no competition named “Hoofdklasse” in the feed — that level of the Dutch pyramid is expressed through the Vierde Divisie and the regional 1e Klasse divisions.
/standings and paginated /events/last/{page} match lists.
Map archive depth for every league worldwide
Two calls give you an exact seasons-available table for the whole catalogue:Bulk import performance
Measured live on 4 September 2026 against production. Warm single-request latency is sub-second (/countries 0.49s, /categories/35/tournaments 0.34s, /tournaments/37/seasons 0.30s, season event pages 0.35–0.8s), so import speed is decided almost entirely by how many requests you run in parallel.
Use 32 concurrent workers as the default, a 30-second timeout and one retry. 64 also ran clean with no
429s, but 32 leaves headroom for transient upstream slowness.
A sequential importer is the usual cause of a multi-hour region sweep: the first (cold) hit on a heavy path can take 10–30 seconds, and a single-threaded loop pays every one of those serially. A second pass over the same region runs an order of magnitude faster because everything is warm.
Further savings:
- Season event pages already contain the full match row —
homeTeam,awayTeam,homeScore/awayScorewith period splits,status,startTimestamp,roundInfo,season,tournament. Do not call/api/match/{matchId}per fixture in a first-pass import. 404on the page after the last one is the normal end-of-pagination signal, not an error. Eredivisie 25/26 (season77012) is 325 events in 11 requests at 30 per page; Regionalliga Nord 26/27 is 56 events in 2 requests.- Pull only the seasons you need. Lower tiers carry the current season only, so a current-season import is one
/seasonscall plus 1–2 event pages per competition.
Page size is fixed at 30
Verified 6 September 2026:events/last/{page} and events/next/{page} always return 30 events per page. ?limit=100 and ?pageSize=100 are ignored — both still return 30. There is no larger-page or bulk-season variant, so total request count is a function of fixture count. The lever is concurrency, not payload size.
Fetch a season’s pages in parallel
Pages within one season are independent, so do not walk them sequentially. Verified 6 September 2026 on Eredivisie 25/26 (tournament37, season 77012): issuing pages 0–11 all at once returned the complete season — 11 populated pages plus the terminating 404 — in 2.18 seconds.
Two ways to know how many pages to request:
season/{seasonId}/rounds is not a backfill route. Round pages hold far fewer events than paged season events — Eredivisie round 1 returns 9 events versus 30 per events/last page — so round-walking roughly triples your request count for the same data.3. Live and date-scoped competition filters
Reference IDs for major competitions
Depth varies by competition, not by plan
The competition list is identical everywhere, but how much data each competition carries differs:
See Data Coverage Questions for the full breakdown.
Player statistics depth by tier
Measured 4 Sep 2026 ontop-players (categories returned) and on
/api/players/{playerId}/tournament/{id}/season/{seasonId}/statistics (fields returned):
A season that has just started returns fewer entries per category rather than fewer categories
(Bundesliga 26/27: 30 categories, 29 entries under
goals).
Only type=overall is served on these endpoints; home and away return 404/503 even
though player-statistics/types lists them. Cold calls to season statistics paths can return
503/504 and succeed on retry — retry once before treating a competition as uncovered.
Premier League player-match depth by season
Measured 10 Sep 2026 onGET /api/match/{matchId}/lineups (union of statistic keys across all
40 players in a finished match) and GET /api/match/{matchId}/shotmap for the same match.
One lineups request returns every player on both teams — per-player requests are never
needed for player-match statistics.
Fixtures, scores, lineups and events run back to 92/93 (
13043) throughout.
Current-season per-match fields include shooting (totalShots, onTargetScoringAttempt,
shotOffTarget, blockedScoringAttempt, hitWoodwork), expected data (expectedGoals,
expectedGoalsOnTarget, expectedAssists), chance creation (keyPass, bigChanceCreated,
bigChanceMissed), passing splits, carries (ballCarriesCount,
progressiveBallCarriesCount, totalBallCarriesDistance), defensive actions
(totalTackle, wonTackle, interceptionWon, outfielderBlock, totalClearance,
ballRecovery, errorLeadToAShot), duels (duelWon, duelLost, aerialWon, aerialLost),
goalkeeping (saves, savedShotsFromInsideTheBox, goodHighClaim, punches,
goalsPrevented) and physical metrics (kilometersCovered, metersCoveredRunningKm,
metersCoveredHighSpeedRunningKm, metersCoveredSprintingKm, numberOfSprints, topSpeed).
Match companions cost one request each: shotmap, incidents, statistics (team level),
average-positions, graph. There is no heatmap endpoint (404).
The 117-field season aggregate is not 117 additional primitives — much of it is percentages,
per-90s, sums and splits derived from the per-match fields. Genuinely season-only fields are
appearances, matchesStarted, cleanSheet, totwAppearances, card totals, penalty splits,
errorLeadToGoal and dribbledPast.
Friendly match depth
Measured 10 Sep 2026 on four Arsenal preseason friendlies (Club Friendly Games, 2025 and 2026): 51–66 player fields per match plus a full shotmap of 19–29 shots, including passing, duel, defensive and physical metrics. The consistent gap is expected data — friendlies carry no player-levelexpectedGoals/expectedAssists and no xg/xgot on individual shots.
Friendly coverage itself remains best-effort.
Endpoints without a documented equivalent — for example
/api/categories or /api/unique-tournaments — are not part of the API and return 404. Use the paths listed above.Marketing overview page
A plain-English summary of this coverage, including the per-tier archive table and pricing, lives on the public site at Football Stats API. Useful for sharing with non-technical stakeholders evaluating the API.Stadium coordinates
Venue objects carry latitude/longitude, city, country and capacity. Verified 1 Sep 2026:Cache venue records locally. Stadium coordinates are static, so one lookup per venue is enough — never fetch them per match.
Venue availability by competition tier
Event-level venue (event.venue) is populated for higher tiers only. Measured 3 Sep 2026 on German competitions, page 0 of season events:
At tiers without event-level venue data there is no alternative event → venue relation:
/api/match/:matchId/venuereturns404(200 for Bundesliga, 404 for Regionalliga and Oberliga matches)./api/tournament/:id/season/:sid/venuesreturns404(200 for 3. Liga, 404 for Regionalliga and Oberliga seasons).- There is no reverse index —
/api/venue/:venueId/events/last|next/:pagereturns404.
GET /api/match/:matchId— the event has novenue, buthomeTeam.venueandawayTeam.venueare fully hydrated (name, city, country, capacity,venueCoordinates). One request covers both clubs.- Or cache the ground per club via
/api/teams/:teamId→team.venue, plus/api/venue/:venueIdfor the full record. Venue IDs resolve normally at these tiers. - Record the provenance (for example
venue_source = 'event' | 'home_club') and letevent.venuewin whenever it is present — it is authoritative and diverges from the home ground for neutral-venue ties (e.g. match15632636PSG vs Arsenal played at the Ferenc Puskás Stadium, not Parc des Princes).
Quota-efficient retrieval
Avoid polling per-match endpoints on a timer: use the date schedule for the day’s card, then the WebSocket for in-play changes.
Bulk import playbook
The cheapest way to build a full database is competition → seasons → paged season events, reading clubs, venues and results out of the event payloads instead of requesting each entity separately.1. Resolve the competition once
uniqueTournament.id (season-independent). Never persist the per-season tournament.id — it is regenerated every year. Verified depth: 2. Bundesliga (44) exposes 19 seasons, UEFA Champions League (7) exposes 35.
No example: this endpoint returned HTTP 404 for our Bundesliga sample on 24 September 2026. It only returns data when the entity has it (for example knockout draws, penalties or media), so handle an empty or 404 response.
2. Page the season
hasNextPage flag. A full 34-matchday league season is roughly 13 requests.
3. Harvest entities from the event payload
Each event carries the core relational model. Venue data needs one extra rule: season and round pages can omitvenue even when the full match response has it, so hydrate
GET /api/match/{matchId} before classifying a match venue as missing.
Store venue provenance as
event, home_club, or unknown. Never present homeTeam.venue as
the confirmed match venue: neutral-site finals and relocated ties can differ from the home club’s
ground. Historical venue coverage is incomplete even for major competitions. In a 16 September
2026 sample, 18/30 Champions League 1993/94 events and 20/30 from 1992/93 carried a venue, while
the final in each season had no event venue and GET /api/match/{id}/venue returned 404.
4. Cups and international competitions
Same flow, plus one bracket call per season:5. Play-offs
Check both shapes:- Additional rounds inside the same season — call
/api/tournament/{id}/season/{seasonId}/roundsand import any rounds beyond the regular matchday count via the normal/events/...pages. - A separate competition — search the country/category for “relegation” or “promotion” (for example
Ettan, Relegation/Promotion11617,Bayernliga Relegation/Promotion18788) and import it like any league.
6. Friendlies
Friendlies are modelled as ordinary competitions, not as a special endpoint:
For a complete backfill, call
/api/tournaments/{id}/seasons, then walk both
/api/tournament/{id}/season/{seasonId}/events/last/{page} and
/events/next/{page} until hasNextPage is false or the next page is empty/404. Seasons use
calendar years. For one club, /api/teams/{teamId}/events/last|next/{page} is the most reliable
cross-competition view because friendly and competitive matches use the same team ID.
Do not use /api/schedule/{date} as the primary friendly importer. It is useful as a same-day
cross-check, but it is not a complete worldwide fixture mirror. Use /api/newly-added-events as
a lightweight new-fixture signal and hydrate the returned match IDs.
7. Logos — fetch once, cache forever
V2 image routes are restored as of 10 September 2026. You can now import logos via V2
(
/v2/football/images/teams/{id}, /v2/football/images/tournaments/{id}) as well as the free
V1 routes and the deep-coverage V6 filename-based route.logo fields are bare filenames — pass them through verbatim, including any !w80 suffix.
V6 IDs are opaque strings, so match by team name plus country once and store the pairing.
Ongoing sync and change detection
Once the initial import is done, keep the local copy current from the same endpoints — there is no push feed, and no dedicated change route (updated-events, events/updated, changes and match/{matchId}/changes all return 404).
Every event carries a change stamp
Verified 10 September 2026. Each event object in season pages,/api/schedule/{date} and /api/match/{matchId} includes:
changeTimestamp is Unix epoch seconds and is 0 for fixtures that have never been touched (typical for scheduled matches). Store it per match and re-write a row only when the incoming value is greater than the stored one. The block ships inside the page payload you already fetch, so change detection costs no extra requests, and it works on old seasons too (1988/89 Eredivisie events carry full score-level change lists).
Newly added fixtures
eventId, homeTeamId, awayTeamId, uniqueTournamentId — with no timestamps (57 entries when measured). Use it to discover cup draws and newly scheduled or rescheduled matches, then hydrate through the season pages or /api/match/{matchId}.
Detecting newly added historical seasons
Older seasons are appended to existing competitions over time, and they arrive with high season IDs rather than in chronological order — Eredivisie (37) 88/89–91/92 are 90654–90657 while 25/26 is 77012. Re-read the season list and diff the ID set:
Recommended update loop
Friendlies stay in sync the same way
Friendlies are ordinary competitions with one season per calendar year (853 has 17 seasons, 2010–2026), so events/next/{page} and events/last/{page} behave normally — season 87177 returned a full 30-event page of played matches and 10 upcoming fixtures. They also appear in /api/teams/{teamId}/events/last|next/{page} and in the date schedule, and they use the same stable team IDs as competitive matches (Arsenal 42 in both the Premier League and Club Friendly Games).
Import hygiene
- Keep concurrency at 4–8 parallel requests and honour the
ttlfield in responses. - Retry once on an occasional 404 or 5xx — a single retry clears nearly all transient upstream blips.
- Ask support for a temporary daily-limit lift before a large backfill; it reverts automatically when it expires.