Skip to content
SportsDataAPI Real-time sports data
Tutorial 13 min read Feb 12, 2026

How to Use an Odds API with Python (Step-by-Step)

Learn how to pull live sports betting odds into Python using The Odds API. This step-by-step tutorial covers setup, API calls, data parsing, storage, and automation.

SportsDataAPI Team
How to Use an Odds API with Python (Step-by-Step)

If you want to build a sports betting app, a price comparison tool, or a data-driven model, you need reliable odds data. An odds API gives you structured, real-time betting lines from dozens of bookmakers in a single JSON response. Python makes it easy to fetch, parse, and store that data.

This tutorial walks through the entire process using The Odds API, one of the most popular sports betting APIs available. By the end, you will have working Python code that fetches live odds, compares bookmaker prices, and saves everything to a local database. If you are new to pulling sports data with code, start with our beginner Python tutorial first.

Prerequisites

Before writing any code, make sure you have the following three things ready.

Python 3.8+

Any recent version of Python will work. Open a terminal and verify your installation:

python --version

If you see Python 3.8 or higher, you are good to go. If not, download the latest version from python.org.

API Key (Free Tier)

The Odds API offers a free tier with 500 requests per month. That is enough to follow this tutorial and build a working prototype. You will grab your key in Step 1 below.

Required Libraries

You need two Python libraries: requests for making HTTP calls and tabulate for formatting output as tables. Install both with pip:

pip install requests tabulate

That is it for setup. Three dependencies, zero configuration files.

Step 1: Get Your API Key

Head to the-odds-api.com and sign up for a free account. The process takes about 30 seconds:

  • Enter your email address on the homepage
  • Check your inbox for a confirmation email
  • Your API key will be included in that email

Copy the key and store it somewhere safe. You will pass it as a query parameter on every API call. Never hardcode it directly in files you share publicly. A simple approach is to use an environment variable:

import os

API_KEY = os.environ.get("ODDS_API_KEY", "your_api_key_here")

Set the variable in your terminal before running scripts:

export ODDS_API_KEY="abc123your_key_here"

The free plan gives you 500 requests per month. Each request to the /odds endpoint costs 1 credit per region per market. A single call fetching h2h odds for the us region costs exactly 1 credit. You can track your remaining balance through response headers, which we will cover in Step 2.

Step 2: Make Your First API Call

The Odds API v4 base URL is https://api.the-odds-api.com/v4. Every endpoint requires your api_key as a query parameter. Let us start with two fundamental calls.

List Available Sports

The /sports endpoint returns every sport currently in season. This call does not cost any credits, so you can use it freely:

import requests
import os

API_KEY = os.environ.get("ODDS_API_KEY", "your_api_key_here")
BASE_URL = "https://api.the-odds-api.com/v4"

response = requests.get(f"{BASE_URL}/sports", params={
    "api_key": API_KEY
})

if response.status_code == 200:
    sports = response.json()
    for sport in sports:
        print(f"{sport['key']:40s} {sport['title']}")
else:
    print(f"Error: {response.status_code} - {response.text}")

This prints a list like:

americanfootball_nfl                     NFL
basketball_nba                           NBA
baseball_mlb                             MLB
soccer_epl                               EPL
icehockey_nhl                            NHL

The key field is what you pass to other endpoints. The value upcoming is a special key that returns the next 8 events across all sports, which is useful for testing.

Fetch Odds for a Sport

Now let us pull live odds. This is the core endpoint and it does consume credits:

SPORT = "basketball_nba"
REGIONS = "us"
MARKETS = "h2h"

response = requests.get(f"{BASE_URL}/sports/{SPORT}/odds", params={
    "api_key": API_KEY,
    "regions": REGIONS,
    "markets": MARKETS,
    "oddsFormat": "american",
    "dateFormat": "iso",
})

if response.status_code == 200:
    events = response.json()
    print(f"Found {len(events)} upcoming events")

    # Check your remaining quota
    remaining = response.headers.get("x-requests-remaining")
    used = response.headers.get("x-requests-used")
    print(f"Quota: {used} used, {remaining} remaining")
else:
    print(f"Error: {response.status_code} - {response.text}")

Key parameters to know:

  • regions: Controls which bookmakers appear. Options include us, us2, uk, au, and eu. You can combine them with commas like us,uk but each region costs 1 additional credit.
  • markets: The type of bet. h2h is moneyline, spreads is point spread, and totals is over/under. Multiple markets can be comma-separated.
  • oddsFormat: Either decimal (1.91) or american (-110). American is standard in the US market.

The response headers x-requests-remaining and x-requests-used let you monitor your quota in real time. Always check these to avoid hitting your limit unexpectedly.

Step 3: Parse and Display the Data

