Skip to main content
Market making across prediction markets means holding a consistent view of several venues at once — prices, depth, the identifiers you need to send an order back, and an honest answer to “is this quote still real?”. OddsPapi normalizes all of that into one stream, so a venue you add is a filter change, not an integration project. This guide is about the trading decisions on top of that stream: deriving a tradeable view, sizing against real depth, knowing when a price is no longer actionable, and behaving correctly when the connection breaks. Payload schemas and wire examples live on the odds channel page — this page assumes you have read it.

What you get per price

Every outcome carries the market-making primitives in one object — no secondary lookup: The four-stamp chain (bookmakerChangedAtchangedAt → envelope ts → your receipt) lets you measure delay per venue yourself rather than take it on trust. See Latency and freshness for the published per-book figures.

Deriving a tradeable view

meta.back and meta.lay are uniform across every exchange and prediction market: arrays of { price, size }, best price first, with identical semantics everywhere. The first back entry is top of book and matches the outcome’s price. See Advanced metadata for the exact shape. Because the shape does not vary, cross-venue comparison is direct — no per-venue parsing branch, and no normalization layer of your own to keep in sync as venues change their APIs. Best bid and best offer across venues. Group every quote by selection, then take the highest back price and the lowest lay price across the venues you are permissioned for. Both sides matter even if you only ever take one: the tightest spread tells you where the consensus actually sits, and a venue quoting far outside it is either carrying information or carrying a stale price — which of the two you decide with the timestamps, not the price. Crossed and locked books are a signal, not an opportunity. If the best back on one venue exceeds the best lay on another, check changedAt on both sides before you treat it as arbitrage. A genuine cross closes in milliseconds; a persistent one almost always means one side has stopped updating. Gate on the staleness checks below before sizing into it. Depth is per venue, not aggregate. Summing ladders across venues gives you a number you cannot trade against — each venue’s queue is separate, and you cannot fill one venue’s order against another’s resting liquidity. Model per-venue executable size, then aggregate the outcomes, not the books. Sizes carry the venue’s own denomination. size is in the venue’s base currency. For crypto venues, convert through the currencies channel before you compare depth or set position limits, otherwise a large number on one venue and a small number on another can mean the same exposure.

Sizing: limit and depth do different jobs

Two independent size signals arrive on the same row, and confusing them is a common source of over-sizing:
  • limit is the maximum stake the venue will accept at the current price. It is normalized across books and carried for close to every sharp book — including traditional bookmakers that publish no ladder at all. See Limits.
  • The ladder is resting liquidity behind the top of book. It tells you what you can clear before you move the price, and at what average cost.
For an exchange or prediction market you have both: limit bounds a single order, the ladder bounds the fill. For a traditional book you usually have only limit, and that is the whole answer. Walk the ladder to a target size and price the fill at the volume-weighted average rather than at top of book — for anything beyond the first level, the touch price is not the price you get. Limit changes are prices too. They arrive as ordinary odds updates on the same oddsId. A book cutting its limit while holding its price is telling you something the price alone does not, and it is often the earlier of the two signals. Log limit history alongside price history if you want to see it. None of this is specific to exchanges — the same distinction governs sizing on traditional sharp books, where limit is the only size signal you get. See sharp-line trading & CLV.

Knowing when a quote is tradeable

Price freshness is a separate question from price value, and for automated trading it is the one that costs money when you get it wrong. Four independent gates, each of which can veto: staleOdds is the one people miss, because nothing about the last price you received looks wrong. The prices you hold may have moved at the venue with no way for us to tell you. Treat everything from that venue as unvalidated until it flips back to false, and pull your quotes rather than re-price them. Detail in Critical fields for automated trading. Add a fifth gate of your own: an age check against the published bound. Each book publishes maxDelayLiveInSec / maxDelayPregameInSec on GET /bookmakers. If now − changedAt exceeds that book’s bound by a comfortable margin, the quote is out of contract even though no flag has fired — a quiet venue and a broken venue look identical for the first few seconds. See Status signals. Wire all five into a single “can I quote against this right now” predicate, evaluated on every update rather than on a timer. One predicate, one place to fix. For fixture markets, participantsRotated on the bookmakers channel is a sixth check of a different kind: when true, that venue’s home/away assignment is flipped relative to ours, which reverses the meaning of side-based outcomes and flips the sign of handicaps. It affects correctness rather than freshness, and it is silent if you map outcomes by position instead of by ID.

Delay budgeting

Your reaction time is the sum of four segments, and you control exactly one of them:
  1. Venue → OddsPapi observation (changedAt − bookmakerChangedAt) — published per book, measured where the venue gives usable timestamps.
  2. Conflation — a fixed ~10 ms batching window; the newest value always lands, so this bounds staleness rather than dropping data.
  3. Gateway → you (your receipt − ts) — the delivery hop, the one segment you can shorten. See Server location.
  4. Your own decision and order round-trip back to the venue.
Measure segment 3 continuously from your own clock; it is the segment that degrades first and the one your infrastructure decisions actually move. Budget segments 1 and 4 against the venue’s update cadence: if a venue re-prices faster than your loop closes, you are not competing on that venue’s touch and should be quoting behind it or sizing smaller, not chasing. Per-venue delay is also why a uniform quoting policy across venues is wrong. A 0.05 s venue and a ~1 s venue justify different spreads for the same underlying, and the numbers to parameterize that are published rather than something you have to infer. See sharp-line trading & CLV for the same reasoning applied to line taking.

