Quickstart
By the end of this page you’ll have a script that prints a live NFL game as it happens and survives a dropped connection without missing a play. It takes about ten minutes.
You need an API key from the Keys page at app.statpad.dev. Steps 1 to 4 work on every plan. Step 5 uses the WebSocket stream, which comes with Pro, Business and the Pro trial; Developer is REST only.
No game on right now? Every step works on a finished game too. Sample responses has a full recorded game you can replay through the same code without a key.
Keep your key on the server
Every request authenticates with your key as a bearer token. Put it in an environment variable rather than in code:
A key is a secret that draws on your whole account’s allowance. Call Statpad from your backend, never from a browser or a mobile app.
List your leagues
You get the leagues your plan includes. The alias is what goes in every other path: /v1/nfl/..., /v1/ncaaf/..., /v1/epl/....
Find a game
Ask for a day’s schedule. date is a calendar day in tz, which defaults to America/New_York, so a Monday night game is on Monday’s date:
Each game in data references its teams by id, and teams holds those teams, so one request is enough to render a scoreboard. Copy the id of the game you want.
Read the game
The response has the game, its live_state (score, period, clock and, for football, down, distance and yard line), both teams, the venue and one more field that matters for the next step:
That’s Monday night’s game with 7:02 left: Bills ball, 1st and goal at the Minnesota 5. yardline counts from the possessing team’s own goal line, so 95 means five yards from scoring.
seq is the position in the game’s stream that this response reflects. Hand it to the stream and you get exactly the changes after this response: nothing missed, nothing repeated.
The full response, untrimmed, is in Sample responses.
Follow it live
The stream is one WebSocket at wss://stream.statpad.dev/v1/stream. After the server’s welcome, send subscribe with the game’s stream name and the seq from step 4 as the resume point:
From then on, changes arrive in order. Each one carries an entity’s full current state, so you don’t merge fields; you replace what you hold under entity_type and entity_id. Here’s the touchdown, five changes after the resume point, trimmed to the fields this page uses (the real change also has at and payload_version, and the full play):
A game stream carries the game, its live state, plays, drives, game roster and player stats. Changes to teams and players, which many games share, go to league:nfl.
Reconnect without missing anything
Connections drop: deploys, mobile networks, laptops going to sleep. Because every change has a gapless seq, recovering is one rule: remember the last seq you applied, and resume from it.
- Reconnect with backoff. Wait about 1 second, then double up to 30 seconds, with some jitter. Resubscribe with
resumeset to your last seq and the server replays everything you missed, then carries on live. - Watch the heartbeat. The server sends
heartbeatevery 5 seconds. If you hear nothing for 15 seconds, the connection is dead even if the socket hasn’t noticed. Close it and reconnect. resync_requiredmeans your seq is older than the stream still holds (a game’s stream is kept for at least 7 days after it ends). Read the game over REST again and resume from theseqit returns.slow_consumermeans you fell behind and were dropped from that stream. Resubscribe from your last seq.
Most close codes mean “reconnect and resume”. The exceptions:
The whole client
This is everything above in one file: it reads the game, subscribes from its seq, applies changes, prints a line per update and reconnects with resume. It runs on Node 22.18 or newer, which runs TypeScript directly.
To see the reconnect work, turn your Wi-Fi off for a few seconds mid-game. The script prints the disconnect, then catches up from its last seq: every change you missed is applied once, in order, and the score line picks up where the game is. An internal or unavailable error closes the socket so the same backoff resumes it; errors that retrying can’t fix, such as forbidden, stop the script.
The stream is plain JSON over a WebSocket, so any language’s WebSocket library works the same way. Python’s websockets takes the key in additional_headers.