---
title: "Per-team post-match team statistics (soccer)"
method: GET
path: "/v1/matches/{id}/statistics"
tags: ["matches"]
---

# Per-team post-match team statistics (soccer)

`GET /v1/matches/{id}/statistics`

## Request

- Base URL: `https://api.bigballsdata.com`
- URL: `https://api.bigballsdata.com/v1/matches/{id}/statistics`
- Auth: one of:
  - API key in header `x-api-key`
  - HTTP bearer

Per-team team-stat line (possession, shots breakdown, fouls, corners,
offsides, cards, GK saves, passes, pass %, and xG where covered) from
the upstream feed, persisted hourly into `fixture_statistics`. Soccer-only;
the linescore stays authoritative for goals.
**Free plan or higher**: the box score is a raw record of the match, so
every factual field is free. One COMPUTED field is not: `expected_goals`
is omitted from both sides below the Solo plan. It is absent, never
null, so an omission is never mistaken for "no xG on record".
Stored-only: never fetches upstream. When a real match has no stats yet
(the upstream posts within ~30 min of full-time, covered leagues only) the
response is `200` with `meta.available=false` and null sides.

## Path parameters

- `id` string, uuid, required

## Response `200`

ok

- object: The standard response wrapper. Only `data` is guaranteed. `error` is absent (not null) on 16 of 40 live 200 responses measured 2026-09-18, so test for the KEY before reading it; this block declared it required until then. On a non-2xx, `error` is present and `data` is absent.
  - `data` object, required
    - `home` FixtureTeamStatistics: Post-match team stat line for one team. All stat fields are nullable: a metric a league doesn't track is null, and `expected_goals` (xG) is present only on covered leagues. Counts are integers; percentages and xG are numbers.
      - `team_id` string, uuid: A stored BigBalls id. This is the identifier every match, team, player and league endpoint returns, and the one every `{id}` path segment accepts. Take it from the `id` field of the corresponding list endpoint and use it as-is.
      - `team_name` string, nullable
      - `shots_on_target` integer, nullable
      - `shots_off_target` integer, nullable
      - `total_shots` integer, nullable
      - `blocked_shots` integer, nullable
      - `shots_inside_box` integer, nullable
      - `shots_outside_box` integer, nullable
      - `fouls` integer, nullable
      - `corner_kicks` integer, nullable
      - `offsides` integer, nullable
      - `ball_possession` number, nullable: percent 0-100
      - `yellow_cards` integer, nullable
      - `red_cards` integer, nullable
      - `goalkeeper_saves` integer, nullable
      - `total_passes` integer, nullable
      - `accurate_passes` integer, nullable
      - `pass_percentage` number, nullable: percent 0-100
      - `expected_goals` number, nullable: xG
    - `away` FixtureTeamStatistics: Post-match team stat line for one team. All stat fields are nullable: a metric a league doesn't track is null, and `expected_goals` (xG) is present only on covered leagues. Counts are integers; percentages and xG are numbers.
      - `team_id` string, uuid: A stored BigBalls id. This is the identifier every match, team, player and league endpoint returns, and the one every `{id}` path segment accepts. Take it from the `id` field of the corresponding list endpoint and use it as-is.
      - `team_name` string, nullable
      - `shots_on_target` integer, nullable
      - `shots_off_target` integer, nullable
      - `total_shots` integer, nullable
      - `blocked_shots` integer, nullable
      - `shots_inside_box` integer, nullable
      - `shots_outside_box` integer, nullable
      - `fouls` integer, nullable
      - `corner_kicks` integer, nullable
      - `offsides` integer, nullable
      - `ball_possession` number, nullable: percent 0-100
      - `yellow_cards` integer, nullable
      - `red_cards` integer, nullable
      - `goalkeeper_saves` integer, nullable
      - `total_passes` integer, nullable
      - `accurate_passes` integer, nullable
      - `pass_percentage` number, nullable: percent 0-100
      - `expected_goals` number, nullable: xG
  - `meta` object: Response metadata. NOTHING here is guaranteed: measured over 38 live envelopes on 2026-09-18, `cached` and `request_id` appeared in 29, `source` in 28, `cache_age_ms` in 9 and `confidence` in 6. This block previously declared all five REQUIRED, which no response has ever satisfied. Treat every field as optional and branch on its absence. Routes add their own keys here (`count`, `limit`, `offset`, `note`, `total`, and per-endpoint ones), so do not assume a closed set.
    - `date_window` object: Present when ?date= was applied. The exact window the filter used, so an omission at a day boundary is visible. start_utc is inclusive, end_utc exclusive. timezone is UTC, or the zone named by ?tz=, in which case the window is that local day. Boxing card dates are published event dates, so timezone and bounds are null there.
      - `date` string
      - `timezone` string, nullable
      - `start_utc` string, date-time, nullable
      - `end_utc` string, date-time, nullable
      - `note` string
    - `source` 'official-league' | 'aggregator-paid' | 'aggregator-free' | 'community-scraper' | 'forecast' | 'mixed' | 'cache' | 'none' | 'unknown-source' | 'stored' | 'catalogue' | 'gateway' | 'coverage-snapshot': Opaque source-tier label. Customers never see the vendor identity. `forecast` marks a weather forecast (GET /v1/matches/{id}/weather).
    - `confidence` number, float
    - `cached` boolean
    - `cache_age_ms` integer
    - `request_id` string
    - `fields_missing` string[]
    - `note` string: Short factual disclosure about something this response deliberately withheld, present only when there is something to disclose. On live scores it appears when `match_id` could not be resolved to a stored match and was therefore omitted: it gives the counts and points at `/v1/stored/matches`, whose ids you can call back into. Absent when every served id resolves. Distinct from `message`, which explains `coverage: false` or a stale response. This one is attached to a response that DID serve data and names the part of it that is deliberately missing.
    - `expected_goals_source` 'stored': Present only when `expected_goals` for one or both sides was served from our own stored xG because the box-score feed carried the rest of the line but not xG. Measured in production 2026-09-30: 17,115 team-sides in `fixture_statistics` hold a null `expected_goals` while our stored xG covers that same match and side, concentrated in the 2015-2022 seasons of EPL, La Liga and Serie A. The live 2025/26 big-five season is NOT among them (4 of 1,756 finished matches carry a null side and none of those four is fillable), so this fills history, not today. A value the box-score feed itself carried is never replaced.
  - `error` ApiError
    - `code` string, required
    - `message` string, required
    - `details` unknown

## Other responses

- `400`: invalid match id
- `404`: no match found with that id

---

[API](https://skmtc.dev/bigballsdata/apis/big-ball-sports-api.md) · [All operations](https://skmtc.dev/bigballsdata/apis/big-ball-sports-api/llms.txt) · [OpenAPI document](https://skmtc.dev/bigballsdata/apis/big-ball-sports-api/revisions/31fb54ac414e?raw)
