Free trial · 7 days

Get a free key

We email you a link; opening it shows your API key. Then message @oddspush on Telegram and we activate your 7-day free trial. No card.

By continuing you agree to the Terms and the Privacy Policy.

Already have a key?

Lost your key?

Pinnacle odds over WebSocket vs REST polling

Last updated

A WebSocket stream of Pinnacle odds pushes every price change to your bot as it happens, about 30 ms after Pinnacle publishes it for a bot hosted in Europe, on one connection that covers every sport you subscribe to. REST polling pulls the current book only when you ask, so your prices are as old as your last request, and each request spends part of a rate limit. Use the stream for in-play, for many events at once and for bots that act on price moves; REST is enough for pre-match, a handful of events and dashboards.

This page puts numbers on that choice: the latency we measured on the stream, what polling the same book would cost in requests, and how a stream client keeps its prices correct. The step-by-step clients are in Python and Node.js.

1. WebSocket vs REST for odds: push vs pull at a glance

With push, the server sends you each change the moment it has it. With pull, you ask, and get the state at the moment of the request. Both read the same prices: the difference is when they reach you, and what it costs to keep up.

WebSocket (push) REST (pull)
How fresh Every change, about 30 ms after Pinnacle publishes it (Europe) As fresh as your last request
Changes between two looks Each one is pushed, with its version Only the last state; moves in between are lost
Cost per update None: the server sends it One request per event, every time
Limit One connection, 1,000 single events or whole sports 120 requests at once, then 300 a minute
State you keep A book: snapshot plus updates by version None needed: each answer is the current book
Plan REST + WebSocket, $138 a month REST, $69 a month (also in REST + WebSocket)

2. What a real-time Pinnacle odds stream gives you

On the stream you log in once, subscribe to sports or single events, and from then on every update arrives as an odds message with Pinnacle's record in data and the time the price was published in data.ts. You do not ask again: a quiet market costs nothing, and a market that moves five times in a second reaches you five times, each with a higher version.

How long that takes we measured on November 6, 2026, with a paying client connected through the public endpoint, wss://feed.oddspush.com/v1/stream, for five minutes, following soccer, tennis, basketball and hockey. The figure is end to end: from the publish time in data.ts to the moment the message reached the bot.

Where the bot runs p50 p90 p99
Bot in Ireland 24 ms 36 ms 71 ms
Bot in Spain ~46 ms ~77 ms ~140 ms

Same feed, same day: the difference between the two rows is the network between Ireland and the bot. That part is yours to choose: host your bot close to Ireland (AWS eu-west-1 or nearby) for the lowest latency. Further away, the median grows with the distance, and the tail with the quality of the line.

The stream also tells you when something is wrong instead of leaving you with stale prices: a keepalive every 15 s of idleness, feed "down" and "up" when the feed drops and comes back, and missed when your bot falls behind (other messages).

3. What REST polling costs: freshness and requests

GET /v1/events lists the events and how many open markets each one has per channel, but no prices. The prices of one event come from GET /v1/events/{event_id} (reference). So a polled book costs one request per event, every time you refresh it. The rate limit is per account: up to 120 requests at once, then 300 a minute, which is 5 a second (REST). Over it you get a 429 with retry-after in seconds.

Three numbers follow from that:

  • Freshness is half your interval, on average. A price changes at a random moment between two polls, so with a poll every 10 s you see it on average 5 s late, and up to 10 s late. Against a median of 24 ms on the stream, that is about 200 times slower.
  • 50 events every 10 s use the whole budget. 5 requests a second times 10 s is 50 requests: nothing left for anything else.
  • A full pass over the board takes minutes. With 3,000+ events holding open markets, 3,000 requests at 5 a second take 600 s: about 10 minutes for one look at each event (the burst of 120 saves under half a minute). Soccer pre-match alone held 1,323 events when we counted on November 6, 2026: 4.4 minutes per pass.

Polling also loses what happens between two looks. If a line moves and moves back within your interval, you never see it, and the version you read jumps over every change in between. For a dashboard that is fine; for a bot that trades on moves, those are the moves.

4. When REST polling is enough

  • Pre-match, far from the start. Pre-match prices move far less often than in-play ones. A model that reprices a slate once an hour reads it once an hour. Ask with ?channel=pre (see Pinnacle pre-match odds).
  • A few events. Five matches every 10 s are 30 requests a minute, a tenth of the limit.
  • Dashboards and research. A page that refreshes when someone looks at it, or a notebook comparing Pinnacle with Polymarket and Kalshi.
  • Margin-free prices and closing lines on demand. ?fair=true adds the no-vig and fair prices of every market (reference), and /v1/closing returns the closing line of a match once it has started (reference). Both are on every plan; the maths is in Pinnacle no-vig odds.

