SOCCER DATA

Football odds API: pre-match and in-play odds by bookmaker

Soccer Data serves pre-match odds with opening and closing prices, and in-play odds, per bookmaker for each match, on the Pro and Business plans.

· 9 min read

Soccer Data has two football odds endpoints: GET /matches/{match_id}/odds returns pre-match prices, with an opening and a latest (closing) price for every selection, and GET /matches/{match_id}/odds/live returns in-play prices while a match is being played. Prices come from Bet365, Paddy Power, BetMGM UK, Pinnacle and Betfair Exchange as decimal odds, one entry per bookmaker, and both endpoints are included in the Pro and Business plans.

The two odds endpoints

Endpoint What it returns Bookmakers When to call it
/matches/{match_id}/odds Pre-match prices, with opening and last_seen for each selection Bet365, Paddy Power, BetMGM UK, Pinnacle, Betfair Exchange Before kickoff, or after it to read closing prices
/matches/{match_id}/odds/live In-play prices, with one live price for each selection Bet365, Paddy Power, BetMGM UK, Betfair Exchange While the match's live_odds_available is true

Every price is in decimal odds, sent as a string such as "1.380". If your users expect fractional or American odds, convert them in your app. Send your API key in the X-Api-Key header, from your server.

Which plans include odds

Odds are part of the Pro and Business plans:

Plan Requests / day Per minute Live data refreshed (normally) Odds
Free 50 10 5 min —
Pro 1,000 60 15 s Yes
Business 10,000 300 15 s Yes

A key on a plan without odds gets 403 with the code PLAN_UPGRADE_REQUIRED:

{
  "error": {
    "code": "PLAN_UPGRADE_REQUIRED",
    "message": "This endpoint needs the Pro or Business plan. Upgrade from your dashboard to use it.",
    "status_code": 403
  }
}

That 403 doesn't count against your daily quota. Plans are monthly and listed on the pricing page.

Pre-match odds with opening and closing prices

This asks for Bet365 and Pinnacle prices on Arsenal v Leeds United in the Premier League:

curl "https://api.soccer-data.co/v1/matches/match_yoeml0e6ae37/odds?bookmaker=bet365,pinnacle" \
  -H "X-Api-Key: YOUR_API_KEY"
{
  "data": {
    "match_id": "match_yoeml0e6ae37",
    "bookmakers": [
      {
        "bookmaker": "Bet365",
        "markets": {
          "match_odds": {
            "home": { "opening": "1.380", "last_seen": "1.380" },
            "draw": { "opening": "4.500", "last_seen": "5.000" },
            "away": { "opening": "7.500", "last_seen": "7.500" }
          }
        }
      },
      {
        "bookmaker": "Pinnacle",
        "markets": {
          "match_odds": {
            "home": { "opening": "1.377", "last_seen": "1.413" },
            "draw": { "opening": "4.910", "last_seen": "4.870" },
            "away": { "opening": "7.620", "last_seen": "7.760" }
          }
        }
      }
    ]
  }
}

Every selection carries two prices:

The endpoint works for upcoming and finished matches alike, so you can follow a line as it moves before kickoff and look back at closing lines afterwards.

Markets

The common keys are match_odds (home, draw, away), btts (both teams to score, yes or no) and total_goals (one key per line, such as "2.5", each with over and under). Depending on the bookmaker and the match, you'll also find first_half_result, double_chance, draw_no_bet, asian_handicap, handicap_result, correct_score, match_corners, total_cards, team_total_goals, to_qualify for knockout ties, and penalty markets. On bigger fixtures, Bet365 adds markets for shots, throw-ins, goal kicks, tackles and offsides.

A market only appears when that bookmaker prices it, so check that a key exists before you read it.

The bookmaker filter

The pre-match endpoint takes one parameter, bookmaker: one or more slugs, comma-separated.

Bookmaker Slug Pre-match In-play
Bet365 bet365 Yes Yes
Paddy Power paddy-power Yes Yes
BetMGM UK betmgm-uk Yes Yes
Pinnacle pinnacle Yes No
Betfair Exchange betfair-exchange Yes Yes

In-play odds

Call the live endpoint only while the match's live_odds_available flag is true:

curl "https://api.soccer-data.co/v1/matches/match_0i2m68v7oaxa/odds/live" \
  -H "X-Api-Key: YOUR_API_KEY"
{
  "data": {
    "match_id": "match_0i2m68v7oaxa",
    "bookmakers": [
      {
        "bookmaker": "Bet365",
        "markets": {
          "match_odds": {
            "home": { "live": "3.400" },
            "draw": { "live": "3.100" },
            "away": { "live": "2.250" }
          }
        }
      },
      {
        "bookmaker": "Betfair Exchange",
        "markets": {
          "match_odds": {
            "home": {
              "live": "3.650",
              "available_to_back": [{ "price": 3.65, "size": 1209.44 }, { "price": 3.6, "size": 1795.66 }],
              "available_to_lay": [{ "price": 3.7, "size": 480.78 }, { "price": 3.75, "size": 2184.81 }]
            }
          }
        }
      }
    ]
  }
}

Each selection has a single live price. Betfair Exchange also gives the money waiting in the market: available_to_back and available_to_lay, up to three levels each, best price first, each with a price and a size. Betfair Exchange's pre-match prices carry the same two lists.

Things to plan for when you poll:

Check odds coverage before you call

Matches and competitions carry flags, so you don't have to ask blindly:

