Changelog

What changed in the API.

Changes to /football-scores/v1 that a customer can see, newest first: what changed, the routes it touches, and whether you need to act. Last change .

Versioning. From here on, changes to v1 are additive: new routes, parameters and fields can appear, so ignore any your client does not know. A change that would break an integration is announced here before it ships, with notice as the terms describe.

  1. Fixed

    Archive-wide totals no longer time out

    The index's totals, the match count in /health and meta.total on an unfiltered /matches list now come from the copy /stats reads, instead of a count of the whole archive that could run out of time and answer 503 query_timeout. Filtered lists still count their own rows. Until the sync has stored its first copy after a deploy, the index and /health leave out a count that runs out of time rather than answering with a 503. Response shapes are unchanged.

    • /
    • /health
    • /matches
    Fixed

    /stats is served from a stored copy

    /stats counted the whole archive itself each time its ten-minute cached copy ran out and, under load, could answer 503 query_timeout for a query the caller had not written. It now reads a copy the sync stores after each run that changes the archive. Before the first copy exists, just after a deploy, a count that runs out of time answers 503 stats_unavailable: retry after a few minutes. The response shape is unchanged.

    • /stats
  2. Changed Action needed

    Malformed dates and list parameters answer 422

    A date, from or to that is not YYYY-MM-DD or an ISO 8601 date-time now answers 422 invalid_parameter, an impossible date such as 2026-02-30 included. It used to be dropped or rolled over, so the list silently widened or moved. A parameter sent as a list (q[]=…) answers the same 422, on any route, instead of a server error.

    What to do. Send date, from and to as YYYY-MM-DD or an ISO 8601 date-time, with the + of an offset sent as %2B, and never send a parameter as a list (name[]=).

    • /matches
    • /competitions/{id}/matches
    • /teams/{id}/matches
    Changed Action needed

    /stats kickoff bounds are ISO 8601

    first_kickoff and last_kickoff now read like every other time in the API, such as 2011-04-05T06:00:00+00:00, instead of the stored form, 2011-04-05 06:00:00.

    What to do. If you parse first_kickoff or last_kickoff, read them as ISO 8601.

    • /stats
    Fixed

    Exact ids, an exact quota header and literal search

    /matches/1001abc was read as match 1001. A match id must now be a whole number without a leading zero, and a competition or team id letters, digits, - and _; anything else answers 404 not_found. X-Quota-Remaining now counts the request it is sent on. In q, % and _ match themselves: q=% used to match every row.

    • /matches/{id}
    • /competitions/{id}
    • /teams/{id}
    • /competitions
    • /teams
  3. Changed Action needed

    Every route but the index needs a key

    A request without a key now answers 401 key_required; until this change a key was optional. A key that is unknown or revoked still answers 401 invalid_key, and GET / still answers without one.

    What to do. Send your key in X-API-Key, or as Authorization: Bearer, on every request.

    Removed Action needed

    Legacy routes retired

    Routes under /football-scores/v1 that are not in the docs were retired and answer 404 not_found, and match objects carry only the fields in the docs. Any path the API does not serve now answers 404 not_found in the API's own JSON envelope rather than the framework's HTML page, and a write method (POST, PUT, PATCH or DELETE) answers 405 read_only anywhere under the prefix. List responses are now Cache-Control: private, so a shared cache no longer stores them, and meta.total on a list may trail its rows by up to five minutes.

    What to do. Call only the routes in the docs.

    Fixed

    Kickoffs in true UTC, and scores only once final

    The sync now reads each kickoff with its UTC offset: kickoffs had been stored one to two hours late. A match gets a score only once the source marks the result final, so a postponed, abandoned or never-updated listing is no longer published as a 0-0 finished match: a past match without a final result is unknown, with null scores. The sync also skips listings that are not football matches. All of this applies to every match the sync writes or rescans from this date.

    • /matches
    • /matches/{id}
    • /competitions/{id}/matches
    • /teams/{id}/matches
    Added

    Three new error codes

    Lists stop at row 5,000 (page × per_page): past it, 422 page_too_deep asks you to narrow the query with a filter or a date range instead of paging further. A status the API does not know answers 422 invalid_status instead of an empty list. A query that runs past the API's time limit is stopped and answers 503 query_timeout.

    • /matches
    • /competitions
    • /teams
    • /competitions/{id}/matches
    • /teams/{id}/matches
  4. Added

    The football results API, v1

    Football results from 2011 under /football-scores/v1: matches with half-time and full-time scores, the competitions and teams behind them, and coverage figures. Every route is a GET and answers JSON. A key was optional, and keyed responses carry the X-Quota-* and X-Plan headers.

    • /
    • /health
    • /stats
    • /countries
    • /docs
    • /competitions
    • /competitions/{id}
    • /competitions/{id}/seasons
    • /competitions/{id}/matches
    • /teams
    • /teams/{id}
    • /teams/{id}/matches
    • /matches
    • /matches/{id}