Endpoint guide

Query fixtures and results.

The match collection covers scheduled fixtures and completed full-time results from the currently enabled datasets. Per-match team statistics — corners, shots, possession, cards, passes and expected goals — are available separately at /football/matches/{match_id}/stats for covered competitions.

Available operations

Method Path Purpose
GET /api/v1/football/matches Filter and paginate fixtures or results.
GET /api/v1/football/matches/{match_id} Retrieve one match by Stats API public ID.

Match detail operations

A match resource is deliberately small. Richer detail lives on dedicated sub-resources so a client polling scores every thirty seconds is not also transferring lineups and per-player statistics it already has.

Each detail route reports its own coverage. A competition without statistics coverage returns the match with an explicit coverage flag and an empty collection rather than a 404 — an empty result and an unsupported competition are different answers, and the response says which one you received.

Method Path Purpose
GET /api/v1/football/matches/{match_id}/stats Team statistics: corners, shots, possession, cards, passes, expected goals.
GET /api/v1/football/matches/{match_id}/live-stats Same shape, cached briefly for matches in progress.
GET /api/v1/football/matches/{match_id}/lineups Starting eleven, bench, formation, coach.
GET /api/v1/football/matches/{match_id}/timeline Ordered goals, cards, substitutions, VAR decisions.
GET /api/v1/football/matches/{match_id}/live-timeline Same events, short cache, for in-play matches.
GET /api/v1/football/matches/{match_id}/player-stats Per-player minutes, rating and canonical statistics.
GET /api/v1/football/matches/{match_id}/live-player-stats Same shape for matches in progress.

Worked example: reading corners

Request team statistics for a single match. Every documented field is always present; a statistic the source did not supply is null rather than absent, so a client never has to distinguish a missing key from a missing value.

Example
GET /api/v1/football/matches/match_a14c38236c0e/stats
Authorization: Bearer sapi_live_...

{
  "data": {
    "match_id": "match_a14c38236c0e",
    "status": "finished",
    "coverage": { "statistics_available": true },
    "teams": [
      {
        "team": { "id": "team_f2c85c3c8cb2", "name": "Colorado Rapids" },
        "statistics": {
          "corners": 7,
          "shots_total": 14,
          "shots_on_target": 4,
          "possession_percent": 64,
          "passes_accuracy_percent": 86,
          "expected_goals": 1.22,
          "red_cards": null
        }
      }
    ]
  }
}

Statistic field semantics

Field names are canonical to Stats API, not inherited from any upstream provider. That is deliberate: a provider change must not rename a field your code reads.

Field Type Meaning
corners integer Corner kicks awarded to the team.
shots_total integer All goal attempts, including blocked and off target.
shots_on_target integer Attempts that would enter the goal without intervention.
shots_inside_box integer Attempts taken inside the penalty area.
possession_percent number Share of possession, 0-100. Both teams sum to roughly 100.
passes_accuracy_percent number Completed passes as a percentage of attempted.
expected_goals number Cumulative xG. Available where the source supplies it.
yellow_cards / red_cards integer Cards shown. Null means not reported, not zero.

Error cases and how to tell them apart

Three outcomes look similar from the outside and mean different things. Handling them identically is the most common integration mistake.

Situation Response Correct handling
Match ID does not exist 404 not_found The identifier is wrong. Do not retry.
Competition has no statistics coverage 200 with coverage.statistics_available false and empty teams Show "not covered". Do not treat as an error or retry.
Match has not been played 200 with empty statistics Poll again after full time.
Key lacks an active entitlement 403 subscription_inactive Renew the plan; retrying will not help.
Per-minute limit exceeded 429 rate_limit with Retry-After Back off for the stated interval.

Filters

Parameter Format
competition_id Stats API competition ID.
season_id Stats API season ID.
team_id Matches where the team is home or away.
status scheduled, live, finished, postponed, canceled, or unknown.
date_from Inclusive YYYY-MM-DD UTC date.
date_to Inclusive YYYY-MM-DD UTC date.
page / limit Positive page; limit 1–100.
Example
GET /api/v1/football/matches?team_id=tm_example&date_from=2026-08-01&date_to=2026-08-31

Freshness boundary

OpenFootball is a community-maintained historical fixtures and results source, not a guaranteed live-score feed. A match updated_at value records when that normalized row last changed during Stats API ingestion; it is not a timestamp published by the source, and an identical re-import does not advance it.

The beta serves each dataset as Stats API observed it at the latest successful ingestion, with no promised maximum age or refresh interval. GET /api/v1/coverage/summary reports dataset-level last_success_at, artifact_retrieved_at, and records_seen. For OpenFootball, source_updated_at is null and freshness_guaranteed is false.

Each ingestion run separately records the retrieved artifact SHA-256, byte count, and retrieval time for audit. Retrieval and success times prove only what Stats API imported and when, not when the football facts became current. Live incidents remain outside the beta contract.