> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://statpad.dev/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://statpad.dev/docs/_mcp/server.

# Sample responses

> Untrimmed Statpad API responses and a full change journal from real games, free to download and replay.

Everything on this page is what the API returned for two real games, untrimmed. Read the files to see the shapes before you sign up, test your parser against them, or replay the NFL game's journal through your client to see a whole game arrive change by change. None of it needs a key, and none of it starts your trial.

## The games

|                | Game                                                               | What's recorded                                                                                                                                                         |
| -------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| NFL            | **Bills 23 at Vikings 6**, Monday Night Football, November 9, 2026 | The whole game, polled every 3 seconds from before kickoff to after the final: 187 plays, 25 drives, 68 players and 1,277 changes, plus a snapshot from the 4th quarter |
| Premier League | **Manchester United 4, Aston Villa 4**, November 8, 2026           | The finished match: 106 events, both squads and every player's stats                                                                                                    |

The files were made by replaying the recorded upstream responses through Statpad's real ingest pipeline into a fresh database and requesting each endpoint from the real API, so they're the exact bodies you'd get. Two things differ from production: the ids are that database's, and every `updated_at` and change `at` is when the sample was generated, not when the game was played.

## Files

#### NFL: Bills at Vikings

| File                                                                                    | Same as                                                                                                                | Size   |
| --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------ |
| [`game-live.json`](https://statpad.dev/samples/nfl-bills-at-vikings/game-live.json)     | `GET /v1/nfl/games/{id}` with 7:02 left in the 4th quarter                                                             | 2 KB   |
| [`game.json`](https://statpad.dev/samples/nfl-bills-at-vikings/game.json)               | `GET /v1/nfl/games/{id}` after the final                                                                               | 2 KB   |
| [`plays.json`](https://statpad.dev/samples/nfl-bills-at-vikings/plays.json)             | `GET /v1/nfl/games/{id}/plays`, all 187 plays                                                                          | 230 KB |
| [`play-groups.json`](https://statpad.dev/samples/nfl-bills-at-vikings/play-groups.json) | `GET /v1/nfl/games/{id}/play-groups`, all 25 drives                                                                    | 16 KB  |
| [`boxscore.json`](https://statpad.dev/samples/nfl-bills-at-vikings/boxscore.json)       | `GET /v1/nfl/games/{id}/boxscore`                                                                                      | 34 KB  |
| [`roster.json`](https://statpad.dev/samples/nfl-bills-at-vikings/roster.json)           | `GET /v1/nfl/games/{id}/roster`, with the players                                                                      | 55 KB  |
| [`changes.ndjson`](https://statpad.dev/samples/nfl-bills-at-vikings/changes.ndjson)     | Every change on `game:{id}`, one per line: what `GET /v1/nfl/games/{id}/changes` pages through and the stream delivers | 839 KB |

#### Premier League: Man United v Aston Villa

| File                                                                                 | Same as                                           | Size   |
| ------------------------------------------------------------------------------------ | ------------------------------------------------- | ------ |
| [`game.json`](https://statpad.dev/samples/epl-man-utd-aston-villa/game.json)         | `GET /v1/epl/games/{id}` after full time          | 2 KB   |
| [`plays.json`](https://statpad.dev/samples/epl-man-utd-aston-villa/plays.json)       | `GET /v1/epl/games/{id}/plays`, all 106 events    | 134 KB |
| [`boxscore.json`](https://statpad.dev/samples/epl-man-utd-aston-villa/boxscore.json) | `GET /v1/epl/games/{id}/boxscore`                 | 26 KB  |
| [`roster.json`](https://statpad.dev/samples/epl-man-utd-aston-villa/roster.json)     | `GET /v1/epl/games/{id}/roster`, with the players | 32 KB  |

#### Other

| File                                                         | What it is                                      |
| ------------------------------------------------------------ | ----------------------------------------------- |
| [`leagues.json`](https://statpad.dev/samples/leagues.json)   | `GET /v1/leagues` for a key with every league   |
| [`manifest.json`](https://statpad.dev/samples/manifest.json) | The games and files above, as JSON, for scripts |

The files allow requests from any origin, so you can fetch them from a browser too:

```bash
curl -s https://statpad.dev/samples/nfl-bills-at-vikings/game-live.json | jq .live_state
```

## A game in progress

`game-live.json` is the game with 7:02 left in the 4th quarter, the Bills up 13 to 6 with 1st and goal at the Minnesota 5. This is its `live_state`, unedited:

```json
{
  "away_score": 13,
  "basketball": null,
  "clock_running": null,
  "clock_seconds": 422,
  "football": {
    "away_timeouts": 3,
    "down": 1,
    "home_timeouts": 2,
    "yardline": 95,
    "yards_to_go": 5
  },
  "game_id": "01a1d60f-67be-7bdd-83f4-53286521d9bb",
  "home_score": 6,
  "last_play_id": "01a1d60f-8d90-7a74-8905-46b0dc0d2751",
  "period": 4,
  "period_half": null,
  "possession_team_id": "01a1d60f-67be-7bdf-ac2f-bcdeeac1da68",
  "soccer": null,
  "updated_at": "2026-11-13T17:58:25.937181Z",
  "version": 264
}
```

Its `seq` is 1101. Line 1,102 of `changes.ndjson` is the first change after it, which is where the [Quickstart](/docs/quickstart) client would resume. Four changes later, at seq 1106, James Cook runs it in.

## Replay the game

`changes.ndjson` is the game's whole journal, in `seq` order, exactly as the stream delivers it to a client that resumes from seq 0. Feeding it through your code is the closest thing to watching the game live, any day of the week.

This script plays it back and prints each play as it lands. It keeps the same map as the Quickstart client, keyed by entity type and id, with the newest version winning, so it also shows corrections arriving:

**`replay.mts`**

```ts title="replay.mts" maxLines=0
const BASE = "https://statpad.dev/samples/nfl-bills-at-vikings";
const rate = Number(process.argv[2] ?? 20);

const detail = await (await fetch(`${BASE}/game.json`)).json();
const alias = new Map([detail.home_team, detail.away_team].map((t) => [t.id, t.alias]));
const home = alias.get(detail.game.home_team_id);
const away = alias.get(detail.game.away_team_id);
const lines = (await (await fetch(`${BASE}/changes.ndjson`)).text()).trim().split("\n");

// The same map the quickstart keeps: entity_type:entity_id -> the newest version seen.
const state = new Map<string, { version: number; payload: any }>();
const clock = (s: number | null) => (s == null ? "" : `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`);

for (const line of lines) {
  const c = JSON.parse(line);
  await new Promise((resolve) => setTimeout(resolve, 1000 / rate));
  const key = `${c.entity_type}:${c.entity_id}`;
  const held = state.get(key);
  if (held && held.version >= c.version) continue;
  state.set(key, { version: c.version, payload: c.op === "delete" ? null : c.payload });

  if (c.entity_type !== "play" || c.op !== "upsert") continue;
  const p = c.payload;
  const score = `${away} ${p.away_score ?? "-"}, ${home} ${p.home_score ?? "-"}`;
  const note = held ? "  (corrected)" : "";
  console.log(`#${c.seq}  Q${p.period} ${clock(p.clock_seconds).padStart(5)}  ${score}  ${p.description}${note}`);
}
```

It needs no packages. Run it with Node 22.18 or newer; the argument is changes per second, so the default of 20 plays the game in about a minute:

```bash
node replay.mts
```

```text
#10  Q1 15:00  BUF 0, MIN 0  W.Reichard kicks 59 yards from MIN 35 to BUF 6. G.Dortch to MIN 36 for 58 yards (I.Pace).
#18  Q1 14:52  BUF 0, MIN 0  J.Cook right guard to MIN 35 for 1 yard (D.Turner).
#23  Q1 14:52  BUF 0, MIN 0  J.Cook right guard to MIN 35 for 1 yard (D.Turner; E.Wilson).  (corrected)
#25  Q1 14:13  BUF 0, MIN 0  PENALTY on BUF-S.Brown, False Start, 5 yards, enforced at MIN 35 - No Play.
...
#1106  Q4  6:17  BUF 19, MIN 6  J.Cook up the middle for 5 yards, TOUCHDOWN.
#1111  Q4  6:17  BUF 20, MIN 6  J.Cook up the middle for 5 yards, TOUCHDOWN. T.Bass extra point is GOOD, Center-R.Ferguson, Holder-T.Doman.  (corrected)
...
#1275  Q4  0:00  BUF 23, MIN 6  END GAME
#1277  Q1  2:19  BUF 0, MIN 0  J.Cook left end to MIN 21 for -2 yards (E.Wilson).Penalty on BUF-T.Grable, Offensive Holding, declined.  (corrected)
```

Corrections are normal. In this game, 83 of the 270 play changes rewrite a play that already arrived: a second tackler added, a touchdown that gains its extra point, a first-quarter penalty noted after the final whistle. Each is an `upsert` with a higher `version`, so replacing by id keeps you right without special handling.

To run your real client against the file, wrap each line in the message the stream sends, `{ "type": "changes", "stream": "game:<id>", "changes": [change] }`, and pass it to the handler you use for the WebSocket.

## Replay a recent game on the API

With a key, a game replays the same way on the real API, from the first change, for at least 7 days after it ends:

* **Over the stream**, subscribe with `resume` set to `0`: `{ "type": "subscribe", "streams": ["game:<id>"], "resume": { "game:<id>": 0 } }`. The server sends every change from the start, then carries on live if the game is still going. Replay doesn't use your request allowance.
* **Over REST**, page `GET /v1/{league}/games/{id}/changes?after_seq=0&limit=1000`, passing the last `seq` of each page as the next `after_seq`. Each page counts as one request against your allowance.

From a position older than what's retained, the stream sends `resync_required` and `/changes` answers `410`. The game's plays, drives and box score are still there over REST.

## What to expect in the data

These samples show the API as it is today, including the fields nothing fills yet. A few you'll notice:

* `period_scores` and the box score's `teams` are empty lists. Add up the player lines for team totals.
* In the NFL game, `clock_running` is `null`: the football feed has no running flag.
* After the final, `live_state.football` keeps the last down, distance and yard line, and its timeouts are `null`, because the feed stops reporting them.
* NFL plays have an empty `participants` list; the `description` names the players.
* Premier League events have no `group_id` or `clock_seconds`. Their time is `soccer.minute` and `soccer.added_minute`.

[Coverage by league](/docs/coverage) lists every field and endpoint that isn't filled yet, per league.

> **Note**
>
> The Premier League match is a finished-match sample, so it has no journal to replay. A recorded Premier League match will join the NFL game here.