Odds
Live order books for a single match, one-shot snapshots or streaming deltas.
The odds resource is dedicated to live betting markets, kept separate
from Match: customers who only render scores and stats pay zero
bandwidth for an order book they don't use.
Requires the Business tier. Upgrade →
odds.list
One-shot snapshot of every live market for a single match. Backed by the
same in-memory cache as odds.subscribe's initial snapshot: typical
latency well under 100ms.
const markets = await mrdoge.odds.list({
matchId: "46215510",
locale: "es",
});
for (const market of markets) {
console.log(market.betType, "-", market.lines.length, "lines");
for (const line of market.lines) {
console.log(` ${line.code} (${line.caption}) @ ${line.price}`);
}
}Params:
| Field | Type | Notes |
|---|---|---|
matchId | string | Required |
betTypes | string[] | Restrict to one or more market sysnames (e.g. ["SOCCER_MATCH_RESULT", "SOCCER_UNDER_OVER"]) |
select | MarketSelect | Field selector, see selectors |
locale | string | Localizes each line's caption |
Returns: Market[], every available live market for the match.
odds.subscribe
WebSocket subscription pushing the full latest markets array on every change. State-snapshot semantics: clients replace, never merge.
const sub = await mrdoge.odds.subscribe({
matchId: "46215510",
betTypes: ["SOCCER_MATCH_RESULT"],
});
// Initial snapshot (already populated from the cache cold-start)
console.log("Snapshot:", sub.snapshot.length, "markets");
// Live updates, the full markets array on every change
sub.on("odds.upd", (markets) => repriceLines(markets));
// Clean shutdown when the match completes
sub.on("closed", ({ reason, message }) => {
if (reason === "data_unavailable" && message === "match_completed") {
teardownUi();
}
});
await sub.cancel();Params:
| Field | Type | Notes |
|---|---|---|
matchId | string | Required |
betTypes | string[] | Restrict pushes to specific market sysnames |
select | MarketSelect | Field selector applies to snapshot AND every push |
locale | string | Localizes each push's line captions |
Returns: Subscription<"odds.subscribe">, see
subscriptions.
Push events: odds.upd, the full Market[] for the match.
The Market shape
type Market = {
id: string;
/** Market sysname, e.g. "SOCCER_MATCH_RESULT", "SOCCER_UNDER_OVER". */
betType: string;
lines: Line[];
};
type Line = {
id: string;
/** Stable outcome identifier (`"1"`, `"X"`, `"2"`, `"O2.5"`, etc.). Locale-independent. */
code: string;
/** Formatted display label, localized per `locale`. See Localization below. */
caption: string | null;
/** Decimal odds (e.g. 2.10). */
price: number;
isAvailable: boolean;
/** ISO timestamp of the last odds update. Prelive: per-line DB write time. Live: feed ingestion time. */
updatedAt?: string;
};Filtering by betTypes
Pass betTypes to restrict the response to specific market sysnames.
Matching is case-insensitive against Market.betType:
// Just the headline markets
await mrdoge.odds.list({
matchId,
betTypes: [
"SOCCER_MATCH_RESULT",
"SOCCER_UNDER_OVER",
"SOCCER_BOTH_TEAMS_TO_SCORE",
],
});For odds.subscribe, the filter applies to every push: you don't
re-broadcast filtered-out market updates to the customer over the wire.
A subscription scoped to two market types receives only those markets'
deltas; the others are dropped server-side.
Some markets use a different betType sysname before kickoff than they
do once live. The standard 1X2 line, for example, is
SOCCER_MATCH_RESULT_PRELIVE up until the match goes live, then
SOCCER_MATCH_RESULT. A match only ever has one of the two at a time.
Filtering on SOCCER_MATCH_RESULT alone returns nothing for a match
that hasn't started yet. If you want the market regardless of match
state, filter on both:
betTypes: ["SOCCER_MATCH_RESULT", "SOCCER_MATCH_RESULT_PRELIVE"]Localization
code is the same identifier across every locale. caption is the
formatted human-readable label of the line. Pass locale to choose
the language; defaults to "en".
Resolution order:
- Per-call
localeparam - Connection-level
X-Localeheader from auth "en"
Subscription completion
When the underlying match completes (final whistle, abandonment), the
gateway terminates every odds.subscribe for that match with a
subscription.closed notification:
sub.on("closed", ({ reason, message }) => {
if (reason === "data_unavailable" && message === "match_completed") {
teardownUi();
}
});No final push fires: the order book is gone. Stats subscribers
(matches.subscribe) receive a status.upd event instead; odds is
terminal because there's nothing live left to stream.
Pattern: render a live odds board with filtering
const sub = await mrdoge.odds.subscribe({
matchId,
betTypes: [
"SOCCER_MATCH_RESULT",
"SOCCER_MATCH_RESULT_PRELIVE", // headline market pre-kickoff, see the betTypes note above
"SOCCER_UNDER_OVER",
"SOCCER_BOTH_TEAMS_TO_SCORE",
],
locale: "en",
select: {
id: true,
betType: true,
lines: { code: true, caption: true, price: true, isAvailable: true },
},
});
renderBoard(sub.snapshot);
sub.on("odds.upd", renderBoard);