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 theX-API-Key header, or as a key query parameter where headers are not possible:
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:/matches first and echo the ids back. Optionally narrow to one sport:
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.
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. Onesubscribe 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 atype field.
snapshot
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
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
unsubscribe if you track subscriptions yourself.
heartbeat
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: falsemeans at least one of the sports you are subscribed to has a disconnected upstream and its prices are no longer being refreshed. Consultfeedsto see which sport, and considerconnected: falseon a specific sport as a signal to suspend that sport only.
error
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 unsolicitedsnapshot 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
snapshotcan arrive at any time, not only in response tosubscribeorresync. 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_slowcounts those disconnects andmessages_coalescedcounts collapses — a steadily climbing coalesce counter means the client is not draining as fast as the book moves.
- 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
- Reconnect with backoff — the handshake re-verifies your key.
- Re-subscribe to your matches. Each returns a fresh
snapshot. - If you kept your state across the reconnect, send
{"action": "resync"}instead of re-subscribing, and replace state from the snapshots it produces. - Wait for a
heartbeatwithfeed_healthy: truebefore treating prices as actionable.