Cricket Odds API: IPL, T20 & Test Match Data from 20+ Bookmakers (Free Tier)

Cricket Odds API - OddsPapi API Blog
How To Guides May 11, 2026

Cricket Odds API: Why Getting Programmatic Access Is Harder Than It Should Be

Cricket is the second-most bet sport on the planet. The IPL alone moves more money than most American leagues combined. But if you’re a developer trying to build anything — a model, a scanner, a dashboard — you’ll quickly discover the data ecosystem is a mess.

The big bookmakers (Pinnacle, Bet365, Betfair) don’t offer public APIs. The cricket-specific data providers (CricketAPI, EntitySport) give you scores and stats, not odds. And the enterprise feeds from Sportradar and LSports will quote you five figures before you see a JSON response.

OddsPapi sits in the middle: 20+ cricket bookmakers (including Pinnacle and the Betfair Exchange), 175+ live markets per fixture, and a free tier that doesn’t require a sales call. This guide walks you through pulling IPL, T20, and Test match odds with Python.

What You Get vs. What You’re Used To

Scraping / Manual Enterprise Feed (Sportradar, LSports) OddsPapi
Cricket Bookmakers 1 at a time 10–40 49 on IPL fixtures
Sharp Lines (Pinnacle) No public API Yes (enterprise only) Yes (free tier)
Betfair Exchange Depth Requires Betfair API key Sometimes Full back/lay + liquidity
Markets per Fixture Match winner only 50–100 175+ (overs, powerplay, runs)
Historical Odds Not available $$$ add-on Free (backtest your models)
Price Free (until you get blocked) $5K–$50K/year Free tier available

Step 1: Authentication & Sport Discovery

Every OddsPapi request uses a query parameter for auth — no headers, no OAuth.

import requests
import time

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

# Verify your key works
response = requests.get(f"{BASE_URL}/sports", params={"apiKey": API_KEY})
sports = response.json()

# Find cricket
cricket = [s for s in sports if s["slug"] == "cricket"][0]
print(f"Sport: {cricket['name']} (ID: {cricket['sportId']})")
# Output: Sport: Cricket (ID: 27)

Important terminology: OddsPapi uses “tournament” where you might say “league,” “fixture” instead of “match,” and “participant” instead of “team.” The cricket sport ID is 27.

Step 2: Fetch Cricket Fixtures (IPL, PSL, T20, Tests)

The /fixtures endpoint returns upcoming and recent matches within a date range (max 10 days per call).

from datetime import datetime, timedelta

# Get the next 10 days of cricket fixtures
today = datetime.utcnow().strftime("%Y-%m-%d")
end = (datetime.utcnow() + timedelta(days=10)).strftime("%Y-%m-%d")

response = requests.get(f"{BASE_URL}/fixtures", params={
    "apiKey": API_KEY,
    "sportId": 27,
    "from": today,
    "to": end
})
fixtures = response.json()

# Filter to fixtures that actually have odds
with_odds = [f for f in fixtures if f.get("hasOdds")]
print(f"Total fixtures: {len(fixtures)}, With odds: {len(with_odds)}")

# Group by tournament
from collections import Counter
tournaments = Counter(f["tournamentName"] for f in with_odds)
for name, count in tournaments.most_common(10):
    print(f"  {name}: {count} fixtures")

A typical 10-day window gives you 50–60 fixtures with odds across the IPL, Pakistan Super League, Big Bash, Caribbean Premier League, international T20s, and domestic competitions.

Tip: Only fixtures with hasOdds: true will return pricing from the /odds endpoint. The rest are metadata-only.

Step 3: Pull Live Odds — Match Winner

The main cricket market is “Winner (incl. super over)” — market ID 271. This is your match-winner line.

# Pick an IPL fixture
ipl_fixtures = [f for f in with_odds if "Indian Premier League" in f.get("tournamentName", "")]

if not ipl_fixtures:
    print("No IPL fixtures in range — try a different date window")
