Skip to main content
The live stream is how you get prices and limits. Subscribe to the matches you care about and the server pushes a full snapshot for each, then only what changes afterwards. Every selection includes the market’s betting cutoff and maximum accepted risk stake.
Scope: odds · Sportsbook grant: pinnacle

Connecting

The key is verified during the handshake, before the connection is upgraded — a rejected key never becomes a WebSocket. Send it as the X-API-Key header, or as a key query parameter where headers are not possible:
One connection carries as many matches as you like. Open a second connection only for redundancy, not for throughput.
Inbound messages are capped at 4096 bytes. Every control message is far smaller than that; a client that exceeds it has almost certainly serialized something wrong, and the connection is closed.

Client → server messages

Send JSON objects. Unrecognized actions and malformed JSON are ignored silently — they do not close the connection and do not produce an error frame. Subscribe to a match and immediately receive its snapshot:
Subscribe in bulk by passing an array. Prefer this form as soon as you are following more than a handful of matches — one frame with a thousand ids replaces a thousand frames:
Subscribe to everything the key is granted, so a client following a whole book does not have to poll /matches first and echo the ids back. Optionally narrow to one sport:
Unsubscribe — single or batch. Unsubscribing a parent match also releases its specials (see below):
Resync — re-send a fresh snapshot for every match you are currently subscribed to, without changing your subscriptions:
Each socket may hold up to 5,000 subscriptions and accept up to 5,000 ids in a single batch frame by default. A batch that exceeds the cap is truncated and answered with an error frame naming the limit; hitting the subscription cap silently stops applying further ids in that batch.
Use resync after a reconnect, after a missed heartbeat, or whenever you suspect your local book has drifted. It is far cheaper than tearing down and re-subscribing every match, and it is the intended recovery path.

Subscribing to a game covers its specials

A game’s derivatives — player props, correct score, winning margin, odd/even, and similar — are their own matchups with their own ids and their own markets. A parent’s book does not contain them, and there can be dozens per game. One subscribe for the parent match streams the parent and every one of its specials. A subscribe on match 1632737408 returns 52 snapshots — the game itself plus its 51 derivatives — followed by live updates for all of them. Specials the sportsbook adds later are picked up automatically, without a fresh subscribe. Every frame keeps its own match_id, and a special also carries parent_id, so a client can still route them to the right local book. Unsubscribing the parent releases the specials with it. Subscribing to a special directly is unaffected — a special has no children of its own.

Server → client frames

Every frame is a JSON object with a type field.

snapshot

The match object is the lightweight header — the same fields as the match object minus participants, version, and markets. On a special it also carries special and parent_id. Each selection carries cutoff_at and limit. The limit applies to that market and can differ across selections in the same match. Replace your local state for this match wholesale when a snapshot arrives.

update

An update is a delta, not a full picture. Merge it into the state you built from the snapshot — do not replace. This is the one place where the passthrough stream differs from the SportriX live-scores stream, which sends a full snapshot on every frame.
When a market’s prices change, every selection in that market is re-sent, not just the one that moved. Markets are versioned as a unit.

match_removed

The match is gone from the book — settled, pulled by the sportsbook, or pruned after disappearing upstream. Discard your local state for it. Your subscription is not automatically cleaned up, so send an unsubscribe if you track subscriptions yourself.

heartbeat

Sent every 10 seconds on every connection, whether or not you are subscribed to anything. Heartbeats are scoped to your subscriptions. feeds, feed_healthy, and last_msg_ms only summarise the sports covered by your current subscribe / subscribe_all set. A basketball client is not tripped by a tennis outage, and a tennis outage is not masked by fresh basketball traffic. Sports that Sportrix does not ingest are simply absent from feeds — subscribing to a match under an unknown sport does not synthesize a feed for it. The scope updates as you subscribe and unsubscribe: adding a new sport to your subscriptions makes it appear in feeds on the next heartbeat, and dropping every match for a sport removes it. Until your first subscription lands, feeds is empty and the top-level fields report the whole-service view so you can still tell whether Sportrix is up. The heartbeat does two jobs:
  • Liveness. If heartbeats stop arriving, the socket is dead even if your TCP stack has not noticed. Reconnect and resync.
  • Feed health. feed_healthy: false means at least one of the sports you are subscribed to has a disconnected upstream and its prices are no longer being refreshed. Consult feeds to see which sport, and consider connected: false on a specific sport as a signal to suspend that sport only.
A client acting on these odds should suspend on feed_healthy: false and resync when it flips back to true. If you route bets by sport, prefer the per-sport feeds[sportId].connected flag so a single sport’s outage does not stall the rest of your book. The prices you hold on an unhealthy sport are not being updated in the meantime, and they will not be marked stale individually.

error

Sent when you subscribe to a match that is not in the book. The subscription is still registered, so if that id later appears you will begin receiving its updates.

Applying updates correctly

1

Key your state by market_key

A selection is identified by market_key plus bet_name. A market is identified by market_key alone, and that is the unit that carries a version.
2

Drop anything not newer

Ignore an incoming selection whose version is not greater than the version you hold for the same market_key. Reordering and re-delivery are both possible; version comparison makes them harmless.
3

Honour removed_market_keys

Delete every selection whose market_key appears in removed_market_keys. A removed market is gone, not locked — those are different states.
4

Respect status

A selection with "status": "locked" is not bettable. Keep displaying its last price if you like, but never act on it.
5

Cap risk at limit

Read limit from the current selection before placing or sizing a bet. It is the maximum risk stake accepted for that market and can change with a later market version.

Slow clients get coalesced snapshots

Each connection has a bounded send queue. A client that reads slower than the book moves does not silently lose ticks — instead, its queued deltas for a match are collapsed into an unsolicited snapshot for that match. The queue holds at most one pending item per match. When a change arrives while something for that match is still queued, the pair collapses into a “send the current state” marker, which the writer renders as a full snapshot. So a lagging client receives fewer, larger messages — never a stale price. It loses intermediate ticks it could not have acted on; it does not lose the tick that is live. Practical consequences for a client:
  • A snapshot can arrive at any time, not only in response to subscribe or resync. Treat every snapshot the same way: replace everything you hold for that match.
  • The queue is bounded by your subscription count, so following 1,000 matches costs at most 1,000 pending items regardless of how fast prices move.
  • A socket that generates control frames faster than it drains reads is disconnected rather than buffered without limit. In /pinnacle/stats, clients_cut_slow counts those disconnects and messages_coalesced counts collapses — a steadily climbing coalesce counter means the client is not draining as fast as the book moves.
Defensive practice still applies:
  • Read from the socket promptly and do your processing off the read loop.
  • Treat a heartbeat gap or any suspicion of drift as a reason to resync.
If you suspect your connection is being cut for lagging, contact SportriX support — the disconnect counter is recorded server-side and can be checked against your connection.

Reconnect checklist

  1. Reconnect with backoff — the handshake re-verifies your key.
  2. Re-subscribe to your matches. Each returns a fresh snapshot.
  3. If you kept your state across the reconnect, send {"action": "resync"} instead of re-subscribing, and replace state from the snapshots it produces.
  4. Wait for a heartbeat with feed_healthy: true before treating prices as actionable.