The raw JSON response contains nested bookmaker and market data. Let us extract and format it.

Extract Bookmaker Odds

Each event in the response contains an array of bookmakers, and each bookmaker contains an array of markets with outcomes. Here is how to drill into that structure:

for event in events:
    home = event["home_team"]
    away = event["away_team"]
    start = event["commence_time"]

    print(f"\n{away} @ {home}")
    print(f"Start: {start}")

    for bookmaker in event["bookmakers"]:
        bk_name = bookmaker["title"]
        for market in bookmaker["markets"]:
            if market["key"] == "h2h":
                outcomes = market["outcomes"]
                odds_str = " | ".join(
                    f"{o['name']}: {o['price']}" for o in outcomes
                )
                print(f"  {bk_name:20s} {odds_str}")

This produces output like:

Lakers @ Celtics
Start: 2026-02-13T00:00:00Z
  FanDuel              Celtics: -180 | Lakers: +155
  DraftKings           Celtics: -175 | Lakers: +150
  BetMGM               Celtics: -170 | Lakers: +145

Compare Across Bookmakers

Finding the best price across bookmakers is one of the main reasons developers use a betting API. Here is a function that extracts the best odds for each outcome:

def find_best_odds(event):
    best = {}
    for bookmaker in event["bookmakers"]:
        for market in bookmaker["markets"]:
            if market["key"] == "h2h":
                for outcome in market["outcomes"]:
                    name = outcome["name"]
                    price = outcome["price"]
                    if name not in best or price > best[name]["price"]:
                        best[name] = {
                            "price": price,
                            "bookmaker": bookmaker["title"]
                        }
    return best

for event in events:
    print(f"\n{event['away_team']} @ {event['home_team']}")
    best = find_best_odds(event)
    for team, info in best.items():
        print(f"  Best for {team}: {info['price']} ({info['bookmaker']})")

With American odds, a higher number is better for the bettor on both sides. For positive odds (+150 vs +145), +150 pays more. For negative odds (-170 vs -180), -170 costs less for the same payout.

Format as a Table

The tabulate library makes console output much easier to read:

from tabulate import tabulate

rows = []
for event in events:
    best = find_best_odds(event)
    for team, info in best.items():
        rows.append([
            f"{event['away_team']} @ {event['home_team']}",
            team,
            info["price"],
            info["bookmaker"]
        ])

print(tabulate(rows, headers=["Game", "Team", "Best Odds", "Bookmaker"]))

Output:

Game                      Team       Best Odds  Bookmaker
------------------------  --------  ----------  ----------
Lakers @ Celtics          Celtics         -170  BetMGM
Lakers @ Celtics          Lakers          +155  FanDuel
Warriors @ Bucks          Bucks           -145  DraftKings
Warriors @ Bucks          Warriors        +130  FanDuel

Step 4: Store Data for Analysis

Fetching odds once is useful. Storing them over time is where things get interesting. Historical price movements reveal line shopping opportunities, closing line value, and market inefficiencies.

Save to CSV/JSON

For quick storage, CSV or JSON work fine. Here is a CSV approach that appends new data on each run:

import csv
from datetime import datetime

def save_odds_csv(events, filename="odds_history.csv"):
    file_exists = os.path.exists(filename)

    with open(filename, "a", newline="") as f:
        writer = csv.writer(f)
        if not file_exists:
            writer.writerow([
                "timestamp", "sport", "game", "bookmaker",
                "market", "team", "price"
            ])

        timestamp = datetime.now().isoformat()
        for event in events:
            game = f"{event['away_team']} @ {event['home_team']}"
            for bookmaker in event["bookmakers"]:
                for market in bookmaker["markets"]:
                    for outcome in market["outcomes"]:
                        writer.writerow([
                            timestamp,
                            event["sport_key"],
                            game,
                            bookmaker["title"],
                            market["key"],
                            outcome["name"],
                            outcome["price"]
                        ])

    print(f"Saved {len(events)} events to {filename}")

save_odds_csv(events)

Each run appends rows, building a time series of odds snapshots. You can also save as JSONL (one JSON object per line) for easier programmatic access:

import json

def save_odds_jsonl(events, filename="odds_history.jsonl"):
    timestamp = datetime.now().isoformat()
    with open(filename, "a") as f:
        for event in events:
            for bookmaker in event["bookmakers"]:
                for market in bookmaker["markets"]:
                    for outcome in market["outcomes"]:
                        record = {
                            "timestamp": timestamp,
                            "sport": event["sport_key"],
                            "home": event["home_team"],
                            "away": event["away_team"],
                            "bookmaker": bookmaker["title"],
                            "market": market["key"],
                            "team": outcome["name"],
                            "price": outcome["price"]
                        }
                        f.write(json.dumps(record) + "\n")