else:
    fixture = ipl_fixtures[0]
    fid = fixture["fixtureId"]
    print(f"{fixture['participant1Name']} vs {fixture['participant2Name']}")
    print(f"Fixture ID: {fid}")

    time.sleep(0.2)

    # Get odds from all bookmakers
    odds_response = requests.get(f"{BASE_URL}/odds", params={
        "apiKey": API_KEY,
        "fixtureId": fid
    })
    odds_data = odds_response.json()

    # Parse match-winner prices (market 271)
    MATCH_WINNER = "271"

    for slug, bookie_data in odds_data["bookmakerOdds"].items():
        markets = bookie_data.get("markets", {})
        if MATCH_WINNER in markets:
            outcomes = markets[MATCH_WINNER]["outcomes"]
            home = outcomes.get("271", {}).get("players", {}).get("0", {})
            away = outcomes.get("272", {}).get("players", {}).get("0", {})
            print(f"  {slug:20s}  {home.get('price', '-'):>6}  {away.get('price', '-'):>6}")

On a live IPL match, you’ll see 40–50 bookmakers pricing the match winner — including Pinnacle (the market benchmark), the Betfair Exchange, FanDuel, DraftKings, Dafabet, Betway, and dozens of regional books.

Step 4: Compare Sharp vs. Soft Lines

This is where it gets useful. Pinnacle sets the sharpest cricket line in the world. Comparing it against soft bookmakers is the foundation of every value betting and arbitrage strategy.

# Build a comparison table: sharp vs soft
SHARP_BOOKS = ["pinnacle"]
SOFT_BOOKS = ["fanduel", "draftkings", "betway", "dafabet", "paddypower"]

def get_match_winner_prices(odds_data, market_id="271"):
    """Extract match-winner prices for all bookmakers."""
    prices = {}
    for slug, bdata in odds_data.get("bookmakerOdds", {}).items():
        markets = bdata.get("markets", {})
        if market_id in markets:
            outcomes = markets[market_id]["outcomes"]
            home_price = outcomes.get("271", {}).get("players", {}).get("0", {}).get("price")
            away_price = outcomes.get("272", {}).get("players", {}).get("0", {}).get("price")
            if home_price and away_price:
                prices[slug] = {"home": home_price, "away": away_price}
    return prices

prices = get_match_winner_prices(odds_data)

print(f"\n{'Bookmaker':20s}  {'Home':>8}  {'Away':>8}  {'Edge vs Pinnacle':>16}")
print("-" * 60)

pinnacle = prices.get("pinnacle")
if pinnacle:
    # Pinnacle implied probabilities (no-vig midpoint)
    pin_home_prob = 1 / pinnacle["home"]
    pin_away_prob = 1 / pinnacle["away"]
    overround = pin_home_prob + pin_away_prob
    fair_home = pin_home_prob / overround
    fair_away = pin_away_prob / overround

    for slug in SHARP_BOOKS + SOFT_BOOKS:
        if slug in prices:
            p = prices[slug]
            # Edge = (price * fair_prob) - 1
            home_edge = (p["home"] * fair_home - 1) * 100
            away_edge = (p["away"] * fair_away - 1) * 100
            best_edge = max(home_edge, away_edge)
            print(f"  {slug:20s}  {p['home']:>8.3f}  {p['away']:>8.3f}  {best_edge:>+14.1f}%")

When a soft bookmaker is slow to move and Pinnacle has already shifted, the edge column lights up. That’s the signal arb scanners and value bettors are looking for.

Step 5: Cricket-Specific Markets (Overs, Runs, Powerplay)

Cricket isn’t just match-winner. OddsPapi covers 175+ markets per IPL fixture, including:

Market Example ID Description
Winner (incl. super over) 271 Match winner — the main line
1X2 273 Home / Draw / Away (Test matches)
Tied Match 274 Will the match end in a tie?
To Win the Coin Toss 278 Coin toss winner
Over/Under Runs 1st Innings 27188+ Total runs lines (multiple thresholds)
3 Overs 1st Innings 271832+ Powerplay segment runs
6 Overs 1st Innings 273620+ Full powerplay lines
10 Overs 1st Innings 278092+ Mid-innings lines

Don’t hardcode these — the catalog is too large and changes across formats. Query the market catalog and build a lookup:

# Build a market name lookup for cricket
time.sleep(0.2)
response = requests.get(f"{BASE_URL}/markets", params={
    "apiKey": API_KEY,
    "sportId": 27
})
catalog = response.json()

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", [])
}

print(f"Cricket market catalog: {len(catalog)} entries")
print(f"Unique market types: {len(set(market_names.values()))}")

# Show what markets a fixture actually has
for slug in ["pinnacle"]:
    if slug in odds_data.get("bookmakerOdds", {}):
        for mid in odds_data["bookmakerOdds"][slug]["markets"]:
            name = market_names.get(int(mid), mid)
            print(f"  {mid}: {name}")