Surviving gaps

A market maker’s worst state is quoting from a book it believes is current and is not. The correct disconnect behavior is therefore ordered: pull quotes first, reconcile second.
  • Server-initiated reconnect. Before a deploy or rebalance we push a reconnect control frame and keep streaming for a short grace window. Act on the frame, not on the eventual socket error — you get a clean handover instead of a gap.
  • Within the replay window. Reconnect with your per-channel cursor (serverEpoch + entryId) and missed updates replay with no gaps.
  • Outside it. The gateway emits snapshot_required; restore with one REST snapshot. This is a designed path with a bounded worst case, not an error.
  • On a changed serverEpoch, your old cursors are meaningless — re-snapshot rather than resume.
Full mechanics in Resume & Replay. The discipline that matters: your book’s “last known good” timestamp per venue should gate quoting independently of whether your socket happens to be up, because a live socket with a stale venue behind it is the failure that actually hurts.

Routing orders back to the venue

bookmakerMarketId and bookmakerOutcomeId travel on every price, on the live stream. When your strategy picks a selection, you already hold the venue’s own identifiers — no mapping table to maintain, no second call before you can send. This matters most exactly when it is hardest: the moment you want to act is the moment you least want a lookup in the path. At fixture level, GET /fixtures/mapping resolves in both directions — pass fixtureIds to get the venue’s IDs, or bookmakerFixtureIds to go from a venue ID back to the OddsPapi fixture. The bookmakers channel also carries bookmakerFixtureId and fixturePath per venue per fixture, so a manual desk can open the venue’s own page for the same market.

Pricing across venues

Group by marketId before you price anything. All outcomes under one marketId form a complete probability space, which is what overround, normalization, and completeness checks require — and each distinct line value is its own marketId, so a market never silently mixes lines. From there the usual construction applies: normalize each venue’s quotes within the market, then combine across venues. Two weightings are worth carrying explicitly, because the data supports both:
  • By size — a venue showing real depth and a high limit is a stronger observation than one quoting a token amount at the same price.
  • By delay — a venue with a measured sub-100 ms bound and one with a multi-second bound are not equally current, and the published per-book figures let you weight that rather than guess it.
For post-hoc work — fill auditing, backtests, closing lines — the REST historical odds and CLV endpoints share the same oddsId key as the live stream, so live decisions and their audit trail join without a translation step.

Non-sports markets are futures

Politics, elections, finance, crypto, and weather are modeled as sports with sportId 69+ and exist as futures only — one future per outright market:
Use the futures and oddsFutures channels, which follow the same delivery semantics and carry the same orderbook meta. Everything above about gating, sizing, and depth applies unchanged. See How Futures Work for the model and Coverage for the sportId bands. Which question a future asks is market.marketId on the futures payload — the 5-digit futureMarketId also carried in the futureId tail, allocated per sport (69002 = Politics Prediction). Which selection a price is on is the last two segments of the key, and which one carries it depends on the market’s shape. Participant-keyed markets (winner, topscorer, relegation, mvp) own no outcome rows: the selection is the entity you back, and futureOutcomeId is the sentinel 0. Decomposed markets (Yes / No, Over / Under) carry a real futureOutcomeId per side per line — one future market spans many lines, and unlike fixture markets the handicap sits on the outcome rather than on the market. Read both from the explicit fields on the odds row rather than splitting oddsId; see Future odds IDs.

Wiring it up

1

Snapshot

Pull current state over REST (/fixtures/odds or /futures/odds) to hydrate your book.
2

Stream

Connect to wss://v5.oddspapi.io/ws and log in with the channels, sports, and venues you trade. Subscribe to bookmakers alongside odds — without it you have prices but no staleness signal.
3

Apply updates

Key by oddsId and overwrite in place. Each message is state, not a tick.
4

Gate

Evaluate the quoting predicate on every update, per venue, before anything downstream sees the price.
5

Recover

Handle reconnect and snapshot_required as designed paths. See Resume & Replay.
Filter at login — sportIds, tournamentIds, and bookmakers cut volume far more effectively than client-side filtering — and prefer receiveType: "zstd" on odds at scale (Compression). Login shapes and a worked client are on the odds channel page and in the Quickstart.

What you can rely on

  • One orderbook shape, not one per venue. meta.back / meta.lay are { price, size } arrays with identical semantics across every exchange and prediction market, so you write one parser rather than a per-venue adapter set. Unknown meta keys are additive; the back / lay core will not be repurposed.
  • Venue-native IDs ride on the price itself. bookmakerMarketId and bookmakerOutcomeId are on the live odds row, so routing an order back to the venue costs no second lookup and no reverse-mapping table of your own.
  • Size is a first-class field. limit is normalized across books and carried for close to every sharp book, so what you can actually get filled for is in the same payload as the price.
  • Loss of contact is reported, not hidden. staleOdds tells you when we can no longer vouch for a venue’s prices, which is the difference between pulling quotes and unknowingly trading against a frozen book.