Your First Odds API Call in Python: A Beginner’s Guide (Free Key)
You want live betting odds in a Python script. You searched, found a wall of enterprise sales pages and scraping tutorials that break every week, and bounced. This guide is the opposite: a copy-paste walkthrough that takes you from zero to a live odds payload in about five minutes, on a free API key, no credit card.
By the end you will have made four real calls: authenticate, list the sports, fetch a fixture, and read a live price from 12 bookmakers including Pinnacle. Every snippet below was run against the live API before publishing.
What You Need
- Python 3.8 or newer.
- The
requestslibrary:pip install requests. - A free OddsPapi API key. Grab one here, it takes a minute and no card is required.
That is the whole shopping list. No SDK to install, no OAuth dance, no webhook setup. The API is plain JSON over HTTPS.
The One Gotcha That Trips Up Beginners
Most APIs want your key in an Authorization header. OddsPapi does not. The key goes in the query string as apiKey. Put it in a header and you get a 401, then waste an hour wondering why. With the requests library you never build the URL by hand; you pass a params dict and it does the encoding for you.
import requests
API_KEY = "YOUR_API_KEY"
BASE = "https://api.oddspapi.io/v4"
# apiKey is a QUERY PARAMETER, not a header.
params = {"apiKey": API_KEY}
response = requests.get(f"{BASE}/sports", params=params)
print(response.status_code) # 200 means you are in
Run that. A 200 is your handshake. If you see 401, your key is wrong or you put it in a header. That is the entire authentication story.
Scraping vs One API Call
| Scraping a sportsbook | One OddsPapi call | |
|---|---|---|
| Setup | Headless browser, proxies, captcha solver | pip install requests |
| Breaks when | The site changes its HTML (weekly) | Practically never (stable JSON) |
| Books covered | One, the one you scraped | 350+ in a single response |
| Legal grey area | Yes | No, it is an API |
| Time to first data | A weekend | Five minutes |
Step 1: List the Sports
The call you already made returns every sport OddsPapi covers, each with a numeric sportId you will use everywhere else. There are 69 of them.
sports = response.json()
print(len(sports), "sports")
# Find the one you want by slug
soccer = next(s for s in sports if s["slug"] == "soccer")
print(soccer)
# {'sportId': 10, 'slug': 'soccer', 'sportName': 'Soccer'}
Soccer is sportId 10. Basketball is 11, tennis 12, baseball 13, American football 14. Hold onto that number, the next call needs it.
Step 2: Fetch Today’s Fixtures
A “fixture” is a single match (OddsPapi uses fixture for game, participant for team, tournament for league). Ask for fixtures in a date range, up to 10 days wide. Each one tells you whether it has odds yet via the hasOdds flag.
from datetime import date, timedelta
today = date.today().isoformat()
in_three = (date.today() + timedelta(days=3)).isoformat()
params = {
"apiKey": API_KEY,
"sportId": 10, # soccer
"from": today,
"to": in_three,
}
fixtures = requests.get(f"{BASE}/fixtures", params=params).json()
# Keep only matches that already have prices
live = [f for f in fixtures if f["hasOdds"]]
print(len(live), "soccer fixtures with odds")
sample = live[0]
print(sample["fixtureId"], sample["participant1Name"], "vs", sample["participant2Name"])
Each fixture carries the two team names, the tournament, the kickoff time, and the all-important fixtureId. That ID is the key to the odds.
Step 3: Read a Live Price
Now the payoff. Pass a fixtureId to /odds and you get every bookmaker’s prices for that match. We ran this on Valur Reykjavik vs Keflavik IF (Icelandic Besta deild) and got back 12 bookmakers, including the sharp books Pinnacle and SBOBet.
The response is nested. The path to a single price reads: bookmaker, then market, then outcome, then the current price under players["0"]. Market 101 is the Full Time Result (1X2); outcomes are 101 (Home), 102 (Draw), 103 (Away).
params = {
"apiKey": API_KEY,
"fixtureId": "id1000018868194688",
"bookmakers": "pinnacle,bet365",
}
odds = requests.get(f"{BASE}/odds", params=params).json()
pinnacle = odds["bookmakerOdds"]["pinnacle"]["markets"]["101"]["outcomes"]
for outcome_id, label in [("101", "Home"), ("102", "Draw"), ("103", "Away")]:
price = pinnacle[outcome_id]["players"]["0"]["price"]
print(label, price)
Home 2.05
Draw 4.48
Away 2.93
That is a live, sharp-bookmaker price in your terminal. The same payload also gives you American (priceAmerican) and fractional (priceFractional) formats, pre-converted, so you never write a converter yourself.
Step 4: Find the Best Price Across All 12 Books
One book is fine. The reason to use an aggregator is to compare them. Here is the smallest useful loop in sports betting: scan every bookmaker on the fixture and find who pays the most on the home win.
# Drop the bookmakers filter to get ALL books on the fixture
odds = requests.get(f"{BASE}/odds",
params={"apiKey": API_KEY,
"fixtureId": "id1000018868194688"}).json()
best_book, best_price = None, 0.0
for slug, book in odds["bookmakerOdds"].items():
try:
outcome = book["markets"]["101"]["outcomes"]["101"]["players"]["0"]
except KeyError:
continue # this book did not price 1X2
if outcome["active"] and outcome["price"] > best_price:
best_book, best_price = slug, outcome["price"]
print(f"Best home price: {best_price} at {best_book}")
# Best home price: 2.16 at sbobet
SBOBet pays 2.16 on the home win where Pinnacle pays 2.05. On a 100 unit stake that is 11 units of extra profit for the exact same bet, found in five lines of code. Always check active before trusting a price; suspended outcomes can carry stale numbers.
What’s Next
You now have the four moves that underpin every odds script: authenticate, discover, fetch, parse. From here the same four calls power real tools:
- 350+ bookmakers in one response means line shopping and arbitrage are just bigger versions of Step 4. See line shopping in Python.
- Free historical odds on the free tier let you backtest a model on real closing prices.
- Want a UI? Pipe the same data into a pandas DataFrame or build a full betting app.
Frequently Asked Questions
How do I authenticate with the OddsPapi API in Python?
Pass your key as the apiKey query parameter, not as a header. With the requests library, use requests.get(url, params={"apiKey": "YOUR_KEY"}). A 200 status confirms it worked; a 401 usually means the key was placed in a header by mistake.
Do I need a credit card to get a free odds API key?
No. The OddsPapi free tier requires only a sign-up, no card. It includes 350+ bookmakers and free historical odds, so you can build and backtest without paying.
What is the difference between a fixture, a tournament, and a participant?
OddsPapi uses fixture for a single match (US “game”), tournament for a league or competition, and participant for a team or player. A fixtureId is what you pass to the /odds endpoint to get prices.
Why is the odds response so deeply nested?
Because one fixture holds many bookmakers, each with many markets, each with multiple outcomes. The path to a current price is bookmaker, market, outcome, then players["0"]["price"]. Never assume flat JSON.
Can I get odds from more than one bookmaker in a single call?
Yes. Pass a comma-separated list to the bookmakers parameter, or omit it entirely to receive every book that priced the fixture (12 in the example above, up to 350+ on major matches).
Make the Call
Four calls, five minutes, zero scraping. Get your free OddsPapi API key and run the snippets above. The hardest part is already behind you.