Step 6: Betfair Exchange — Full Depth of Book

If you’ve ever used the Betfair API, you know the pain: account verification, API keys, rate limits, session tokens. OddsPapi normalizes Betfair Exchange data into the same format as every other bookmaker — with the addition of back/lay depth.

# Betfair Exchange data comes with full order book depth
betfair = odds_data.get("bookmakerOdds", {}).get("betfair-ex", {})

if betfair:
    for mid, mdata in betfair.get("markets", {}).items():
        name = market_names.get(int(mid), mid)
        print(f"\nMarket: {name} (ID: {mid})")

        for oid, odata in mdata.get("outcomes", {}).items():
            p0 = odata.get("players", {}).get("0", {})
            price = p0.get("price")
            meta = p0.get("exchangeMeta", {})
            oname = outcome_names.get((int(mid), int(oid)), oid)

            print(f"  {oname}:")
            print(f"    Back: {price} (available: {p0.get('limit', 0):.2f})")

            if meta and "availableToBack" in meta:
                for level in meta["availableToBack"][:3]:
                    print(f"      {level['price']} x £{level['size']:.2f}")
                for level in meta.get("availableToLay", [])[:3]:
                    print(f"      Lay {level['price']} x £{level['size']:.2f}")

This gives you the full Betfair order book — back prices, lay prices, and available liquidity at each level — without touching the Betfair API directly.

Step 7: Historical Odds for Backtesting

Building a cricket prediction model? You need historical closing lines to measure accuracy. Most providers charge for this. OddsPapi includes it on the free tier.

# Pull historical odds for a completed fixture
# Note: historical endpoint uses "bookmakers" key (not "bookmakerOdds")
# Max 3 bookmakers per call

completed = [f for f in fixtures if not f.get("hasOdds") or f["statusId"] != 1]
# Or use a known completed fixture ID

HISTORICAL_FIXTURE = completed[0]["fixtureId"] if completed else fid

time.sleep(0.2)
hist_response = requests.get(f"{BASE_URL}/historical-odds", params={
    "apiKey": API_KEY,
    "fixtureId": HISTORICAL_FIXTURE,
    "bookmakers": "pinnacle,betfair-ex,dafabet"
})
hist_data = hist_response.json()

# The structure is different from live odds:
# hist_data["bookmakers"][slug]["markets"][mid]["outcomes"][oid]["players"]["0"]
# is a LIST of snapshots, not a single dict

for slug, bdata in hist_data.get("bookmakers", {}).items():
    for mid, mdata in bdata.get("markets", {}).items():
        for oid, odata in mdata.get("outcomes", {}).items():
            snapshots = odata.get("players", {}).get("0", [])
            if snapshots:
                print(f"\n{slug} | Market {mid} | Outcome {oid}")
                print(f"  {len(snapshots)} price snapshots")
                print(f"  Opening: {snapshots[0]['price']} at {snapshots[0]['createdAt']}")
                print(f"  Closing: {snapshots[-1]['price']} at {snapshots[-1]['createdAt']}")

Key difference: The live /odds endpoint returns bookmakerOdds with a single price per outcome. The /historical-odds endpoint returns bookmakers with a list of timestamped price snapshots. Don’t mix up the response shapes.

Step 8: Full Pipeline — IPL Odds Scanner

Here’s a complete script that scans all upcoming IPL fixtures and finds where soft bookmakers are offering better prices than Pinnacle’s fair line:

import requests
import time
from datetime import datetime, timedelta

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

def get_fixtures(sport_id, days=10):
    """Fetch fixtures with odds for a date range."""
    today = datetime.utcnow().strftime("%Y-%m-%d")
    end = (datetime.utcnow() + timedelta(days=days)).strftime("%Y-%m-%d")
    r = requests.get(f"{BASE_URL}/fixtures", params={
        "apiKey": API_KEY, "sportId": sport_id, "from": today, "to": end
    })
    return [f for f in r.json() if f.get("hasOdds")]

def get_odds(fixture_id):
    """Fetch live odds for a fixture."""
    r = requests.get(f"{BASE_URL}/odds", params={
        "apiKey": API_KEY, "fixtureId": fixture_id
    })
    return r.json()

