On the wire · 04:56:49 UTC Live 566 · Prematch 14,380 · Markets 56,913 Vol. I · No. 5 · Friday 13 November 2026 wss://pinnfeed.io/v1/stream
PinnFeed
Answer · Line movement · Chart

Pinnacle line movement API

Updated · PinnFeed · 2 min read

Answer: PinnFeed gives you three pieces of line movement: the live price stream (delta frames on the Builder WebSocket), recent drops (GET /v1/drops) and the closing price (GET /v1/closing, Builder). There is no tick-by-tick history endpoint, so to chart a full line you record the deltas yourself from the moment you subscribe. The script below writes every price change to SQLite and plots one outcome.

Record deltas (Python, Builder)#

# pip install websockets
import asyncio, json, os, sqlite3, time, websockets

db = sqlite3.connect("moves.db")
db.execute("create table if not exists moves(ts real, market_id text, event_id int, type text, line real, outcome text, decimal real)")

async def main():
    url = "wss://pinnfeed.io/v1/stream"
    async with websockets.connect(url, additional_headers={"x-api-key": os.environ["PINNFEED_KEY"]}, max_size=None) as ws:
        await ws.send(json.dumps({"type": "subscribe", "sports": ["soccer"], "markets": ["moneyline", "total"], "main": True}))
        async for raw in ws:
            msg = json.loads(raw)
            if msg["type"] == "ping":
                await ws.send(json.dumps({"type": "pong"}))
            elif msg["type"] == "delta":
                for ch in msg["changes"]:
                    if ch["kind"] == "market" and ch["op"] == "upsert":
                        m = ch["market"]
                        for o in m["outcomes"]:
                            db.execute("insert into moves values(?,?,?,?,?,?,?)",
                                       (time.time(), m["id"], m["eventId"], m["type"], m.get("line"), o["name"], o["decimal"]))
                db.commit()

asyncio.run(main())

On websockets older than 14, the argument is extra_headers instead of additional_headers.

Chart one outcome#

import sqlite3, pandas as pd, matplotlib.pyplot as plt
df = pd.read_sql("select * from moves where market_id = ? and outcome = 'home'", sqlite3.connect("moves.db"), params=["<market id>"])
df["ts"] = pd.to_datetime(df["ts"], unit="s")
df.plot(x="ts", y="decimal", drawstyle="steps-post", title="Pinnacle home price")
plt.show()

Without the WebSocket#

Poll GET /v1/drops?sport=soccer&minutes=10&min_pct=5 for recent shortenings (dropping odds API) and read the close with GET /v1/closing/{eventId} (closing line API). Steam logic: steam detection in Python.

Plans#

Line recording needs the WebSocket: Builder (€99/mo), which also includes closing-line history. /v1/drops works on Free (30-second delay) and Hobby (€29/mo). Paid plans: 3-day trial; a card is required and it is charged after day 3 unless you cancel.

Educational content for adults (18+). Line movement is a statistical signal, not a tip.

Related: Pinnacle lines API, Sharp money signals via Pinnacle maxStake.

Questions people ask#

Can I download past line movement?#

Not tick by tick. You get closing prices on Builder and recent drops via /v1/drops. Bulk archive exports are an Enterprise item.

Does the snapshot count as a move?#

No. The snapshot is the starting state. Store it as your first row if you want the opening point of your recording.

How much data is this?#

It depends on how many sports and markets you subscribe to. Filter with main: true and a short market list to start.

The WebSocket plan: Builder, €99/mo.

Real-time Pinnacle odds pushed over one WebSocket, all 13 sports, every market, no-vig fair odds and closing lines. 3 days free, cancel in one click.

Form K-1 · Free API key

Get your free API key.

One field. No password, no card: enter your email, click the link we send, and your key is on screen.

By continuing you agree to the Terms and Privacy Policy.

One free key per email. Link valid for 20 minutes.