When a price at Pinnacle drops sharply, it usually means money hit the market and the line moved. Pinnacle is the reference book
for a lot of modellers because it runs a low margin and moves its line instead of limiting winning players, so its drops are
worth watching.
Pinnacle closed its public API in July 2025. In this post I'll use PinnRadar, an independent API that
serves Pinnacle's live and prematch odds, with its small Python client.
## Setup
pip install pinnradar Grab a free key at pinnradar.com (just an email) and export it:
export PINNRADAR_API_KEY=your_key_here ## 1. Watch for drops (works on the free key)
The /api/drops endpoint keeps a rolling buffer of recent price drops across every sport. This script polls it and prints each
new drop once:
import time from pinnradar import Client pr = Client() # reads PINNRADAR_API_KEY seen = set() while True: for d in pr.drops(mode="live", min_drop_pct=5, max_age_sec=900): key = (d["ts"], d["market_key"], d["event_id"]) if key in seen: continue seen.add(key) pick = d.get("designation") or d.get("side") or "" line = f' {d["points"]}' if d.get("points") is not None else "" print(f'{d["sport_name"]:<8} {d["home"]} v {d["away"]} | {d["market"]} {pick}{line} | ' f'{d["from"]:.2f} -> {d["to"]:.2f} (-{d["drop_pct"]:.1f}%)') time.sleep(900) # the free key allows 100 calls a day Real output from a run:
Basketball Mobis Phoebus v Goyang Skygunners | team_total over 88.5 | 1.77 -> 1.68 (-5.1%) Tennis Adrian Oetzbach v Radu David Turcanu | spread home -1.5 | 2.77 -> 2.52 (-9.0%) Tennis Adrian Oetzbach v Radu David Turcanu | moneyline home | 1.93 -> 1.79 (-6.9%) Useful filters: sport_id=1 for soccer only, mode="prematch" for upcoming games, markets=["total"] for totals only.
## 2. Turn prices into fair (no-vig) odds
Bookmaker prices include a margin. To get the market's actual estimate, remove it across every outcome of the market:
from pinnradar import Client, no_vig pr = Client() board = pr.prematch_fixtures(sport_id=1) # upcoming soccer for ev in board["events"]: ml = (ev.get("periods") or {}).get("num_0", {}).get("money_line") or {} if ml.get("home") and ml.get("draw") and ml.get("away"): prices = [ml["home"], ml["draw"], ml["away"]] print(ev["home"], "v", ev["away"], prices, "fair", [round(x, 2) for x in no_vig(prices)]) break Liverpool v Manchester United [1.719, 4.28, 4.45] fair [1.79, 4.45, 4.63] Those fair odds are what people compare against softer bookmakers when hunting for value, or use as the closing line to measure
CLV. If you just want to play with the math, there is a no-vig calculator that
loads live Pinnacle prices.
## 3. Going real-time
Polling every 15 minutes is fine for research, but drops matter most in the seconds after they happen. Paid plans can stream
every drop over Server-Sent Events, and the client handles reconnects:
for d in pr.stream_drops(min_drop=5): print(d["home"], d["sect"], d["outcome"], d["from_price"], "->", d["to_price"], "fair", d["nvp"]) There is also a WebSocket feed with every price change, and an MCP server so Claude or Cursor can query odds directly.
## Links
- Python client: pypi.org/project/pinnradar (source on GitHub)
- Node.js client:
npm install pinnradar - API docs: pinnradar.com/docs
Questions or ideas for the next post? Drop them in the comments.
Data only, not betting advice. PinnRadar is independent and not affiliated with Pinnacle.

