2026-05-19 · in-play live betting api integration

Live in-play sportsbook odds: why it's not the same product as pre-game

Most teams new to building betting products treat live in-play odds as "pre-game odds plus a clock." They wire up ?live=true on their odds endpoint, expect roughly the same data shape, and assume the same integration patterns work. They do, until they don't, and by then there's a partially-built product on top of a wrong mental model.

In-play markets are structurally different from pre-game markets in five ways that materially change how you build against them. This post covers each.

The five structural differences

Pre-gameIn-play
Update cadence Typically 5-60s between line moves on flagship markets, longer on niche. 200ms-5s between line moves during active play; sub-100ms during high-leverage moments (penalty, late-game possession).
Market suspensions Rare; markets stay open from posting through commence_time. Constant. Books "freeze" markets during plays, between possessions, on injuries, and on review. A market can be suspended 30-60% of the time during an NFL game.
Partial-market state All markets for an event move together (h2h, spreads, totals all suspended or all live). Individual markets transition independently. h2h might be live while alt-spread is frozen, or vice versa.
Latency to bet acceptance Tens of seconds to minutes; books accept at displayed price. Sub-second matters. By the time your bet hits the book, the line has often moved or the market has frozen, and the book asks you to confirm at the new price.
API shape Stable per-event response with full market list. Many books expose in-play through different endpoints (/v1/inplay/*) with diff-update WebSocket pushes instead of full snapshots.

1. Update cadence

Pre-game lines on the NBA might move 5 to 30 times between posting and tip-off. In-play, that same game gets 1,500 to 4,000 line moves over 48 minutes of play. If your scanner pulls a snapshot every 10 seconds, you're missing 98% of the in-play price discovery.

This is why pre-game polling architectures translate poorly to in-play: the polling interval is roughly the same order of magnitude as the data's coherence window. You can't just "poll faster." Books rate-limit you, your bandwidth bill explodes, and you still miss most updates because they happen between your polls.

Action item. For in-play, use WebSocket. Our /v1/ws ships per-market diffs at sub-second cadence on the scale tier and above. SSE alternative at /v1/sse/odds. See /docs/websocket for the subscribe / unsubscribe shape.

2. Market suspensions ("freezes")

When a book's risk engine sees something it can't immediately model (an injury, a stoppage in play, a goal under review), it suspends the market. Customers see "this market is currently unavailable" on the book's website. The price is gone, not zeroed.

For an API consumer, this means your in-play feed has three states per market, not two:

  1. Active: price posted, bet accepted at displayed price (subject to confirmation latency).
  2. Suspended: no price; not zero; not stale; just unavailable. Book is computing.
  3. Closed: market permanently removed (event ended, leg already resolved).

If your code conflates "suspended" with "no value," you'll silently filter out half your potential opportunities. If your code conflates "suspended" with "active at last-known price," you'll try to place bets at prices the book isn't currently offering.

# Bad: assumes any non-null price means active
def is_bettable(market):
    return market.get("price") is not None

# Better: respect explicit status flag
def is_bettable(market):
    return market.get("status") == "active" and market.get("price") is not None

ParlayAPI's in-play endpoints carry an explicit status field per market: active, suspended, closed, or settled. Trust it; don't infer from price presence.

3. Partial-market state

Books transition markets independently. On an NBA game in the 4th quarter:

If your scanner pulls a full event snapshot and treats "no spread price" as "spread market is dead," you'll wait minutes for a snapshot that includes both h2h and spread together. That snapshot may never come on a fast-paced game.

Pitfall. Don't write code that requires all markets for an event to be simultaneously present. Iterate markets independently and respect each market's individual status.

4. Latency to bet acceptance

This is the killer. In-play markets move so fast that the price you scanned 800ms ago is often not the price you can actually bet at. Books model this and apply an extra "in-play review" step on bet submission:

  1. You submit a bet at displayed price -110 Knicks.
  2. Book's risk engine recomputes its current fair price.
  3. If your bet price matches current fair within a tolerance, book accepts.
  4. If not, book either: (a) rejects outright, (b) offers you the new price (-115) and asks you to re-confirm, or (c) accepts at the new price silently (depends on your account settings).

End result: your "+EV bet at -110" might land at -115, which is no-EV or slightly -EV. Or it doesn't land at all.

Mitigation:

5. API shape differences

Many sportsbook APIs expose in-play through dedicated endpoints with different response shapes:

If you're consuming through ParlayAPI:

# Pre-game (full snapshot)
GET /v1/sports/basketball_nba/odds?regions=us&markets=h2h,spreads,totals

# In-play (live filter on same endpoint)
GET /v1/sports/basketball_nba/odds?regions=us&markets=h2h&live=true

# In-play dedicated (different shape: diffs vs snapshots)
GET /v1/inplay/odds?sport=basketball_nba
GET /v1/inplay/arbs?sport=basketball_nba

# In-play WebSocket (preferred for production)
wss://parlay-api.com/v1/ws?key=$PARLAY_API_KEY&sport=basketball_nba&market=h2h&mode=live

The five-line in-play checklist

If you're integrating in-play for the first time, run this checklist before going live:

  1. Use WebSocket, not polling. Polling is structurally wrong for in-play; you'll miss most of the data and trip rate limits.
  2. Respect the status field. Active ≠ suspended ≠ closed ≠ settled. Code accordingly.
  3. Iterate markets independently per event. Don't assume all markets for an event are in the same state.
  4. Time-stamp every observed price. An in-play snapshot 1 second old is potentially stale; an in-play snapshot 5 seconds old usually is.
  5. Budget for bet rejection. If your model says you have +1.5% edge at -110, your realized edge after rejection-and-resubmit at -115 might be -0.8%. Account for this in stake sizing.

Where to start with ParlayAPI

Free no-auth previews:

WebSocket reference: examples/ws_reference_client.py. The reference client subscribes per-market, respects status transitions, and times every observed price for staleness filtering. ~250 LOC, single file, no deps beyond websockets.

Sign up free for an API key at /signup if you want to go past the no-auth try endpoints.

All posts · Latency budget for a betting agent · Sub-1s freshness · WebSocket docs