Odds Comparison API: Compare 372 Bookmakers in Python (Free)

Odds Comparison API - OddsPapi API Blog
How To Guides June 22, 2026

The Odds Comparison API You Actually Wanted

You have used the odds comparison sites. Oddschecker, Oddspedia, OddsJam: they all do one thing well, line up the same bet across dozens of bookmakers so you can see who is pricing it best. The problem is obvious the moment you try to build anything on top of them. None of the consumer comparison sites ship a public API. You are left scraping a table that changes its markup every other week, or paying enterprise rates for a feed locked behind a sales call.

This guide skips that. OddsPapi is an odds comparison API: one HTTP call returns every bookmaker’s price for a fixture as clean JSON, across 372 bookmakers and 69 sports, on a free tier. You build the comparison grid yourself in about 30 lines of Python, with no scraping and no contract.

Why Scraping a Comparison Site Is a Dead End

The comparison sites are products, not data providers. Their whole business is the front end, so the data underneath is deliberately hard to reach:

  • No public API. Oddschecker and Oddspedia have no documented developer endpoint. jedibets (a sharp player-props screener with Discord alerts) is a tool, not a feed. You cannot query them programmatically without reverse-engineering private calls.
  • Markup roulette. A scraper keys off CSS classes and DOM structure. One redesign and your parser is dead. You end up maintaining a brittle scraper instead of shipping features.
  • Rate limits and blocks. Hit a consumer site hard enough to power your own product and you get Cloudflare-challenged or IP-banned.
  • Enterprise feeds cost a sales call. The Sportradar / Genius tier gives you the data, but only after pricing negotiation and a contract.

OddsPapi is the third option: enterprise-grade coverage (sharps like Pinnacle, crypto books, exchanges, prediction markets) at a developer price, queried over a plain REST endpoint with a free key.

The Old Way OddsPapi Comparison API
Scrape Oddschecker HTML, re-fix on every redesign One GET /odds call returns clean JSON
~40 bookmakers on a typical comparison site 372 bookmakers, including sharps and exchanges
No historical prices without paying Free historical odds endpoint for backtesting
Poll a web page and hope it loads REST now, WebSocket push for real-time
Risk of IP bans and CAPTCHAs API key auth, documented rate limits

Build a Live Odds Comparison Grid in Python

The goal is the same data structure every comparison site renders: rows of bookmakers, columns of outcomes, each cell a price, with the best price in each column flagged. Here is the whole thing against the live API.

Step 1: Authenticate

Auth is a query parameter, never a header. That is the single most common mistake when starting out.

import requests

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.oddspapi.io/v4"

def api_get(path, **params):
    params["apiKey"] = API_KEY
    r = requests.get(f"{BASE_URL}/{path}", params=params)
    r.raise_for_status()
    return r.json()

# Smoke test: should print 200-style data
print(len(api_get("sports")), "sports available")

Step 2: Find a Fixture

Pull fixtures for a sport in a date window and keep the ones that already have odds. Sport IDs come from /sports (baseball is 13). The from/to window is capped at 10 days.

from datetime import datetime, timedelta

now = datetime.utcnow()
window = {
    "from": now.strftime("%Y-%m-%dT%H:%M:%S"),
    "to": (now + timedelta(days=1)).strftime("%Y-%m-%dT%H:%M:%S"),
}

fixtures = api_get("fixtures", sportId=13, **window)
live = [f for f in fixtures if f.get("hasOdds")]
live.sort(key=lambda f: f["startTime"])  # nearest first = best coverage

game = live[0]
print(game["participant1Name"], "vs", game["participant2Name"], game["fixtureId"])

One thing to know: a fixture can report hasOdds: true days out but only return a populated book list once it is close to start time. Sort by start time and take the nearest fixtures and you get the deepest comparison.

Step 3: Fetch Every Book’s Price

A single /odds call returns all bookmakers pricing the fixture. The payload is deeply nested, so never assume flat JSON. The path to a price is bookmakerOdds[slug]['markets'][marketId]['outcomes'][outcomeId]['players']['0']['price'].

odds = api_get("odds", fixtureId=game["fixtureId"])
books = odds["bookmakerOdds"]
print(len(books), "bookmakers:", sorted(books))

On Atlanta Braves vs Milwaukee Brewers this returned 15 bookmakers on the one fixture, including Pinnacle (the sharp benchmark), Kalshi and Polymarket (prediction markets), and the US books DraftKings, FanDuel, BetMGM, Caesars and William Hill.

Step 4: Build the Comparison Grid

Now the core. Pick a market (moneyline on baseball is market 131, “Winner incl. extra innings”; its two outcomes are 131 home and 132 away). Walk every book, pull the current price for each outcome, and skip anything inactive or zero-priced.

def comparison_grid(books, market_id, outcome_ids):
    """Return {bookmaker: {outcome_id: price}} for active prices only."""
    grid = {}
    for slug, data in books.items():
        market = data.get("markets", {}).get(str(market_id))
        if not market:
            continue
        row = {}
        for oid in outcome_ids:
            leg = market.get("outcomes", {}).get(str(oid), {})
            price = leg.get("players", {}).get("0")
            # sportsbooks: players['0'] is a dict with one current price
            if price and price.get("active") and price.get("price"):
                row[oid] = price["price"]
        if len(row) == len(outcome_ids):  # only books pricing the full market
            grid[slug] = row
    return grid

grid = comparison_grid(books, market_id=131, outcome_ids=[131, 132])

