---
title: "NFL play-by-play with EPA + Win Probability"
method: GET
path: "/v1/nfl/games/{id}/plays"
tags: ["nfl"]
---

# NFL play-by-play with EPA + Win Probability

`GET /v1/nfl/games/{id}/plays`

## Request

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

**Pro plan or higher**: all NFL play-by-play (the full play/type/
down/distance/EPA/win-probability event log) requires Pro; Free /
Solo / Trio / Edge receive a 403 naming the tier and linking the docs
and pricing pages. NBA play-by-play carries the same Pro floor; it
started at Edge until 2026-09-18.

Each play also carries (migration 283): passer/receiver/rusher
attribution; kicker/punter/punt-returner/kickoff-returner attribution
(`kicker_player_id` is used on both field-goal and kickoff attempts);
per-play `air_yards`/`yards_after_catch`; scoring, turnover and
penalty attribution (`touchdown`, `interception`, `fumble`, `sack`,
`penalty` and the player/team ids behind each); and situational
context (`qtr`, `game_seconds_remaining`, `goal_to_go`). Boolean
indicators are `null`, not `false`, when the play was not classified
for that indicator. `pass_attempt`/`complete_pass`/`field_goal_attempt`/
`field_goal_result`/`kick_distance` (migration 255) are also included.

## Path parameters

- `id` string, required

## Query parameters

- `play_type` string
- `min_epa` number
- `down` integer
- `limit` integer
- `offset` integer

## Response `200`

ok

- object
  - `data` object[]
  - `pagination` object
    - `total` integer
    - `limit` integer
    - `offset` integer
  - `meta` object: Present ONLY when the request returned no plays AND the game itself exists, so an empty array always explains itself. `coverage` is false when we hold no play-by-play for the game yet, and true when the game does have plays but your play_type/down/min_epa filters matched none of them (the message then says how many it has). Paging past the end carries no meta, because that page is empty for a reason the caller already knows. An id that names no game is a 404, not an empty array.
    - `source` 'stored'
    - `coverage` boolean
    - `message` string

## Other responses

- `400`: invalid game id format. Carries a suggested_fix naming the SEASON_WEEK_AWAY_HOME shape and an id that works today.
- `403`: plan_required (Pro or higher)
- `404`: game not found. The id is well formed but names no game we hold, which is what swapping the away and home team produces. An empty plays array is NOT a 404, see the 200 above.

---

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