Pinnacle Odds API Tutorial: From Zero to Your First Live Board
A hands-on walkthrough: get a key, prove the connection, pull a live board, parse it in Python, and handle the errors you'll actually hit.
Pinnacle Odds API Tutorial: From Zero to Your First Live Board
The goal of this post is narrow and practical: get you from nothing to a parsed, live Pinnacle board in about fifteen minutes, and leave you with code that fails gracefully instead of mysteriously. We'll use pinnapi's endpoints, but the structure — authenticate, verify, snapshot, parse, recover — is the same for any odds feed.
What you actually need
- A terminal with
curl - Python 3.10+ with
requests - A pinnapi key (the free tier is enough: 100 REST requests/day, no card)
That's it. No Pinnacle account; pinnapi is independent.
Step 1 — Grab a trial key
Sign in at the panel and copy your key. Treat it like a password — it's your identity to the API, so keep it out of source control and read it from an environment variable:
export PINNAPI_KEY="your_key_here"
Step 2 — Prove you're connected before you debug a phantom
Half of all "the API is broken" tickets are actually auth or networking issues. Confirm the basics before you write any real logic:
curl -i -H "x-portal-apikey: $PINNAPI_KEY" \
"https://pinnapi.com/health"
A 200 with a small JSON body means your key works and you can reach the service. A 401 means the key is wrong or a missing/misnamed x-portal-apikey header. A timeout means it's your network or a proxy, not the API. Knowing which of those three you're looking at saves an hour later.
Step 3 — Pull your first live board
curl -H "x-portal-apikey: $PINNAPI_KEY" \
"https://pinnapi.com/kit/v1/markets?sport_id=1&event_type=live"
You'll get a list of fixtures, each with its markets (moneyline, totals, spreads) and current prices. If the list is empty, there may genuinely be no live soccer right now — switch event_type=live to event_type=prematch to confirm you're getting data at all.
Step 4 — Parse it in Python
import os, requests
KEY = os.environ["PINNAPI_KEY"]
BASE = "https://pinnapi.com/kit/v1"
def get_markets(sport_id=1, event_type="live"): # 1 = soccer
r = requests.get(
f"{BASE}/markets",
headers={"x-portal-apikey": KEY},
params={"sport_id": sport_id, "event_type": event_type},
timeout=5,
)
r.raise_for_status()
return r.json()["events"]
for ev in get_markets():
ml = ev.get("periods", {}).get("num_0", {}).get("money_line") # full-match 1X2
if not ml:
continue
print(f'{ev["home"]} vs {ev["away"]}: {ml["home"]} / {ml.get("draw")} / {ml["away"]}')
Note the timeout=5 and raise_for_status(). Those two lines are the difference between a script that hangs forever on a bad night and one that tells you what went wrong.
Step 5 — Drill into one event efficiently
Once you've found a fixture you care about, don't keep pulling the whole board. Request just that fixture:
def get_fixture(fixture_id):
r = requests.get(
f"{BASE}/fixtures/{fixture_id}",
headers={"x-portal-apikey": KEY},
timeout=5,
)
r.raise_for_status()
return r.json()
On the free tier you have 100 requests/day, so scoping calls to single fixtures is the habit that keeps you under the cap while you're learning.
A word on big tournaments
During something like the 2026 World Cup, live markets update fast and often. REST polling will work for getting started, but you'll feel the staleness: by the time your next poll fires, the price has already moved. That's the moment to graduate from polling to a stream.
The capability worth graduating to: odds drops
The reason people pay for a Pinnacle feed isn't the snapshot — it's catching the move. pinnapi exposes drops over SSE so you're notified the instant a price falls, rather than discovering it on your next poll. In our benchmark that's roughly 15–40 ms end-to-end (measure your own region before relying on it). There's a dedicated SSE walkthrough if you want to go there next.
Troubleshooting, quickly
| Symptom | Most likely cause | Fix |
|---|---|---|
401 Unauthorized | Missing or wrong key / wrong header name | Re-check the header exactly |
429 Too Many Requests | Hit the free-tier cap | Scope calls to single fixtures; upgrade if needed |
Empty events list | No events live right now | Try event_type=prematch to confirm data flow |
| Script hangs | No request timeout | Always pass timeout= |
Takeaway
You now have a working auth flow, a live snapshot, a parser, and the error handling that keeps it alive in production. The natural next step is replacing the poll with a drop stream — that's where the speed actually pays off.
FAQ
Do I need a Pinnacle account? No. pinnapi is independent and not affiliated with Pinnacle.
Is the free tier enough to follow this? Yes — every call here fits inside 100 requests/day.
What languages are supported? Anything that can make an HTTP request; official examples cover curl, Python, Node.js, Go, and PHP.
Get real-time Pinnacle odds in your code
Live & prematch markets with sub-second odds-drop alerts. Free trial key in seconds — no card.
Start free trial