def find_value(odds_data, market_id="271"):
    """Compare all bookmakers against Pinnacle's fair line."""
    pin = odds_data.get("bookmakerOdds", {}).get("pinnacle", {})
    pin_markets = pin.get("markets", {})
    if market_id not in pin_markets:
        return []

    # Calculate Pinnacle fair probabilities
    outcomes = pin_markets[market_id]["outcomes"]
    pin_prices = {}
    for oid, odata in outcomes.items():
        p = odata.get("players", {}).get("0", {}).get("price")
        if p:
            pin_prices[oid] = p

    if len(pin_prices) < 2:
        return []

    overround = sum(1/p for p in pin_prices.values())
    fair_probs = {oid: (1/p) / overround for oid, p in pin_prices.items()}

    # Scan all bookmakers
    edges = []
    for slug, bdata in odds_data.get("bookmakerOdds", {}).items():
        if slug == "pinnacle":
            continue
        markets = bdata.get("markets", {})
        if market_id not in markets:
            continue
        for oid, odata in markets[market_id].get("outcomes", {}).items():
            price = odata.get("players", {}).get("0", {}).get("price")
            if price and oid in fair_probs:
                edge = (price * fair_probs[oid] - 1) * 100
                if edge > 0:
                    edges.append({
                        "bookmaker": slug,
                        "outcome": oid,
                        "price": price,
                        "pinnacle_price": pin_prices[oid],
                        "edge": edge
                    })

    return sorted(edges, key=lambda x: -x["edge"])

# Run the scanner
fixtures = get_fixtures(sport_id=27)
ipl = [f for f in fixtures if "Indian Premier League" in f.get("tournamentName", "")]

print(f"Scanning {len(ipl)} IPL fixtures...\n")

for fixture in ipl:
    name = f"{fixture['participant1Name']} vs {fixture['participant2Name']}"
    time.sleep(0.2)
    odds = get_odds(fixture["fixtureId"])
    values = find_value(odds)

    if values:
        print(f"\n{name}")
        for v in values[:5]:
            side = "Home" if v["outcome"] == "271" else "Away"
            print(f"  {v['bookmaker']:20s}  {side}  {v['price']:.3f}  "
                  f"(Pin: {v['pinnacle_price']:.3f})  Edge: {v['edge']:+.1f}%")

Coverage: What Tournaments Are Included?

Tournament Format Typical Bookmakers
Indian Premier League (IPL) T20 49+
Pakistan Super League (PSL) T20 30+
Big Bash League T20 20+
Caribbean Premier League T20 20+
International T20s T20 20+
ODI Series ODI 20+
West Indies Championship First-class 10+
County Cricket / Domestic Various 10+

Coverage scales with liquidity. The IPL gets the deepest bookmaker coverage because it has the most betting volume. Niche domestic leagues still get odds, just from fewer books.

Why Not Just Use The Odds API or CricketAPI?

The Odds API covers cricket, but with ~40 bookmakers total across all sports. You won’t find Dafabet, TabTouch, Pamestoixima, or the other regional books that price cricket aggressively. And you won’t get Betfair Exchange depth.

CricketAPI (Roanuz) and EntitySport are excellent for scores, ball-by-ball data, and player stats. They don’t provide bookmaker odds. You’d need both CricketAPI and OddsPapi to build a complete cricket data pipeline — stats from one, prices from the other.

Enterprise feeds (Sportradar, LSports, OddsMatrix) cover cricket with depth, but the free tier either doesn’t exist or is too restrictive for development work. OddsPapi gives you free historical data, WebSocket streaming, and 350+ bookmakers across all sports — cricket is one of 69 supported sports.

Quick Reference: Cricket API Endpoints

Endpoint Purpose Key Parameters
GET /fixtures List cricket matches sportId=27, from, to
GET /odds Live odds for a fixture fixtureId
GET /historical-odds Price history for backtesting fixtureId, bookmakers (max 3)
GET /markets Market catalog & outcome names sportId=27
GET /bookmakers Full bookmaker list

All endpoints use ?apiKey=YOUR_KEY as a query parameter. Rate limit on free tier: ~1 request per second per endpoint. Add time.sleep(0.2) between iterations.

Get Started

Stop scraping Cricbuzz for odds that update every 30 seconds. Get your free API key, point your script at sportId=27, and start pulling real-time cricket lines from Pinnacle, Betfair, and 47 other bookmakers in under five minutes.

The IPL is live right now. Your model should be too.