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?

Real-time Pinnacle odds API for trading bots

Pinnacle odds,
ahead of the market.

Every Pinnacle price change, pushed to your bot over WebSocket the moment it happens, plus REST for the current book. Every market, 13 sports.

wss://feed.oddspush.com/v1/stream
Demo data
Live events with decimal odds. Demo data. Choose an event to inspect it.
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

SOC Premier League 67′

Arsenal 1 to 0 Chelsea

Home 1.46
Draw 4.45
Away 8.10

Arsenal to win · last 60 s

2,418 messages 6 events

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}}

The Pinnacle odds API in numbers

Pinnacle → your bot · Europe
about 30 ms
From the moment Pinnacle publishes a price to the moment it reaches a bot hosted in Europe.
Events with open markets
3,000+
Live and upcoming fixtures, main events and their child records.
WebSocket you need
1
Filter by sport and event on the same connection.

Live13 sports

  • 01 Soccer
  • 02 Tennis
  • 03 Basketball
  • 04 Hockey
  • 05 American Football
  • 06 Baseball
  • 07 Rugby
  • 08 MMA
  • 09 Boxing
  • 10 Volleyball & handball
  • 11 Esports
  • 12 Golf
  • 13 Cricket
Markets

How it works

Pinnacle updates.
You get it in milliseconds.

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

Pinnacle odds in your bot.
Connected in 5 minutes.

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.

  1. Paste this into your agent 1 min

    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.

  2. Review it and run it 4 min

    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.

Your coding agent · ~/my-bot 3:28 to first odds
You

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.

Agent

Read the guide

What should happen with each price?

You

Print the moneyline when it moves.

Agent

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:

The code it writes, in short

Minimal client

# 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":[ …

An odds message

message.json Trimmed for display
{"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 }}
  1. Line 1: type names the message. data is the frame as published.
  2. Line 4: The last segment is the channel. Any we do not list is other.
  3. Line 7: Event id. Key your book on it, or follow it alone with events. On "op": "del" the event is gone: drop it.
  4. Line 17: Unique per event. Period 0 is the full match, m the moneyline.
  5. Line 20: Keep the highest per key. Equal to or lower than yours: stale, skip it.
  6. Line 24: American odds, as quoted: 230 is +230, or 3.30 decimal.
  7. Line 27: Anything but open means the market is closed: remove it. So are all markets of a period that rec.periods lists as closed or settled.
  8. Line 32: When it was published, in Unix ms.

Live, pre-match, no-vig and CLV

Pinnacle odds,
without the vig.

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

Two ways in.
The same Pinnacle odds.

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.

7 days Free

Free trial

Start here

The real feed, REST and WebSocket, on your own bot. Pay only if it earns its place.

Try it free for 7 days

No card. Sign up, then we switch it on.

REST

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%

  • REST API: events, markets, odds
  • Live and pre-match, all 13 sports
  • No-vig, fair and closing odds
  • One key, one IP at a time
  • Not included: Live WebSocket stream
  • 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

Pinnacle odds API,
questions answered.

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 days

Or ask us: [email protected]

What is OddsPush?

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.

How does the free trial work?

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 or REST + WebSocket: which one do I need?

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.

How much does the Pinnacle odds API cost?

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.

How fast is it, really?

~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.

What exactly do I get?

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.

Do you give closing lines for CLV?

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.

Which sports and markets are covered?

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.

Does Pinnacle still have a public API?

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.

How do I get Pinnacle odds in Python or Node.js?

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.

Is OddsPush the official Pinnacle API?

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.

Can I run my bot from more than one machine?

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.

How do I pay, and can I cancel?

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.