---
title: "Live per-player boxscore"
method: GET
path: "/v1/live-stats/{sport}/{matchId}/players"
tags: ["live-stats"]
---

# Live per-player boxscore

`GET /v1/live-stats/{sport}/{matchId}/players`

## Request

- Base URL: `https://api.bigballsdata.com`
- URL: `https://api.bigballsdata.com/v1/live-stats/{sport}/{matchId}/players`
- Auth: one of:
  - API key in header `x-api-key`
  - HTTP bearer

Per-player stat groups for one match, during the game and after it
finishes. One response shape for every sport listed below; cached 15s.

COVERAGE, PER SPORT, STATED RATHER THAN IMPLIED. These sports are not
at parity and this endpoint does not pretend they are.

- `baseball`: broad demonstrated coverage.
- `basketball`, `ice_hockey`: covered for the primary professional
  league only (NBA, NHL). Other leagues return a 400 naming the league.
- `football`: soccer, and only the mapped competitions (EPL, MLS,
  FIFA World Cup 2026). Anything else returns a 400 naming the league.
- `american_football`: **NFL and NCAAF (FBS) only**, from the 2026
  season opener. NCAAF FCS matches are a valid request that will keep
  returning an empty box score: the live feed does not poll that
  division, and naming it here is cheaper than letting a caller
  discover it by waiting. Before kickoff of any covered game the
  request also succeeds with `data.teams: []`, `meta.stale: true` and
  a `meta.note` saying so. It is NOT a 404: a match with no rows yet
  is a correct request whose data has not been written, and the two
  are different answers.

FRESHNESS. `meta.as_of` is the timestamp of the most recent stat cell
in the response, and `meta.stale` says whether we would expect to have
learned something more recently than that. A box score that has stopped
updating mid-game and one that is simply quiet are indistinguishable
without those two fields, which is why they are here rather than left
for the caller to infer from `fetched_at`.

## Path parameters

- `sport` 'basketball' | 'baseball' | 'ice_hockey' | 'football' | 'american_football', required
- `matchId` string, uuid, required

## Response `200`

ok

- object
  - `data` object
    - `match_id` string, uuid
    - `sport` string
    - `status` 'scheduled' | 'in_progress' | 'finished'
    - `teams` object[]
      - `team_name` string, nullable
      - `stat_groups` object[]
        - `type` string, nullable
        - `keys` string[]
        - `players` object[]
          - `player_id` string, uuid, nullable: Big Balls player UUID, resolvable against /v1/players/{id}. Null when the player is not yet linked to a player record; `name` is still populated.
          - `name` string, nullable
          - `starter` boolean, nullable: Null on `american_football`; that path does not carry it, and a guessed `false` would be worse than an honest null.
          - `position` object, nullable: Null on `american_football`, for the same reason as `starter`. Elsewhere an object carrying the position's id, name, display name and abbreviation. Measured 2026-09-25 over the two sports that had any players on the day: 67 of 67 `american_football` rows null, 31 of 31 `baseball` rows the four-field object. `basketball`, `ice_hockey` and `football` returned 0 players, so they are UNMEASURED here, not confirmed; no `required` is declared and extra keys are permitted, because this value is passed through from the upstream boxscore.
            - `id` string
            - `name` string
            - `displayName` string
            - `abbreviation` string
          - `stats` string[]: Positionally aligned to the group's `keys`, one entry per key, with an empty string where this player has no value for that key. Index into it by the position of the key you want; never assume a shorter array is truncated rather than shifted.
  - `meta` object
    - `source` string
    - `fetched_at` string, date-time
    - `cached` boolean
    - `cache_age_ms` integer
    - `as_of` string, date-time, nullable: Timestamp of the freshest stat cell in this response, or null when the response carries none. Distinct from `fetched_at`, which is when WE answered.
    - `stale` boolean: True when `as_of` is null or older than 120 seconds. A stale response is still served; this is a disclosure, not a refusal.
    - `note` string: Present only when `teams` is empty, explaining in plain English that no per-player rows have been written for this match yet and when coverage begins.

## Other responses

- `400`: unsupported sport, sport/match mismatch, or an uncovered league
- `404`: match not found, or no live-stats coverage for it. An empty box score is NOT a 404; see the 200 above.
- `502`: upstream failure

---

[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)
