GET /odds, and in snapshot and update frames on the live stream.
The sportsbook’s own format nests prices inside markets inside matchups, with outcomes referenced by id. The passthrough flattens all of that into a list of selections with names already resolved, so you do not have to reimplement the sportsbook’s labelling rules.
Fields
market_key
The key is the sportsbook’s own market identifier, passed through as an opaque string. It encodes the period and market type, and the line where one applies:
All selections sharing a
market_key share a version, status, cutoff_at, and limit, because those are properties of the market rather than the individual outcome.
An alternate line is its own market
Because the line is part of the key,s;0;s;-2.5 and s;0;s;-3.5 are two separate markets with independent versions. A line move arrives as a new market key, not as a changed line on an existing one — and the old key arrives in removed_market_keys when the sportsbook withdraws it.
market_name and period
market_name is composed as <period label> - <market label>, both taken from the sportsbook’s own label table rather than generated:
20 is Map 1 Round 3 and period 66 is Map 2 Round 13. Use market_name for display and period only as an identifier; do not derive one from the other.
On a special, market_name is the special’s own description instead — Correct Score, (Map 2) Winning Margin — since that description already carries the full context.
The label table loads at startup. In the first moments of a cold start, before it is available, names fall back to a generic form such as
Period 3 - Money Line. Names never invent a round number they cannot look up.bet_name
How the outcome is named depends on the market type:
Outcomes the sportsbook keys by participant id — which includes every special, and some money lines — are resolved to the participant’s name. If a name genuinely cannot be resolved, the fallback is the raw designation rather than an empty string.
price and decimal
price is American odds, exactly as the sportsbook publishes them. decimal is the same price converted, rounded to three decimal places, provided so you do not have to convert it yourself:
decimal is a convenience derived from price. Where the two could ever disagree by rounding, price is authoritative.
status
A locked selection keeps its last known price. That is intentional — it lets you keep rendering the board through an in-play suspension instead of blanking it.
Two things lock a market
The upstream sportsbook only ever publishes the first of these, so the passthrough resolves both for you:- The market’s resolved status is not
open— either the market’s own status, or the matchup’s period status when the market does not carry one. cutoff_athas passed relative to the current server time.
status: open forever with a cutoff in the past. Without the cutoff check those stale prematch quotes would look bettable indefinitely; with it, any client that trusts status alone is safe. You do not have to police stale prematch quotes yourself.
Every GET /odds response and every stream frame also carries a top-level ts (epoch milliseconds) — the moment SportriX last applied a change to that match. If ts is not advancing, no new information has arrived for that match, and staleness is visible without any extra bookkeeping.
cutoff_at
RFC3339 UTC timestamp for when betting closes on this market, always normalised to a Z suffix (upstream spells the same instant as +00:00 on markets and Z on periods; both come out as Z here). Present on every selection, straight and special alike. If the market itself does not carry its own cutoff, the matchup’s period-level cutoff is used as a fallback so the field is always populated.
Use cutoff_at to:
- Detect stale prematch markets. A cutoff in the past is the passthrough’s signal that this market has been abandoned in favour of a live twin — the same signal that flips
statustolocked. - Show a countdown. Render the remaining time until the market closes.
- Sort or filter markets by close time, for example to surface markets closing soonest.
limit
The maximum risk stake the sportsbook will accept on a single bet on this market, expressed as an integer in the account’s currency. Present on every selection.
Limits vary widely — even within one match. For example, a response can carry a limit of 100 on first-half markets, 250 on team totals, 500 on match totals, and 1000 on match handicaps and 1X2. These values are illustrative, not fixed bands; always use the value returned for the current market.
A client sizing a bet must read limit per market rather than assuming a match-wide, sport-wide, or account-wide constant. Use it to cap the stake you offer a user, gray out a stake input that exceeds it, or decide whether a market is deep enough to be worth surfacing at all.
version
The monotonic version of the market this selection came from. It is the mechanism for correct state updates:
- Deduplicate. Ignore a selection whose
versionis not greater than the version you already hold for thatmarket_key. - Detect staleness. A version that stops advancing while the feed is healthy means a quiet market, not a lost one.
market_key. Comparing versions across markets is meaningless.
Match objects also carry a
version. That is the match-level version and moves independently of any market’s. Do not compare the two.