A small feed with an exact job
Two published historical feeds take a season parameter: historical fixtures and historical standings. In both, the value is two consecutive four-digit years separated by a hyphen, such as 2026-2027. That format suits competitions that run across a winter, but it also means you cannot pass a single year, a label like 26/27, or a free-text season name. The season catalogue exists so you do not have to guess which values are valid.
The catalogue takes no parameters. It returns the season information the source supplies, inside the usual gateway envelope with data and meta. We pass the data object through without reshaping it, so read a real response to see how seasons are represented and how they relate to competitions in the source structure. Identifiers stay as strings and source attributes keep any at-sign prefix.
Calendar-year competitions are worth a specific check. Where a league plays within a single year, look at how the catalogue expresses its seasons before building requests, rather than assuming which pair of years applies.
Why a listed season can still be thin
A season appearing in the catalogue does not guarantee that every historical field is populated. Older seasons commonly have fixtures and final scores but fewer details: missing kick-off times, partial venue information, or a table without every column. Some competitions changed format over the years, so a season might have one table where a later season has groups.
Plan for unevenness rather than discovering it in production. A reasonable approach is to request one early, one middle and one recent season for each competition you intend to archive, compare what comes back, and decide the earliest season you are comfortable presenting to readers. Publishing a range that turns out to be mostly blank is worse than publishing a shorter range that is solid.
- Sample old, middle and recent seasons before choosing an archive range.
- Expect format changes such as groups appearing or disappearing.
- Present missing historical fields as missing, not as zero.
Using the catalogue to drive historical requests
The shell example reads the catalogue once and then requests one historical table. In a real job you would loop over the seasons you selected after sampling, pausing between requests, and store each response permanently. Finished seasons rarely change, so there is no reason to request them repeatedly.
If a historical request returns a 400 response with the invalid_parameter code, check the season string first. A missing hyphen, two-digit years or non-consecutive years are the usual causes.
curl -sS -H "X-API-Key: $FOOTBALLAPI_KEY" \
"https://footballapi.net/api/v1/football/seasons" -o seasons.json
# after choosing a season value from seasons.json
curl -sS -H "X-API-Key: $FOOTBALLAPI_KEY" \
"https://footballapi.net/api/v1/football/historical-standings?leagueId=1204&season=2026-2027" \
-o standings-1204-2026-2027.jsonDesigning season navigation
A season picker on a league page is the most visible use of this feed. Build it from your stored catalogue rather than calling the API when a reader opens the menu. Show only the seasons you have actually archived and checked, not every season the catalogue lists, so readers never land on an empty page.
Use the season value itself as the stable part of your URL, for example a path segment containing 2026-2027, and display a friendlier label alongside it. That keeps links durable even if you change how the label is written.
Storing season values safely
Season values look like simple text, and it is tempting to split them into two integers, store the first year only, or reformat them into a friendlier label in the database. Each of those choices creates a small translation step you must reverse every time you call a historical feed, and translation steps are where bugs hide. Store the value exactly as the catalogue supplies it, use it as the key for archived responses, and derive any display label at render time. If your product covers both cross-year and calendar-year competitions, a single text column for the season value handles both without special cases.
Reading errors from historical requests
When a historical request built from the catalogue fails, the response code tells you where to look. A 400 response points at the request itself: a malformed season string or a missing leagueId. A 403 response means the key is valid but the account lacks entitlement, for example a Starter key asking about a league outside the saved five. A 503 response with the feed_not_qualified code means the endpoint is not currently ready, and repeating the request will not change that. Every error body includes a requestId; keep it in your logs, because it is the quickest way for support to find the exact call when you ask for help.
Testing season handling
Test your season helper against saved directory responses: confirm it only accepts YYYY-YYYY values, rejects malformed strings before they reach the API, and selects the current season from the directory rather than from the calendar. A season bug sends every historical request to the wrong year, so it is worth a few lines of tests.
Displaying seasons
Show the supplied season string on archive pages so readers know exactly which campaign they are looking at, and keep season labels consistent across tables, fixtures and statistics.
Caching the directory
Season lists change once or twice a year. Cache the directory for a day or more and refresh it around season boundaries. Every historical request can then read valid values from your copy without an extra call.
Plan scope
Like the competitions directory, the season catalogue is a global service that is not included in Starter. It requires Growth at EUR 99 a month or Pro at EUR 249, plus activation of the service for your account. Starter customers can still request historical fixtures and standings for their five saved leagues; they need to know the season value from another source, such as our documentation examples, rather than from the catalogue.
The feed is cached for about an hour. A daily refresh is more than enough, and most teams only look at it when planning a new archive. Each successful request counts towards the daily inclusion; failed and denied requests are not billed. Above the inclusion, extra successful requests are EUR 0.002 each on the monthly EUR subscription invoice.
Season turnover each summer
The most delicate moment for any football product is the changeover between seasons. Fixtures for the new campaign appear, the previous table becomes history, and promoted and relegated clubs move between competitions. The catalogue is a useful signal during this period. When a new season value appears for a competition you follow, it is time to archive the closing table through the historical feed, check that your current-season pages have switched to the new fixtures, and update any season pickers.
Do not switch on the first sight of a new season value alone. Confirm that the current feeds have actually moved over before changing your default season, because the catalogue can list a season before every feed is ready for it.
Questions developers ask
- What format do season values use?
- Two consecutive four-digit years separated by a hyphen, for example 2026-2027.
- Which feeds need a season value?
- Historical fixtures and historical standings. Historical statistics uses a dated request instead.
- Is the season catalogue on Starter?
- No. It requires Growth or Pro and separate activation. Starter can still call the historical feeds for saved leagues.
- Does a listed season have complete data?
- Not necessarily. A listing does not guarantee that every historical field is populated. Sample before you publish.
- How often does the catalogue change?
- Rarely, mostly around the start of a new season. A daily refresh is plenty.
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.