Price is not enough — take the size with it
Every outcome on theodds channel carries limit: the maximum accepted stake at that price, normalized across books and carried for close to every sharp book.
limit and depth answer different questions
Two size signals can arrive on the same row, and conflating them is a common way to over-size:
limitbounds a single order — the most the book will accept at this price.- The ladder (
meta.back/meta.lay, on exchanges and prediction markets) bounds the fill — what is resting behind the touch, and therefore what it costs you to clear a given size.
limit is the whole answer: one number, one order, and if you want more you wait for the book to re-offer. On an exchange or prediction market you have both, and they constrain you independently — a generous limit against a thin ladder still fills badly.
Where you have depth, price the fill at the volume-weighted average, not at the touch. Walk the ladder to your target size and use the resulting average; beyond the first level the top-of-book price is a number you will not get. Sizing off the touch is what turns a modelled edge into a realised loss on exactly the trades you most wanted.
Depth is also per venue, never aggregate. Summing ladders across books gives a total you cannot trade against, because one venue’s order does not fill against another’s resting liquidity. Model executable size per book, then compare at the selection level.
Limit cuts are a signal in their own right
Limit changes arrive as ordinary odds updates on the sameoddsId, so a book tightening size shows up in the same stream as a price move — often earlier, and often more informative. A sharp book that holds its price and halves its limit has told you something the price alone has not.
Log limit history alongside price history if you want to use this. The REST historical odds endpoints carry the same rows, so the sequence is reconstructable after the fact as well as observable live.
Know your delay per book
Four timestamps travel with the data, so the delay is something you verify rather than assume:changedAt − bookmakerChangedAt is the observation delay for that update. Per-book bounds are published on GET /bookmakers (maxDelayLiveInSec, maxDelayPregameInSec, maxDelayPregameMainInSec), and representative rolling p50s per book are in Latency and freshness.
Two things worth wiring in early: log t0–t3 per fill so you can attribute a bad fill to the right hop, and deploy near the gateway — the last hop is the only one you control.
Stop trading when the book stops being current
Treat
staleOdds as a kill switch per book, not a warning — a stale sharp line is worse than no line, because it still looks tradeable.
Also watch participantsRotated on the bookmakers channel: a book whose home/away assignment differs from the OddsPapi baseline will otherwise read as an arbitrage that does not exist.
Main lines and change filtering
mainLinemarks the book’s main line for a market, so you can follow the headline price without subscribing to every alternate line.GET /fixtures/odds/mainreturns main lines only for snapshots.sincefilters (epoch milliseconds) on the odds endpoints let you backfill only what moved. See Timestamps.
marketId — “Over 2.5” and “Over 3.5” are separate markets, not two outcomes of one. Group by marketId for overround and normalization.
Asian lines
Asian handicaps and quarter lines are first-class:marketType spreads with the line in handicap (e.g. -0.25), and settlement returns HALFWIN / HALFLOSS for split-stake grading alongside WIN / LOSE / PUSH. See Enumerations → settlementStatus.
For books quoting in crypto or non-USD fiat, the currencies channel carries fiat and crypto rates against USD for stake and limit conversion.
Grade yourself against the close
Trade on the stream, measure on REST. The live channels deliver conflated state on a fixed ~10 ms window — the newest value always arrives, but superseded intermediate ticks do not, so the stream is not a tick ledger. For full movement and opening/closing prices:
Both share the
oddsId key ({fixtureId}:{bookmaker}:{outcomeId}:{playerId}) with the live stream, so fills join to their own closing line without a mapping step.
1
Record the fill
Store
oddsId, your executed price, and t0–t3 at the moment you traded.2
Pull the close
After the event starts, call
/fixtures/odds/clv for those oddsIds.3
Grade
Compare fill price to closing price for CLV, and to the recorded timeline (
/fixtures/odds/historical) for slippage attribution.4
Settle
Use
GET /fixtures/settlement for per-outcome results rather than your own grading logic.What you can rely on
- Delay is published per book, and measured.
maxDelayLiveInSec/maxDelayPregameInSec/maxDelayPregameMainInSeccome from observed-vs-bookmaker timestamps where a book publishes usable ones, and are labelled as estimates where it does not. The four-stamp chain means you can check those numbers against your own clock instead of taking them on faith. - Nothing is dropped. Updates are conflated on a fixed ~10 ms window and the newest value always lands — the stream is latest-state, not a lossy sample. When you need every tick, the REST history endpoints have them.
- IDs stay addressable across seasons. The market taxonomy is append-only — new
marketTypevalues appear as coverage grows and existing ones never change meaning — and the same coordinates always resolve to the samemarketId/outcomeId. A backtest written against last season’s data still addresses the same selections today.