Build a Simple Database

For anything beyond a quick prototype, SQLite gives you structured queries without any external server:

import sqlite3

def init_db(db_path="odds.db"):
    conn = sqlite3.connect(db_path)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS odds (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            timestamp TEXT,
            sport TEXT,
            home_team TEXT,
            away_team TEXT,
            commence_time TEXT,
            bookmaker TEXT,
            market TEXT,
            outcome_name TEXT,
            price REAL
        )
    """)
    conn.commit()
    return conn

def store_odds(conn, events):
    timestamp = datetime.now().isoformat()
    rows = []
    for event in events:
        for bookmaker in event["bookmakers"]:
            for market in bookmaker["markets"]:
                for outcome in market["outcomes"]:
                    rows.append((
                        timestamp,
                        event["sport_key"],
                        event["home_team"],
                        event["away_team"],
                        event["commence_time"],
                        bookmaker["title"],
                        market["key"],
                        outcome["name"],
                        outcome["price"]
                    ))

    conn.executemany("""
        INSERT INTO odds (
            timestamp, sport, home_team, away_team,
            commence_time, bookmaker, market, outcome_name, price
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
    """, rows)
    conn.commit()
    print(f"Stored {len(rows)} odds records")

conn = init_db()
store_odds(conn, events)

Now you can run SQL queries against your stored data:

# Find the best historical odds for a team
cursor = conn.execute("""
    SELECT bookmaker, price, timestamp
    FROM odds
    WHERE outcome_name = 'Celtics' AND market = 'h2h'
    ORDER BY price DESC
    LIMIT 5
""")

for row in cursor.fetchall():
    print(f"  {row[0]:20s} {row[1]:>6} at {row[2]}")

Next Steps

You now have a working pipeline that fetches odds, parses them, and stores the results. Here are three ways to take it further.

Add More Sports

Replace the SPORT variable with any key from the /sports endpoint. The Odds API covers NFL, NBA, MLB, NHL, soccer leagues, tennis, MMA, and many more. You can also use upcoming to pull the next 8 events across all sports at once:

SPORT = "upcoming"

Request multiple markets in a single call to get moneyline, spreads, and totals together:

MARKETS = "h2h,spreads,totals"

Each additional market costs 1 credit per region, so a call with 3 markets and 1 region costs 3 credits. For a deeper look at what is available across different providers, see our comparison of the best sports betting APIs.

Build a Dashboard

Once you have data flowing into SQLite, you can build a simple web dashboard with Flask or Streamlit to visualize odds movements in real time. A minimal Streamlit app takes about 20 lines of code:

import streamlit as st
import sqlite3
import pandas as pd

conn = sqlite3.connect("odds.db")
df = pd.read_sql("SELECT * FROM odds ORDER BY timestamp DESC LIMIT 500", conn)

st.title("Live Odds Dashboard")
sport_filter = st.selectbox("Sport", df["sport"].unique())
filtered = df[df["sport"] == sport_filter]
st.dataframe(filtered)

That gives you a filterable table updated every time you refresh the page. For a production dashboard, you would add auto-refresh, charts, and alerts when lines move beyond a threshold.

Automate with Cron

To build a meaningful dataset, schedule your script to run at regular intervals. A cron job that pulls NBA odds every 15 minutes looks like this:

*/15 * * * * cd /path/to/project && python fetch_odds.py >> logs/cron.log 2>&1

At 4 calls per hour, that is 96 calls per day, which fits comfortably within the free tier for a single sport and region. If you need higher frequency or more coverage, paid plans start at around $25 per month with 10,000+ credits. Our complete guide to sports betting APIs covers pricing and rate limits across all major providers.

FAQ

Is the odds API free?

Yes. The Odds API offers a free tier with 500 requests per month. Each request to the odds endpoint costs 1 credit per region per market. That means a single call for NBA moneyline odds from US bookmakers costs 1 credit. The free tier is enough to build and test a working application. Paid plans range from around $25 to $249 per month and offer between 10,000 and 250,000+ monthly credits.

How many requests can I make?

On the free plan, 500 per month. The /sports and /events endpoints do not count against your quota, so you can call those as often as you need. The /odds endpoint is where credits get consumed. A request for 2 markets across 2 regions costs 4 credits (2 x 2). You can monitor your balance in real time using the x-requests-remaining and x-requests-used response headers that come back with every API call.

What Python libraries do I need?

Just requests for making HTTP calls to the API. That is the only hard requirement. This tutorial also uses tabulate for formatted console output, sqlite3 (included in Python’s standard library) for local storage, and csv/json (also standard library) for file-based storage. No heavy frameworks or complex dependencies are needed.

Already have an account?