Free trial
Start hereThe real feed, REST and WebSocket, on your own bot. Pay only if it earns its place.
No card. Sign up, then we switch it on.
Real-time Pinnacle odds API for trading bots
Every Pinnacle price change, pushed to your bot over WebSocket the moment it happens, plus REST for the current book. Every market, 13 sports.
| Event | Home | Draw | Away | Handicap | Total |
|---|---|---|---|---|---|
| 1.46 | 4.45 | 8.10 | −1 2.06 +1 1.80 | o1.5 1.94 u1.5 1.91 | |
| 2.31 | 3.08 | 3.62 | −0.25 2.02 +0.25 1.84 | o2.25 1.96 u2.25 1.89 | |
| 1.19 | No draw | 5.15 | −4.5 1.92 +4.5 1.93 | o21.5 1.88 u21.5 1.97 | |
| 1.42 | No draw | 2.98 | −6.5 1.93 +6.5 1.92 | o224.5 1.91 u224.5 1.94 | |
| 1.61 | No draw | 2.42 | −1.5 2.64 +1.5 1.51 | o5.5 1.95 u5.5 1.90 | |
| 1.86 | No draw | 1.98 | −1.5 1.92 +1.5 1.93 | o161.5 1.90 u161.5 1.95 |
Pinnacle → your bot · Europe
32ms
median 29 ms · 1.4 msg/s
Arsenal 1 to 0 Chelsea
Arsenal to win · last 60 s
The latest odds message for a moneyline, as the feed sends it. Prices are American odds. Demo data.
{"type": "odds", "data": {"type": "live", "sport_id": 1, "topic": "matchups/reg/sp/29/live/ld", "op": "upd", "rec": {"id": 1638780974, "markets": [{"key": "s;0;m", "version": 3724875515, "status": "open", "prices": [ { "designation": "home", "price": -217 }, { "designation": "draw", "price": 345 }, { "designation": "away", "price": 710 } ]}]}, "ts": 1792512311307}}
{"type": "odds", "data": {"type": "live", "sport_id": 1, "topic": "matchups/reg/sp/29/live/ld", "op": "upd", "rec": {"id": 1638780974, "markets": [{"key": "s;0;m", "version": 3724875515, "status": "open", "prices": [ { "designation": "home", "price": -217 }, { "designation": "draw", "price": 345 }, { "designation": "away", "price": 710 } ]}]}, "ts": 1792512311307}}
import json, websocketsURL = "wss://feed.oddspush.com/v1/stream" async def stream(token): async with websockets.connect(URL, compression=None) as ws: send = lambda m: ws.send(json.dumps(m)) await send({"type": "login", "token": token}) await ws.recv() # welcome await send({"type": "subscribe", "sports": [1], "channels": ["ld", "both"]}) async for raw in ws: on_message(json.loads(raw)) # odds, snapshot, keepalive
import WebSocket from "ws";const URL = "wss://feed.oddspush.com/v1/stream"; const ws = new WebSocket(URL, { perMessageDeflate: false });const send = (m) => ws.send(JSON.stringify(m));ws.on("open", () => send({ type: "login", token: TOKEN }));ws.on("message", (raw) => { const { type, data } = JSON.parse(raw); if (type === "welcome") send({ type: "subscribe", sports: [1], channels: ["ld", "both"] }); if (type === "odds") onOdds(data);});
How it works
The moment Pinnacle changes a price, our relay picks it up and pushes it to you over WebSocket, exactly as published. No polling, no waiting for the next refresh.
Source
Pinnacle
Arsenal v Chelsea
Moneyline · Arsenal 2.10
Relay
OddsPush relay
1 in to 3 out, same bytes
Dublin +27 ms
Arsenal ML 2.10
London +30 ms
Arsenal ML 2.10
Frankfurt +33 ms
Arsenal ML 2.10
0ms Pinnacle publishes the price Every change, from a connection we keep alive for you.
Fan-out Received once, sent to every client Byte for byte as published: nothing rounded, nothing reordered.
~30 ms On your servers, in Europe Your own queue: a slow client never holds back the others.
Integration
Our docs come in a version written for AI coding agents. Download one file, hand it to Claude Code, Codex or Cursor, and it builds the client for you: login, subscription, a live price book and reconnects.
The link is our guide for coding agents: the whole protocol, the rules for a correct price book and two reference clients, in one Markdown file.
Read https://oddspush.com/oddspush-agent-guide.md and build a Python service that streams soccer and basketball odds into a live price book. My key is in ODDSPUSH_KEY.
Claude Code, Codex, Cursor or any agent that can open a link. Without web access, download the guide (81 KB) into your project. Also at /llms.txt.
The agent asks what the guide leaves to you, writes the client and runs the guide's checklist. Set
ODDSPUSH_KEY on the one server that runs your bot, and start it.
Read https://oddspush.com/oddspush-agent-guide.md and build a Python service that streams soccer and basketball odds into a live price book. My key is in ODDSPUSH_KEY.
Read the guide
What should happen with each price?
Print the moneyline when it moves.
Wrote client.py and its tests
Logged in, snapshot of 1,255 events
Checklist passed, reconnect in 0.6 s
Running, about 11 updates a second:
# pip install websocketsimport asyncio, json, osimport websocketsURL = "wss://feed.oddspush.com/v1/stream"KEY = os.environ["ODDSPUSH_KEY"] # opk_…book = {} # event id -> {market key: market}def merge(rec): markets = book.setdefault(rec["id"], {}) for m in rec.get("markets") or []: k = m.get("key") old = markets.get(k, {}).get("version", -1) if k is None or m.get("version", 0) <= old: continue # stale or reordered: skip if m.get("status") == "open": markets[k] = m else: markets.pop(k, None) # closed: remove it for p in rec.get("periods") or []: if p.get("status") in ("closed", "settled"): # a closed period closes all its markets for k in [k for k, m in markets.items() if m.get("period") == p.get("period")]: del markets[k]async def main(): # compression=None: deflate only adds latency async with websockets.connect(URL, compression=None, max_size=2**24) as ws: send = lambda msg: ws.send(json.dumps(msg)) await send({"type": "login", "token": KEY}) welcome = json.loads(await ws.recv()) if welcome["type"] == "error": raise SystemExit(welcome["code"]) await send({"type": "subscribe", "sports": [1], "channels": ["ld", "both"]}) async for raw in ws: msg = json.loads(raw) kind = msg["type"] if kind == "odds": data = msg["data"] rec = data.get("rec") or {} channel = str(data.get("topic")).rsplit("/")[-1] if data.get("op") == "del": book.pop(rec.get("id"), None) elif "id" in rec and channel in ("ld", "both"): merge(rec) # other channels are not prices elif kind == "snapshot": for rec in msg["events"]: merge(rec) elif kind == "missed" or msg.get("status") == "up": await send({"type": "snapshot"})asyncio.run(main())
// npm install wsimport WebSocket from "ws";const KEY = process.env.ODDSPUSH_KEY; // opk_…const book = new Map(); // event id -> Map(market key -> market)const ws = new WebSocket("wss://feed.oddspush.com/v1/stream", { perMessageDeflate: false, // deflate only adds latency});const send = (msg) => ws.send(JSON.stringify(msg));function merge(rec) { if (!book.has(rec.id)) book.set(rec.id, new Map()); const markets = book.get(rec.id); for (const m of rec.markets ?? []) { const old = markets.get(m.key)?.version ?? -1; // stale or reordered: skip if (m.key === undefined || (m.version ?? 0) <= old) continue; if (m.status === "open") markets.set(m.key, m); else markets.delete(m.key); // closed: remove it } // a closed period closes all its markets for (const p of rec.periods ?? []) { if (p.status !== "closed" && p.status !== "settled") continue; for (const [k, m] of markets) { if (m.period === p.period) markets.delete(k); } }}ws.on("open", () => send({ type: "login", token: KEY }));ws.on("message", (raw) => { const msg = JSON.parse(raw); if (msg.type === "welcome") { send({ type: "subscribe", sports: [1], channels: ["ld", "both"], }); } else if (msg.type === "error") { console.error(msg.code); } else if (msg.type === "odds") { const { op, rec = {}, topic = "" } = msg.data; if (op === "del") book.delete(rec.id); // other channels are not prices else if (rec.id && /\/(ld|both)$/.test(topic)) merge(rec); } else if (msg.type === "snapshot") { msg.events.forEach(merge); } else if (msg.type === "missed" || msg.status === "up") { send({ type: "snapshot" }); }});
# Log in within 15 s, from the machine that runs your bot. npm install -g wscat wscat -c wss://feed.oddspush.com/v1/streamConnected (press CTRL+C to quit) {"type":"login","token":"opk_…"} {"type":"welcome","feed":{"alive":true,"ready":true}, …} {"type":"subscribe","sports":[1],"channels":["ld","both"]} {"type":"subscription","filter":{"sports":[1], …}} {"type":"odds","data":{"type":"live","sport_id":1, …}} {"type":"snapshot","part":0,"final":false,"events":[ …
{"type": "odds", "data": { "type": "live", "sport_id": 1, "topic": "matchups/reg/sp/29/live/ld", "op": "upd", "rec": { "id": 1638780974, "type": "matchup", "startTime": "2026-10-20T16:00:00Z", "participants": [ {"name": "Lazio", "alignment": "home"}, {"name": "Roma", "alignment": "away"} ], "state": {"state": 1, "minutes": 31}, "markets": [ { "key": "s;0;m", "type": "moneyline", "period": 0, "version": 3772905262, "prices": [ {"designation": "home", "price": 145}, {"designation": "away", "price": 215}, {"designation": "draw", "price": 230} ], "isAlternate": false, "status": "open" }, … 41 more markets ] }, "ts": 1792512282138 }}
type names the message. data is the frame as published.
other.
events. On "op": "del" the event is gone: drop it.
0 is the full match, m the moneyline.
key. Equal to or lower than yours: stale, skip it.
230 is +230, or 3.30 decimal.
open means the market is closed: remove it. So are all markets of a period that rec.periods lists as closed or settled.
Live, pre-match, no-vig and CLV
Live and pre-match Pinnacle odds. Ask for it ("fair": true on the stream, ?fair=true over REST) and every open price also comes with its no-vig and fair odds, plus the closing line of every match. In every plan.
No-vig odds
Pinnacle’s odds with the bookmaker’s margin taken out.
Fair odds
A more precise estimate of the real chance of each outcome.
Closing line (CLV)
Pinnacle’s last pre-match price of every match, without the vig, to measure the CLV of your bets.
Prediction markets
Compare fair odds with Polymarket and Kalshi prices to spot outcomes priced too low or too high.
Pricing
Pull the book over REST, or have every change pushed to you. All 13 sports on both. Run it on your own bot free for 7 days before you pay anything.
The real feed, REST and WebSocket, on your own bot. Pay only if it earns its place.
No card. Sign up, then we switch it on.
Pull the whole book whenever your model asks for it.
On request as fresh as you poll
$69 / month
$62
/ month
instead of
$69
$186 for 3 months, paid once Save 10%
$55.17
/ month
instead of
$69
$331 for 6 months, paid once Save 20%
Every price change pushed to you, milliseconds after Pinnacle.
~30 ms pushed to your bot
$138 / month
$124.33
/ month
instead of
$138
$373 for 3 months, paid once Save 10%
$110.33
/ month
instead of
$138
$662 for 6 months, paid once Save 20%
The same feed
Every plan and the trial get the same prices at the same moment. Nothing sampled.
Crypto, prepaid
USDC, USDT, BTC and more. No card, no auto-renewal: you renew when you choose.
Switch any time
Move between paid plans whenever you like: the days you have left carry over, pro rata.
One key per account, used from one IP at a time. Every limit, plan by plan
FAQ
Still deciding?
Run it on your own bot for 7 days. If it does not earn its place, let it end.
Try it free for 7 daysOr ask us: [email protected]
OddsPush is a real-time Pinnacle odds API for trading bots. Every Pinnacle price change is pushed to your bot over WebSocket about 30 ms after Pinnacle publishes it (bots hosted in Europe), with a REST API for the current book. 13 sports, every live market.
Your bot opens one WebSocket to wss://feed.oddspush.com/v1/stream and receives a snapshot of the current book, then every change. The REST API at https://feed.oddspush.com/v1 returns the book on request.
Sign up with your email and open the link we send: your key is on that page. Then message @oddspush on Telegram with the email of your account and we switch the trial on. The 7 days count from that moment.
You get REST + WebSocket, all 13 sports and real prices, on your own bot. No card, nothing to cancel: when it ends, pick a plan or let it go. One trial per person.
REST ($69 a month): your bot asks for the book when it needs it. Enough for models that price before kick-off or check every few seconds.
REST + WebSocket ($138 a month): every change is pushed to you about 30 ms after Pinnacle publishes it, and REST comes with it. If you trade on price moves, this is the one.
REST is $69 a month and REST + WebSocket is $138 a month. Prepaying 3 months costs $186 or $373 (10% less), and 6 months $331 or $662 (20% less).
You pay in crypto, with no card and nothing that renews on its own. Try it free for 7 days first. See pricing.
~30 ms from Pinnacle publishing a price to a bot in Europe. OddsPush runs in Western Europe; from further away, add the network time between your server and Western Europe.
If every millisecond matters, host your bot in Europe.
Pinnacle's odds for 13 sports: moneyline, spreads, totals and the rest of each market, live and pre-match (the pre channel). Each message carries the record as Pinnacle publishes it: nothing rounded, renamed or held back.
On connect you get a snapshot of the current book, then every change. Formats and examples are in the docs.
Yes, in every plan. When a match kicks off we keep Pinnacle's last pre-match price of every market, with its no-vig and fair odds: the closing line. Compare your price with it to get your CLV (closing line value).
It comes on the stream with "closing": true and over REST at /v1/closing/{event_id}, kept for 7 days. Details in the docs.
Soccer, Tennis, Basketball, Hockey, American Football, Baseball, Rugby, MMA, Boxing, Volleyball and handball, Esports, Golf and Cricket: all 13 with their live and pre-match markets.
Moneyline, spreads, totals, team totals and lines by period, depending on the sport: golf has moneyline, and MMA, boxing and cricket have moneyline and totals. The table is on the coverage page.
Not for the general public. Pinnacle's API documentation says access “has been closed for the general public since July 23rd, 2025”, and that it now offers data to selected bettors, commercial partners and academic projects that apply (pinnacleapi-documentation, read on 2026-11-04).
With OddsPush you get Pinnacle’s live prices with an OddsPush key: you do not need a Pinnacle account or Pinnacle API access. More on the Pinnacle API alternative page.
Open a WebSocket to wss://feed.oddspush.com/v1/stream, send login with your key, then subscribe to the sports you want. You get a snapshot of the book, then one odds message per change.
The Python guide walks through a complete client, and the docs have Python and Node.js examples. Or hand the agent guide to Claude Code, Codex or Cursor and it writes the client for you.
No. OddsPush is not affiliated with, endorsed by or sponsored by Pinnacle. It is an independent service that delivers Pinnacle’s prices to your bot.
Your key works from one IP at a time, and there is nothing to set. When it connects from a new IP, that IP takes over and the connection from the old one closes. So run your bot from one server: a second machine with the same key kicks the first.
One key per account, and only we change it. If you lose it or think it has leaked, message @oddspush on Telegram and we issue a new one; the old one stops working.
In crypto, from your account: USDC, USDT, BTC or another coin, no card. You prepay 1, 3 or 6 months, and the longer terms cost less.
Nothing renews on its own, so there is nothing to cancel: you renew when you choose. Move between paid plans and the days you have left carry over.
Ask about OddsPush
AI assistant · answers from our docs
Hi! Ask me anything about OddsPush: plans and prices, the free trial, paying in crypto, your key, or connecting your bot.
I'm an AI and can make mistakes. Never paste your API key here. For your account, message @oddspush on Telegram.