Flag On Means
odds_available Match Pre-match prices exist. Always false for postponed and cancelled matches.
live_odds_available Match In-play prices exist right now.
odds_available Competition Its matches have prices.
live_odds_available Competition At least one of its matches has in-play prices right now.

The flags come back on /matches, /matches/{match_id} and /competitions/{competition_id}. A match without prices of the kind you ask for returns 404 from the odds endpoints, and that 404 counts as a request, so checking the flag first saves quota.

To see which competitions and seasons have odds, use the coverage endpoints. /coverage/leagues takes data_type=opening_odds, closing_odds or odds (an older name for closing_odds):

curl "https://api.soccer-data.co/v1/coverage/leagues?data_type=opening_odds" \
  -H "X-Api-Key: YOUR_API_KEY"

/coverage/leagues/{competition_id} breaks one competition down season by season, with covered_events and coverage_pct for opening_odds and closing_odds:

curl "https://api.soccer-data.co/v1/coverage/leagues/competition_705uv6jm8myn" \
  -H "X-Api-Key: YOUR_API_KEY"

Each season in data.seasons looks like this (trimmed to the odds keys):

{
  "id": "season_98l95mxugqs9",
  "year": "25/26",
  "status": "complete",
  "data_types": {
    "opening_odds": { "available": false, "covered_events": 0, "coverage_pct": 0 },
    "closing_odds": { "available": true, "covered_events": 380, "coverage_pct": 100 }
  }
}

Odds history is thinner than match history. From the 2026/27 season, matches can have opening and closing prices from all five bookmakers. Before that, most matches only have Bet365 closing prices: opening is null and last_seen holds the closing price. In the Premier League, for example, closing odds go back to 2015/16 and opening odds start in 2026/27.

Example: compare prices across bookmakers

This Python script fetches the pre-match odds for one match and prints the best match_odds price for each outcome, with the bookmaker offering it and how far that price moved from its opening:

import os, requests

API_URL = "https://api.soccer-data.co/v1"

def best_prices(match_id, bookmakers="bet365,paddy-power,betmgm-uk,pinnacle,betfair-exchange"):
    res = requests.get(
        f"{API_URL}/matches/{match_id}/odds",
        params={"bookmaker": bookmakers},
        headers={"X-Api-Key": os.environ["SOCCER_DATA_KEY"]},
    )
    if res.status_code == 404:
        return {}  # no pre-match odds for this match
    res.raise_for_status()

    best = {}
    for book in res.json()["data"]["bookmakers"]:
        market = book["markets"].get("match_odds")
        if not market:
            continue  # this bookmaker doesn't price the result market
        for outcome in ("home", "draw", "away"):
            price = market.get(outcome)
            if not price:
                continue
            last = float(price["last_seen"])
            if outcome not in best or last > best[outcome]["price"]:
                opening = price["opening"]
                best[outcome] = {
                    "price": last,
                    "bookmaker": book["bookmaker"],
                    "moved": None if opening is None else round(last - float(opening), 3),
                }
    return best

for outcome, row in best_prices("match_yoeml0e6ae37", "bet365,pinnacle").items():
    print(f'{outcome}: {row["price"]:.3f} at {row["bookmaker"]} (moved {row["moved"]})')

For the Bet365 and Pinnacle prices in the example response above, it prints:

home: 1.413 at Pinnacle (moved 0.036)
draw: 5.000 at Bet365 (moved 0.5)
away: 7.760 at Pinnacle (moved 0.14)

To run this over a round of fixtures, list matches with /matches using competition_id, date_from and date_to, and skip any match whose odds_available is false.

Next steps

Frequently asked questions

Which football API includes bookmaker odds?

Soccer Data includes pre-match and in-play football odds from Bet365, Paddy Power, BetMGM UK, Pinnacle and Betfair Exchange. Pre-match prices come from GET /matches/{match_id}/odds and in-play prices from GET /matches/{match_id}/odds/live, on the Pro and Business plans.

How do I get opening and closing odds for a football match?

Call Soccer Data's GET /matches/{match_id}/odds. Every selection has an opening price (the first one seen, or null if none was recorded) and a last_seen price, which becomes the closing price once the match kicks off.

Can I get live in-play football odds from an API?

Yes. Soccer Data's GET /matches/{match_id}/odds/live returns a live price per selection from Bet365, Paddy Power, BetMGM UK and Betfair Exchange while a match is in play. Call it while the match's live_odds_available flag is true; prices are normally refreshed every 15 seconds.

How do I filter football odds by bookmaker?

Add the bookmaker parameter to Soccer Data's pre-match odds endpoint with one or more slugs, comma-separated: bet365, paddy-power, betmgm-uk, pinnacle or betfair-exchange. Leave it out to get every bookmaker; an unknown slug returns 400.

Does the Soccer Data odds API include Betfair Exchange back and lay prices?

Yes. In Soccer Data's pre-match and in-play odds, Betfair Exchange prices include available_to_back and available_to_lay: up to three levels each, best price first, each with a price and the size of money available.

How far back does football odds history go in Soccer Data?

From the 2026/27 season, matches can have opening and closing prices from all five bookmakers. Earlier seasons mostly have Bet365 closing prices only; check each competition season by season with GET /coverage/leagues/{competition_id}.

Try it with your own key

Fixtures, live scores, stats, xG, standings and odds for 1,100 competitions, in one REST API.

More guides