Skip to content
Fixtures and scheduling

Football fixtures API for schedules and match IDs

Fixture lists are the spine of almost every football product. This page explains how the published fixture feeds work, why the match identifier matters more than the kick-off time, and how to keep a calendar correct when the real world moves matches around.

Primary endpoint

GET/api/v1/football/fixtures

  • leagueId required, numeric string, e.g. 1204

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

Illustration of a calendar grid with highlighted match days beside a football.
Fixtures are scheduled events keyed by a source match ID; dates can move, the ID should not.
01

Start with the identifier, not the date

A fixture is a promise that two teams will meet. Dates slip, venues change and televised matches move by a day, but the match itself stays the same event. That is why we tell every integrating team to treat the source match identifier as the primary key and every other attribute, including the scheduled time, as something that can change underneath it.

Our fixture feeds keep the identifiers exactly as the source supplies them. They arrive as strings and should stay strings in your database. Converting them to integers looks harmless until an identifier with a leading zero, a prefix or a value larger than your column type appears, and then two different matches collapse into one row. Store them as text, index them, and join everything else to them.

The same rule applies to team and competition identifiers inside a fixture. Team names are display attributes. A club can be renamed, shortened for a scoreboard or spelled with different diacritics in a different competition. If your code looks a team up by name, it will eventually attach a result to the wrong club.

02

Which fixture feeds are published

Fixture data is spread across a small group of feeds in the public catalogue, each with a distinct job. They all live under the same gateway path, accept the same API key header and return the source structure rather than a reshaped schema of our own.

Every feed here currently carries the catalogue state sample received. A sample has returned through the gateway, while final freshness, coverage and licensing qualification is still pending. We would rather say that plainly than call the feeds complete.

  • fixtures, with a required leagueId parameter: the season schedule for one competition. Use it to build a full calendar for a league page.
  • upcoming, with a required offset parameter: matches coming up across the competitions your account can see. Useful for a today and tomorrow strip.
  • historical-fixtures, with leagueId and season: completed or archived seasons where the source supplies them.
  • competitions and seasons: directory feeds you use to discover valid identifiers before requesting schedules. These two are not part of the Starter league-scoped service set.
Fixture sync cycleFour-step flow: request fixtures by leagueId, upsert by string match ID, update changed attributes, flag missing fixtures for review.01fixtures?leagueIdrequest schedule02Upsert by IDstring match key03Update fieldsdate, status04Flag missingreview, not delete
Fixture sync cycle. A sync run upserts by match ID and flags, rather than deletes, fixtures that drop out of a response.
03

A first schedule request

The example below asks for one competition schedule. The identifier 1204 is used throughout our documentation as a working example; use the competitions directory or your dashboard to find the identifiers for the leagues you actually need. Keep the key on your server. A browser bundle is not a safe place for it.

Notice that the script writes the body to disk untouched. On your first integration pass, resist the temptation to map fields immediately. Read a real response, note which attributes carry an at-sign prefix, and keep those names as they are. The prefix tells you the value came from a source attribute rather than a nested element, and renaming it early makes later debugging against the source structure much harder.

Fetch one league schedule and keep the raw bodybash
curl -sS \
  -H "X-API-Key: $FOOTBALLAPI_KEY" \
  "https://footballapi.net/api/v1/football/fixtures?leagueId=1204" \
  -o fixtures-1204.json

# inspect before mapping anything
jq '.' fixtures-1204.json | head -n 60
04

Keeping a calendar honest when matches move

The most common fixture bug is not a missing match. It is a postponed match that still shows its old date because the application only inserts new rows and never revisits old ones. A sound sync job does three things on every run: it inserts identifiers it has never seen, it updates attributes on identifiers it already holds, and it flags identifiers that were present last time but are now absent, without deleting them automatically.

That last step matters. A match disappearing from a response can mean it was rescheduled out of the window you requested, removed by the source, or simply omitted from one response. Mark it for review and let a later run or a person decide. Silent deletion is how a cup replay vanishes from a club site the night before it is played.

