API v2
Canonical football resources, signed cursor pagination, Problem Details, and authenticated account writes.
API v2 is the canonical StatsHub contract for new integrations. It groups data around domain resources, uses stable response envelopes, and documents every operation with closed OpenAPI schemas.
https://statshub.com/api/v2The legacy /api contract remains available unchanged.
Two tiers, one surface
Every capability a StatsHub client can reach on /api is reachable on
/api/v2. They are not all served the same way, and the difference decides how
much work a migration is.
Designed resources are the ones this page describes: cursor pagination,
{data, page, links}, opaque string ids, RFC 3339 timestamps, closed schemas.
They were built for v2 rather than ported, so their payloads differ from the
legacy route they replace, and moving to one means updating response parsing.
Bridged capabilities are the rest. Each is the legacy handler reached at a
canonical v2 URL — /api/team/{id}/roster answers at
/api/v2/teams/{teamId}/roster — with the payload unchanged, byte for byte.
Errors are still v2 errors: the whole /api/v2 subtree normalizes an error body
into Problem Details on the way out. Moving to one of these is a URL change and
nothing else.
Of 215 operations, 81 are designed and 134 are bridged.
Nothing marks the tiers apart in the OpenAPI document. Tell them apart by the
response: a designed resource returns {data} or {data, page, links} with
string ids and RFC 3339 timestamps, and a bridged one returns the legacy shape.
A bridged route can graduate to the designed tier. Its URL is already canonical, so that is a payload change under a stable path. Graduating is not only a reformatting — the designed resource is free to answer a better question, and several have:
| Capability | What changed on the way across |
|---|---|
| Fixture formations | Confirmed, projected and last-known resolve in one request, and one statement. Clients used to ask three endpoints and fall through. |
| Card rankings | /rankings/{player,team,referee}-cards take the competition as a parameter. The legacy boards were pinned to one tournament by a season id and a hard-coded referee list. |
| Player season statistics | Seventy flat keys, counts as numbers and rates as strings, become typed groups with a source saying which feed answered. |
| Team player statistics | A stat map keyed by fixture id becomes one flat row per player and fixture, which is what makes it pageable. |
| Bet tracker | The account-wide P&L splits out of the list envelope it was recomputed in on every page turn. |
| Commentary statistics | A GET. It was a POST because the fixture ids are a list. |
| Betting history | Repeated bracket=min:max:label instead of a JSON-encoded string parameter. |
What is on /api and not on /api/v2 is the operational surface: the admin
and ingestion consoles, Stripe, the Telegram webhook, the MCP transport, the XML
sitemaps and the tweets proxy. None of those is a resource, and several do not
answer JSON at all.
Both tiers are listed in the v2 endpoint reference.
Resource vocabulary
| Legacy term | V2 resource |
|---|---|
| unique tournament | competition |
| event | fixture |
| category | region |
| tournament | season phase |
Nested routes express ownership. Examples include
/competitions/{competitionId}/seasons,
/seasons/{seasonId}/standings, and
/fixtures/{fixtureId}/incidents.
Response envelopes
A single resource is returned under data:
{
"data": {
"id": 17,
"name": "Premier League"
}
}Cursor-paged collections add navigation state under page and resolvable URLs
under links:
{
"data": [],
"page": {
"limit": 25,
"nextCursor": "eyJ2IjoxLCJrIjp...",
"previousCursor": null,
"hasNext": true,
"hasPrevious": false
},
"links": {
"self": "/api/v2/fixtures?limit=25",
"next": "/api/v2/fixtures?after=eyJ2IjoxLCJrIjp...&limit=25",
"previous": null
}
}Cursor pagination
Paged collections accept:
| Parameter | Meaning |
|---|---|
limit | Page size from 1 to 100. |
after | Opaque cursor for the next page. |
before | Opaque cursor for the previous page. |
after and before are mutually exclusive. Cursors are signed and bound to
the collection's filters and sort order; clients should store and replay them
without decoding or editing them.
Ranked analytical read models expose conventional page and pageSize
parameters when users need stable page numbers across arbitrary sort columns.
They live under /rankings; their response schemas define the accompanying
page metadata precisely.
Analytical resources
| Prefix | Purpose |
|---|---|
/rankings | Price outliers, prop trends, hit rates, referee performance, and substitute impact. |
/predictions | Model-generated team statistic projections. |
/data-coverage | Fixture-level ingestion coverage and aggregate coverage summaries. |
/lineup-projections | Predicted or confirmed starters across upcoming fixtures. |
/matchday-fixtures | The matchday board's fixture read model and competition facets. |
/value-bets | Model-backed market selections whose offered prices exceed fair value. |
Errors
V2 failures use RFC 9457 Problem Details with
application/problem+json:
{
"type": "https://statshub.com/problems/invalid-request",
"title": "Invalid request",
"status": 400,
"detail": "limit must be between 1 and 100",
"instance": "/api/v2/fixtures",
"code": "INVALID_PAGINATION",
"requestId": "2d8bd660f45b"
}Treat type or code as the machine-readable error category and detail as
diagnostic text. Do not branch on the prose in detail.
Authentication and writes
Public football data is readable without a session. /me and favorite
resources require either a Supabase bearer token or the existing Supabase
session cookie. Authenticated clients can create, read, update, and delete
favorite teams and players with POST, GET, PATCH, and DELETE.
Send JSON writes with Content-Type: application/json. Unknown body fields are
rejected, ownership is derived from the authenticated user, and a client cannot
read or mutate another user's favorites.
OpenAPI
Use the focused document for SDK generation:
https://statshub.com/api/openapi-v2.json
https://docs.statshub.com/openapi-v2.jsonThe OpenAPI contracts page lists the combined document for the narrower case where one client intentionally calls both versions.