GET /odds or the live stream.
odds · Sportsbook grant: pinnacle
Query parameters
sport is required. A match list is only useful per sport, and requiring it means one cached, gzipped response can be reused for every caller granted that sport. Call GET /sports first for the ids you hold.Results are filtered by your key’s per-sport grants. With no
sport filter — including ?league=<id> on its own — matches in ungranted sports are silently absent. With ?sport=<id> for a sport you do not hold, this endpoint returns 403 {"error": "API key not permitted for sport <id>"}. Match ids you discover here are always safe to pass to GET /odds and to WS /data subscribes.Request
What the response contains
By default the response is scoped to what you almost certainly want:- Games — every top-level matchup for the sport.
- Live derivative twins — when a fixture goes in-play the sportsbook mints a new matchup id for the live book and parents it to the prematch one. Those in-play children are always listed, because a parent-only filter would hide the entire live book. Each carries
parent_idback to its prematch entry. - Parentless specials — a special with no parent (an outright, for example) is itself top-level and is always listed.
specials_count and live_specials_count, so you can tell at a glance how many derivatives a game has and how many of them are currently in play.
Pass ?include=specials to return the entire catalog — every derivative matchup, not just the in-play twins. On a busy card the difference is large (a live baseball sport goes from ~17 KiB by default to ~479 KiB with include=specials), so only opt in when you actually want to enumerate the whole book.
Response
A bare JSON array, sorted ascending byid. No matches held returns [].
Match object
Participant object
Specials
A special is a derivative market that the sportsbook models as its own matchup — player props, correct score, winning margin, odd/even, and similar. A parent game’s own book does not contain them; each derivative has its own id, its own participants, and its own markets. The split between a game and its derivatives is onparent_id, not on type. A child’s type varies by sport:
- On baseball and most sports, derivatives are
type:"special"with aspecialdescription. - On tennis, many derivatives are
type:"matchup"carryingunits:"Games"orunits:"Sets"— around half of the tennis catalog in practice.
specials_count, and both are included when you pass ?include=specials. To tell whether an entry is a derivative, check parent_id rather than type.
Reading a game and its derivatives
There are three ways to pull the derivatives for one game:- Read
specials_countandlive_specials_counton the game in the default list, then follow up withGET /odds?match=<game>&include=specialsto fetch the game’s own selections and every derivative’s selections in one call. - Ask for the whole catalog with
?include=specialshere, then filter byparent_id. - Subscribe to the game on the live stream — subscribing to a game covers its derivatives too.
Caching, gzip, and ETag
Because this response is metadata rather than prices, it is safe to serve from a short-lived cache — and it is the only Passthrough endpoint that is cached.- Cache TTL. Responses are reused for up to
PINNODDS_REST_CACHE_TTL(default 1 second) across every client granted that sport. Worst-case staleness is therefore one TTL, and only for fields likeis_live,specials_count, and the match list itself. Prices never appear here, so the trade-off is bounded and safe. - Gzip. Responses above
PINNODDS_REST_GZIP_MIN_BYTES(default 1 KiB) are compressed if you sendAccept-Encoding: gzip. On a live baseball card this is a roughly 17× reduction — 212 KB raw to about 13 KB on the wire. - ETag. Every response carries an
ETagheader. Send it back on the next request asIf-None-Matchand the server answers304 Not Modifiedwith an empty body when the response would be identical.
GET /odds and the WebSocket are always live.