Mr. Doge
Reference

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:

FieldTypeNotes
matchIdstringRequired
betTypesstring[]Restrict to one or more market sysnames (e.g. ["SOCCER_MATCH_RESULT", "SOCCER_UNDER_OVER"])
selectMarketSelectField selector, see selectors
localestringLocalizes 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:

FieldTypeNotes
matchIdstringRequired
betTypesstring[]Restrict pushes to specific market sysnames
selectMarketSelectField selector applies to snapshot AND every push
localestringLocalizes 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:

  1. Per-call locale param
  2. Connection-level X-Locale header from auth
  3. "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);

Next

On this page