5. When you need the Pinnacle odds stream

  • In-play. Live prices move with every attack, break of serve and timeout. Seconds of delay are the difference between a price that is still there and one that has gone.
  • Many events. One connection carries every sport you subscribe to; by REST, the same board is 10 minutes per pass.
  • Value and CLV bots. A value bet against Pinnacle's fair price exists only until the other book or market catches up. With {"type": "subscribe", "fair": true} each odds is followed by its fair message, and {"type": "subscribe", "closing": true} sends the closing line of each match as it starts (fair, closing, CLV calculator).
  • Prediction-market trading. Quoting on Polymarket or Kalshi against Pinnacle means repricing the moment Pinnacle moves.

Nothing here is betting or investment advice: a faster price is a tool, not an edge on its own.

6. How a stream client keeps a correct book

A stream gives you changes, so you keep the state yourself. The rules are short, and none of them raises an error when broken: you just end up with wrong prices.

  • Start from a snapshot. Your subscribe brings a snapshot of what it adds, in parts of up to 100 events; the last one has "final": true.
  • Apply updates by version. Key events by rec.id and markets by key, and take a market only when its version is higher than the one you hold. A market whose status is not "open" leaves the book, and op: "del" removes the whole event, on whichever channel it comes.
  • On missed, or feed "up", ask for a snapshot. Your queue holds 1,000 updates; past that the oldest are dropped and you are told how many. After a snapshot of everything you follow, drop what it did not mention.
  • Reconnect with backoff. Exponential with jitter, 1 to 30 s, reset after a minute connected; stop on 4101, 4103, 4107 and 4109, which retrying cannot fix (reconnecting). Treat 40 s with no message at all as a dead connection.
  • Keep the read loop light. A client more than 15 s behind is closed with too_slow (4108). Read in one task, trade in another.

The full recipe is in Keeping a correct book, and both tutorials build it end to end: Pinnacle odds in Python and Pinnacle odds in Node.js.

7. The minimal code: a WebSocket subscribe and a REST poll loop

The stream, in Node.js 18+ with npm install ws: log in, subscribe to soccer on the ld and both channels (the price Pinnacle displays), and print every message. The snapshot arrives first, then each change.

stream.mjs JavaScript
import WebSocket from 'ws';

const ws = new WebSocket('wss://feed.oddspush.com/v1/stream', { perMessageDeflate: false });
ws.on('open', () => {
  ws.send(JSON.stringify({ type: 'login', token: process.env.ODDSPUSH_KEY }));
  ws.send(JSON.stringify({ type: 'subscribe', sports: [1], channels: ['ld', 'both'] }));
});
ws.on('message', (raw) => {
  const msg = JSON.parse(raw.toString());
  if (msg.type === 'odds') console.log(msg.data.topic, msg.data.rec?.id, msg.data.rec?.markets?.length ?? 0);
  else console.log(msg.type); // welcome, subscription, snapshot, keepalive...
});
ws.on('close', (code) => console.log('closed', code));

The poll, in Python with the standard library only: refresh two events every 10 s, print the markets whose version changed, and back off on 429.

poll.py Python
import json
import os
import time
import urllib.error
import urllib.request

BASE = "https://feed.oddspush.com/v1"
KEY = os.environ["ODDSPUSH_KEY"]
WATCH = [1638780974, 1638781120]  # the event ids you follow
EVERY_S = 10                      # 2 events every 10 s = 12 requests a minute


def get(path: str) -> dict:
    req = urllib.request.Request(BASE + path, headers={"x-token": KEY})
    with urllib.request.urlopen(req, timeout=10) as r:
        return json.loads(r.read())


seen = {}  # (event id, market key) -> version last printed
while True:
    for event_id in WATCH:
        try:
            ev = get(f"/events/{event_id}")
        except urllib.error.HTTPError as e:
            if e.code == 429:  # over the rate limit: wait what the server says
                time.sleep(int(e.headers.get("retry-after", "1")))
            elif e.code != 404:  # 404: the event is gone (settled or removed)
                raise
            continue
        for key, m in ev["merged"].items():  # ld and both, merged by version
            if seen.get((event_id, key)) != m["version"]:
                seen[(event_id, key)] = m["version"]
                print(event_id, key, [(x["designation"], x["price"]) for x in m["prices"]])
    time.sleep(EVERY_S)

8. Plans and prices: REST or REST + WebSocket

Both plans carry the same prices at the same moment, live and pre-match, all 13 sports, with no-vig, fair and closing odds. The difference is the stream.

  • REST: $69 a month ($186 for 3 months, $331 for 6). Pull the book whenever you need it, within the rate limit.
  • REST + WebSocket: $138 a month ($373 for 3 months, $662 for 6). Everything in REST, plus one WebSocket connection with every sport on it and 12 snapshots an hour.

The free trial gives you both for 7 days, so you can measure the difference from your own server before you choose (pricing). Paid plans are prepaid in crypto.

Measure it from your own bot

The free trial gives you REST and the live stream, all 13 sports, for 7 days. No card.

OddsPush is not affiliated with, endorsed by or sponsored by Pinnacle. Nothing on this page is betting or investment advice.