For a weekly league page, a schedule refresh a few times a day is usually plenty. The catalogue lists a cache time of about fifteen minutes for the fixtures feed, so polling every minute burns allowance without bringing fresher data. Move faster only around match days, and use the scores feeds rather than the fixture feed for anything that changes during play.

  • Upsert by source match identifier, never by date plus team names.
  • Keep the previous scheduled time so you can show a moved-from note.
  • Record the time you last saw each fixture in a response.
  • Review, rather than delete, fixtures that drop out of a response.
05

Kick-off times as supplied

Kick-off values are passed through as the source provides them. Where the timezone of a value has not been confirmed, we flag that rather than converting it, because a wrong conversion is worse than an honest raw value. Before you render local times to readers in several countries, check how the competition you care about expresses its times and test across a daylight-saving change.

Some fixtures are published before a time is fixed. A date with no confirmed kick-off is a real state in football, particularly for later rounds of a season while broadcasters choose their matches. Design the interface to say time to be confirmed rather than inventing midnight or a placeholder hour.

06

Past seasons and archive pages

Archive work has different priorities from a live calendar. You request a season once, check it, and rarely touch it again. The historical-fixtures feed takes both leagueId and season, and the seasons directory is where you discover which season values exist for a competition. Depth varies a great deal by league: one competition may go back many years while another starts recently. We do not promise a fixed archive depth.

Cache archived seasons aggressively on your side. Requesting a finished season every hour spends allowance for no benefit, and every successful request counts towards your daily inclusion whether or not the data changed.

07

Cup rounds and placeholder fixtures

Knockout competitions add a wrinkle that league calendars do not have. A cup round can be scheduled before its participants are known, because the previous round or a replay has not been played yet. Depending on the source, such a fixture may appear late, may appear with incomplete team details, or may change identity once the draw settles. Do not hard-code assumptions about when a cup tie becomes visible.

In practice, run the same upsert logic for cups as for leagues, but give your interface a state for ties whose sides are not yet confirmed, and avoid sending reminder notifications until both teams are present in the data. When a replay or a rearranged leg is added, it should arrive as its own match identifier; store it alongside the original rather than overwriting the first meeting, so that both results remain available for your archive pages and aggregate-score displays.

08

Plans, leagues and request budgeting

Starter costs EUR 29 a month and covers up to five selected leagues with 5,000 successful requests per UTC day. On Starter, fixture requests only succeed for the leagues you have saved, and the competitions and seasons directories are not included, so pick your leagues from the verified selection list in the dashboard. Growth at EUR 99 opens all available package leagues with 25,000 requests a day, and Pro at EUR 249 raises that to 250,000.

A practical budget for a five-league fixture site looks like this: five schedule pulls six times a day is thirty requests, plus a handful of upcoming calls. That leaves a large margin on Starter. Successful requests above the daily inclusion cost EUR 0.002 each, billed monthly on the EUR subscription; failed and denied requests are not billed. An account safety ceiling of 100 requests per second applies on every plan. There is no free trial, and the business is UK based and not VAT registered, so no VAT is added.

Questions developers ask

Should I store fixture IDs as numbers?
No. Keep them as strings exactly as returned. Numeric conversion can strip leading characters or overflow, and two distinct matches can end up sharing a row.
How do I find the right leagueId?
Use the competitions directory on Growth or Pro, or the verified league list in your dashboard on Starter. The value 1204 in our examples is illustrative only.
Does the fixtures feed tell me when a match is postponed?
It returns whatever the source supplies for that match. Compare each run with your stored copy by match identifier so a changed date or status is detected rather than assumed.
How far back do historical fixtures go?
It depends on the competition. Check the seasons directory for the league you need; we do not guarantee a minimum archive depth.
Can I use upcoming instead of fixtures?
For a short window across your leagues, yes. For a full season calendar for one league, the leagueId-scoped fixtures feed is the better fit.
Are fixture times converted to my timezone?
No. Times are kept as supplied, and unconfirmed timezones are flagged rather than converted. Do the conversion yourself once you have confirmed the source convention.

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.