Skip to content
Statistics and squads

Historical football statistics API by competition and date

Historical statistics turns past match days into data you can analyse: team and player statistics for completed matches in a competition on a given date. This page is candid about what is confirmed and what is not, especially around date semantics, and shows how to build an archive that does not depend on assumptions.

Primary endpoint

GET/api/v1/football/historical-statistics

  • leagueId required, numeric string, e.g. 1204
  • date required, dd.MM.yyyy

Access: All plans; Starter limited to your five saved leagues. Header: X-API-Key.

Illustration of a desk calendar page beside stacked bar charts and a football, suggesting statistics for a past date.
Historical statistics are requested by competition and date; date semantics still need care.
01

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.

02

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.

Dated statistics requestsFlow: take a completed match date from your fixtures, format it as dd.MM.yyyy, request with leagueId, then verify returned match dates and IDs before storing.01Known match dayfrom fixtures02dd.MM.yyyynot ISO03leagueId + daterequest04Verify datesthen store
Dated statistics requests. Derive dates from known completed fixtures and check the returned match dates rather than assuming them.
03

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.

TypeScript: format dd.MM.yyyy and keep the requested datetypescript
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
}
04

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.
05

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.

06

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.

07

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.

08

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.

09

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.

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.