Errors
Every error carries a code. Branch on it: code values are stable, while title, detail and message are for people and can change.
REST errors
A failed request answers with an RFC 9457 problem, as application/problem+json:
type links to this page’s entry for the code. request_id is also in every response’s X-Request-Id header; include it when you report a problem. resync_required adds min_seq, and quota_exhausted adds resets_at.
Errors don’t count against your monthly allowance. Only 2xx and 304 responses do.
Stream errors
On the WebSocket, an error is a message, and the connection stays open:
id echoes the id of the message that caused it, if you sent one. With stream set, only that subscription is affected, and you’re no longer subscribed to it. Problems with the connection itself close the socket instead; see close codes.
Error codes
bad_request
The request is malformed. Over REST, a parameter is missing or invalid: a date that doesn’t parse, a limit out of range, a cursor that isn’t a next_cursor from the same list, an after_seq beyond the stream’s latest change, or a required parameter left out, such as since on /transactions. On the stream, a message isn’t a JSON object with a known type, its id is over 64 characters, a subscribe names no streams or too many, or a stream name isn’t a well-formed game:<game id> or league:<alias>. detail or message says which. Sending the same request again fails the same way.
unauthorized
The request has no API key, or the key is unknown or revoked. Send it as Authorization: Bearer <key>. The response carries a WWW-Authenticate header, with error="invalid_token" when a key was sent but not accepted. On the stream, a bad key closes the connection with 1008 before welcome. Check that the key is current on the Keys page of your account, and see API keys.
forbidden
The key is valid, but the account can’t read this. Either the account has no active access, because its trial ended, its subscription lapsed or it’s suspended, or its plan doesn’t include the league or feature. A league alias Statpad doesn’t serve also gets forbidden, over REST and on the stream, so a typo in the league looks the same as a league you don’t have. On the stream, a plan without streaming, such as Developer, gets forbidden and then a 1008 close, and losing access to a league ends its subscriptions with forbidden. Check your plan and leagues in your account; retrying won’t help until they change.
not_found
No such entity: the game, team, player or season in the path doesn’t exist in that league. A path that isn’t an endpoint at all answers 404 too. Depth charts also answer 404 today; see Coverage by league.
resync_required
The position you asked to continue from is older than the history Statpad still keeps for that stream. min_seq is the oldest seq still held. Over REST, GET /games/{id}/changes answers 410 when the changes after after_seq are no longer all kept. To recover a game, read it over REST and continue from the seq it returns. For any stream, you can also subscribe again without resume to get a fresh snapshot. Game streams keep their history for at least 7 days after the game ends, and league streams for at least 30.
invalid_resume
A resume seq is beyond the stream’s latest change, so it can’t be a position the stream gave you. This usually means a seq saved for a different stream. Subscribe without resume for a snapshot, or read the game over REST and resume from its seq.
slow_consumer
Your client fell behind and the subscription’s send buffer filled, so the server dropped you from that stream. resume_from is the last seq it delivered. Subscribe again with resume set to it (or to the last seq you applied) and the journal catches you up. If it keeps happening, handle messages faster: apply changes and hand slow work to a queue instead of doing it in the message handler.
too_many_subscriptions
There are two limits. The account’s plan caps subscriptions across every key and connection on the account: 10 on the trial, 20 on Pro and 150 on Business. Separately, one connection holds at most 100 subscriptions, whatever the plan; welcome reports it as max_subscriptions. Unsubscribe from streams you’ve finished with, such as games that have ended. Past the account limit, move to a plan with more; past the connection limit, open another connection.
unknown_stream
No game has the id in a game:<game id> stream name. Check it against GET /v1/{league}/games. A league alias Statpad doesn’t serve gets forbidden instead, and a name that isn’t well formed gets bad_request.
rate_limited
You sent requests faster than your plan’s rate. Wait Retry-After seconds, then send more slowly. See Rate limits.
quota_exhausted
The account has used this billing period’s request allowance. resets_at says when the next period starts, and Retry-After gives the same moment in seconds. Wait for it, or upgrade your plan. See Rate limits.
internal
Something failed on Statpad’s side. Retry with exponential backoff. On the stream, the subscription has ended: subscribe again, resuming from the last seq you applied. If it persists, report it with the request_id, or for the stream, the connection_id from welcome.
unavailable
Statpad couldn’t check your account or its limits, so it didn’t serve the request rather than serve it unmetered. Over REST, wait Retry-After seconds and retry. On the stream, the capacity check for a subscription failed and the connection stays open: subscribe again after a short wait.
Close codes
When the server closes the WebSocket, the close code says why and whether to reconnect. Reconnect with backoff and resume from the last seq you applied, except where the table says not to.
The Quickstart client’s reconnect loop follows this table.