# Print it like a comparison table
print(f"{'Bookmaker':16} {'Braves':>8} {'Brewers':>8}")
for slug in sorted(grid):
    print(f"{slug:16} {grid[slug][131]:>8} {grid[slug][132]:>8}")

Live output (11 of the 15 books priced this market):

Bookmaker         Braves  Brewers
betmgm              1.87     1.95
borgata             1.87     1.95
caesars             1.87    1.952
draftkings          1.84     1.98
fanduel             1.85      2.0
hardrockbet          1.8     2.05
kalshi             1.887    2.041
pinnacle           1.909     2.02
pointsbet.com.au    1.83      2.0
polymarket         1.923    2.041
williamhill         1.87    1.952

Step 5: Flag the Best Price and the Spread

The reason anyone compares odds: the best available number is meaningfully better than the worst. Add two helpers.

def best_per_outcome(grid, outcome_ids):
    out = {}
    for oid in outcome_ids:
        prices = {slug: row[oid] for slug, row in grid.items()}
        best_book = max(prices, key=prices.get)
        worst = min(prices.values())
        out[oid] = {"best_book": best_book, "best": prices[best_book],
                    "worst": worst,
                    "spread_pct": (prices[best_book] / worst - 1) * 100}
    return out

for oid, info in best_per_outcome(grid, [131, 132]).items():
    print(oid, info)

Result, with real numbers:

  • Braves (home): best price 1.923 at Polymarket, worst 1.80 at HardRock. A 6.8% spread on the exact same bet.
  • Brewers (away): best price 2.05 at HardRock, worst 1.95 at BetMGM/Borgata.

Notice the prediction market (Polymarket) topped the field on the favorite while a US sportsbook (HardRock) led on the underdog. That cross-category coverage is exactly what a 40-book comparison site misses. These are best available prices, not value calls: against Pinnacle’s no-vig fair line (below) both sit just under fair. If your goal is to always bet the best number, our line shopping guide turns this grid into a single best-price helper.

Step 6: Anchor It to the Sharp Line

A comparison grid is more useful when you know the fair price. Pinnacle runs the tightest margin in the field, so strip its vig and use it as the benchmark.

def devig_two_way(p_home, p_away):
    ih, ia = 1 / p_home, 1 / p_away
    overround = ih + ia
    return {
        "overround_pct": (overround - 1) * 100,
        "fair_home": overround / ih,   # fair decimal odds
        "fair_away": overround / ia,
        "prob_home": ih / overround,
        "prob_away": ia / overround,
    }

pin = grid["pinnacle"]
print(devig_two_way(pin[131], pin[132]))

Pinnacle priced the Braves at 1.909 and the Brewers at 2.02, an overround of just 1.89%. Stripped of vig that is Braves 51.4% (fair 1.946) and Brewers 48.6% (fair 2.058). Now every cell in your grid can be read against a real fair line instead of guessed at. To benchmark across many books at once, see consensus odds, and to measure each book’s margin, the vig calculator.

One Function, Any Market

The grid builder is market-agnostic. To compare a soccer 1X2 board, pass market_id=101 and outcome_ids=[101, 102, 103] (home, draw, away). For totals, pass the over/under outcome pair. Look the IDs up per sport instead of hardcoding them:

catalog = api_get("markets", sportId=13)
market_names = {m["marketId"]: m["marketName"] for m in catalog}
outcome_names = {(m["marketId"], o["outcomeId"]): o["outcomeName"]
                 for m in catalog for o in m.get("outcomes", [])}
# market_names[131] -> "Winner (incl. extra innings)"

How OddsPapi Compares to the Comparison Sites

Service Type Public API? Books Historical
Oddschecker Consumer screener No ~40 No
Oddspedia Consumer screener No ~40 Limited
jedibets Player-props screener + alerts No Props focus No
OddsJam Tool + paid API Paid ~70 Paid
OddsPapi Developer API Yes, free tier 372 Free

If you want to read a comparison, the consumer sites are fine. If you want to build one, or feed compared prices into a model, an alert bot, or your own front end, you need an API. That is the gap OddsPapi fills. Want a ready-made UI? See our Streamlit odds comparison dashboard, or weigh the providers in our best odds APIs of 2026 rundown.

Frequently Asked Questions

Is there an Oddschecker API?

No. Oddschecker does not publish a public developer API. To get the same multi-bookmaker comparison data programmatically, use an odds API like OddsPapi, which returns every book’s price for a fixture as JSON on a free tier.

What is an odds comparison API?

An odds comparison API returns the same bet’s price across many bookmakers in one call, so you can line them up, find the best price, and measure the spread. OddsPapi covers 372 bookmakers and 69 sports over a single REST endpoint.

How many bookmakers can I compare at once?

A single /odds call returns every bookmaker pricing that fixture. In practice that is around 9 to 15 books on a typical fixture and more on marquee events, spanning sharps (Pinnacle), US sportsbooks, exchanges, and prediction markets (Kalshi, Polymarket).

Can I get historical odds for backtesting?

Yes. The /historical-odds endpoint returns the full price history for a fixture (up to 3 bookmakers per call) on the free tier, where most comparison sites charge for or do not offer historical data.

Do I need to convert American or fractional odds?

No. Every price ships pre-converted: price is decimal, priceAmerican and priceFractional are strings in those formats. Pick the field you need.

Stop Scraping. Start Comparing.

The consumer comparison sites solved the display problem and locked the data away. OddsPapi hands you the data: 372 bookmakers, free historical odds, and a WebSocket feed when you need real-time, all on a free key. Build your own grid, your own alerts, your own edge. Grab your free API key and run the code above in five minutes.