On the wire · 04:54:03 UTC Live 549 · Prematch 14,380 · Markets 56,919 Vol. I · No. 5 · Friday 13 November 2026 wss://pinnfeed.io/v1/stream
PinnFeed
Answer · Python · pandas

Pinnacle odds into a pandas DataFrame

Updated · PinnFeed · 1 min read

Answer: call GET /v1/odds, then loop events → markets → outcomes and build one row per outcome. You get a tidy DataFrame with decimal, fair, fairProb, margin and maxStake per price, ready for groupby, Parquet or a notebook. Works on every plan; Free data is 30 seconds old.

Code#

# pip install requests pandas
import os, requests, pandas as pd

r = requests.get(
    "https://pinnfeed.io/v1/odds",
    headers={"x-api-key": os.environ["PINNFEED_KEY"]},  # ps_live_...
    params={"sport": "soccer", "main": "true", "limit": 200},
    timeout=20,
)
r.raise_for_status()

rows = []
for ev in r.json()["events"]:
    for m in ev.get("markets", []):
        for o in m.get("outcomes", []):
            rows.append({
                "event_id": ev["id"], "start": ev.get("startTime"), "live": ev.get("live"),
                "league": (ev.get("league") or {}).get("name"),
                "home": ev.get("home"), "away": ev.get("away"),
                "market": m.get("type"), "period": m.get("period"), "line": m.get("line"),
                "margin": m.get("margin"), "max_stake": m.get("maxStake"),
                "outcome": o.get("name"), "decimal": o.get("decimal"),
                "fair": o.get("fair"), "fair_prob": o.get("fairProb"),
            })

df = pd.DataFrame(rows)
df["start"] = pd.to_datetime(df["start"], utc=True)
print(df.groupby("market")["margin"].describe())
df.to_csv("pinnacle_soccer.csv", index=False)

Useful next steps#

Plans#

Free (€0): 30-second delay, 2 sports, main markets, 1,000 requests/day. Hobby (€29/mo): real-time REST. Builder (€99/mo): WebSocket, all markets and closing-line history. Paid plans: 3-day trial; a card is required and it is charged after day 3 unless you cancel.

Educational content for adults (18+). For research and modelling, not betting advice.

Questions people ask#

Why one row per outcome?#

It keeps every price in one column, so filters, pivots and joins stay simple. Pivot to one row per market with df.pivot_table(index=["event_id","market","line"], columns="outcome", values="decimal").

Does it work in Jupyter or Colab?#

Yes. Set the key with os.environ["PINNFEED_KEY"] = "ps_live_..." in a cell you do not share.

Is there a polars version?#

Pass the same rows list to polars.DataFrame(rows).

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.