Two parameters and an unusual date format
Historical statistics takes two required parameters: leagueId, the usual positive numeric competition identifier sent as a string, and date. The date must be a valid calendar date written as day, month and four-digit year separated by full stops, for example 30.10.2026. ISO dates such as 2026-10-30 are not accepted and produce a 400 response with the invalid_parameter code.
The format catches people out because almost every other modern API uses ISO dates. Write a single helper that formats dates for this feed and use it everywhere, rather than formatting strings by hand in different places. A day and month swapped in one call site will request a different date entirely, or an invalid one, and the bug can be hard to spot when both values are below thirteen.
What has been confirmed, and what has not
We prefer to be precise about the state of this feed. Populated responses have been returned for our documentation example competition, leagueId 1204, on two consecutive dates in August 2025, and those responses included team and player statistics. That confirms the request shape works and that statistics can come back.
Two things are not yet confirmed. First, exact date semantics: in one source example, a request for a given date returned a match dated the following day. That may reflect timezone handling, the way the source assigns matches to days, or something else, and we have not settled it. Second, archive coverage: how far back statistics go, and for which competitions, varies and has not been certified. Samples do not establish entitlement or licensing for your account.
So build defensively. Request dates on which you know, from fixtures or results, that completed matches took place in the competition, and check the match dates and identifiers in each response before filing them under a day.
A defensive request helper
The TypeScript helper formats a date correctly, makes the request, and returns the envelope together with the date you asked for. Storing the requested date separately from whatever dates appear inside the data keeps the two from being confused when they differ.
Use matches you already hold from historical fixtures or recent results to decide which dates to request. That way you never waste requests on days with no completed matches in the competition.
const pad = (n: number) => String(n).padStart(2, "0");
const feedDate = (d: Date) => `${pad(d.getUTCDate())}.${pad(d.getUTCMonth() + 1)}.${d.getUTCFullYear()}`;
export async function statsFor(leagueId: string, day: Date) {
const q = new URLSearchParams({ leagueId, date: feedDate(day) });
const res = await fetch(`https://footballapi.net/api/v1/football/historical-statistics?${q}`, {
headers: { "X-API-Key": process.env.FOOTBALLAPI_KEY! },
});
const body = await res.json();
return { requested: q.get("date"), ok: res.ok, body }; // check match dates inside body.data
}Building a statistics archive
A statistics archive is valuable because it lets you answer questions the current feeds cannot: how a team's shot volume changed across a season, how a player's involvement varied by opponent. The way to build it is to request each completed match day once, store the raw envelope keyed by competition and requested date, and extract the attributes you need into an analysis table that also records the source match identifier.
Keep the raw copy even after extracting. Source attributes can change between seasons, and if you discover later that you misread one, you can re-extract from storage without spending requests again. The cache time for this feed is about an hour, but for completed match days the data should not need refreshing at all once you are satisfied with it.
- Key storage by leagueId and requested date, not by dates found inside data.
- Record the source match identifier for every extracted row.
- Keep the raw envelope for re-extraction.
- Never fill a missing statistic with zero.
Comparing seasons honestly
Statistics recorded in one season are not always comparable with another. A source may have started recording a statistic partway through a period, changed its definition, or recorded it for some competitions and not others. Before you publish a season-on-season comparison, check that the attribute exists and is populated in both seasons for the competition in question.
When you find a gap, say so. A chart that silently starts in a later season because earlier data was missing invites readers to draw the wrong conclusion. A short note stating that the statistic is available from a given point is honest and costs nothing.
How this differs from current match statistics
The competition match statistics feed returns statistics for current matches in a competition, takes only a leagueId and is cached for about a minute. It is built for match days. Historical statistics adds a date and is built for looking back. Treat them as separate sources in your code, with separate mappings and tests, even where their structures look similar, because similar is not the same as guaranteed identical.
A common pattern is to capture statistics from the current feed after each match day and later backfill gaps with historical requests. Mark which feed produced each stored record so you can trace any discrepancy.
Working with the date parameter
The service takes a date in dd.MM.yyyy form, for example 14.09.2024, with day and month first and four-digit years. Build that string in one helper and validate it before sending, because a swapped day and month produces a valid-looking request for the wrong day, or a 400 invalid_parameter response when the day exceeds twelve. Exactly which matches a given date returns, for example by kick-off date in a particular timezone, has not been confirmed in the published contract, so check a few known matchdays against your own fixture records before building an archive around it.
Store the date string you requested alongside each stored response. If questions arise later, you can see precisely what you asked for.
Building a statistics archive
Archives are built once and refreshed rarely. Work through the matchdays of a past season using dates taken from your stored fixtures, request each date once, and store the response with its request date and fetch time. Space requests out rather than firing hundreds in parallel; there is no benefit to speed here and you stay well below the account rate ceiling. Afterwards, re-request only dates where you suspect corrections.
Treat the archive as the source's record at the time you fetched it, and say so on archive pages.
Plan scope
Historical statistics is league addressed. On Starter at EUR 29 a month, the leagueId must be one of your five saved leagues; others are refused with a 403 response. Growth at EUR 99 and Pro at EUR 249 cover all available package leagues, subject to whatever archive the source holds for each.
Archive builds are the most likely place to spend many requests in a short time. A season of weekly match days for one competition is perhaps forty requests, which is modest, but a multi-competition backfill can add up. Pace requests well below the 100 requests per second account ceiling. Successful requests above the daily inclusion are EUR 0.002 each on the monthly invoice; failed and denied requests are not billed.
Questions developers ask
- What date format does historical statistics accept?
- Day, month and four-digit year separated by full stops, for example 30.10.2026. ISO dates are not accepted.
- Does the date I request always match the match dates returned?
- Not necessarily. One source example returned a match dated the day after the requested date. Check the dates inside each response.
- How far back do historical statistics go?
- Archive coverage varies by competition and has not been certified. Sample the dates and competitions you need.
- Which dates should I request?
- Dates on which you know completed matches took place in that competition, taken from fixtures or results you already hold.
- Can Starter use this feed?
- Yes, for the five saved leagues only.
- Should missing statistics be shown as zero?
- No. A missing value means it was not supplied, not that it was measured as zero.
Guides and documentation
Check the plan before you write the integration.
Starter EUR 29, Growth EUR 99, Pro EUR 249 per month. No free trial. No VAT currently charged.