Odds
Pre-match and in-play prices from five bookmakers, and how to compare them.
Soccer Data collects prices from several bookmakers so you can compare them side by side, follow how they move before kickoff, and track them in play. This guide covers the two odds endpoints, the bookmakers and markets, and the checks worth making first.
Every example sends your key in the X-Api-Key header. Keep that key on your server.
Which plan you need
| Endpoint | What it returns | Needs |
|---|---|---|
/matches/{match_id}/odds |
Pre-match prices, with opening and latest values. | Pro or Business plan |
/matches/{match_id}/odds/live |
In-play prices while the match is live. | Pro or Business plan |
On the Free plan, you get 403 with the code PLAN_UPGRADE_REQUIRED. You can upgrade on the plans page of your dashboard.
Bookmakers
| Bookmaker | Slug |
|---|---|
| Bet365 | bet365 |
| Paddy Power | paddy-power |
| BetMGM UK | betmgm-uk |
| Pinnacle | pinnacle |
| Betfair Exchange | betfair-exchange |
The pre-match endpoint takes a bookmaker filter with one or more slugs, comma-separated. Leave it out to get every bookmaker. An unknown slug returns 400.
In-play prices come from Bet365, Paddy Power, BetMGM UK and Betfair Exchange.
Odds format
All prices are decimal odds. If your users expect fractional or American odds, convert them in your app.
Pre-match and in-play prices are strings, such as "1.380".
Check 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. |
If a match has no prices of the kind you ask for, /odds and /odds/live return 404. Treat that as "no odds for this match", not as a failure.
Pre-match odds
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" }
},
"btts": {
"yes": { "opening": "2.000", "last_seen": "2.050" },
"no": { "opening": "1.750", "last_seen": "1.700" }
},
"total_goals": {
"2.5": {
"over": { "opening": "1.725", "last_seen": "1.800" },
"under": { "opening": "2.075", "last_seen": "2.000" }
}
}
}
},
{
"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 has two prices:
opening: the first price we saw, ornullif we don't have it.last_seen: the latest price. Once the match has kicked off, this is the closing price.
The endpoint works for upcoming and finished matches alike, so you can also look back at closing lines.
Markets
The example shows three common markets:
match_odds: home, draw or away.btts: both teams to score, yes or no.total_goals: a key per line, such as"2.5", each withoverandunder.
Depending on the bookmaker and the match, you'll also find full-time and half-time results, corners, cards, Asian and European handicaps, team totals, shots, correct score, to qualify and penalty specials. On bigger matches Bet365 adds markets for shots, throw-ins, goal kicks, tackles and offsides, some with per-half versions such as first_half_match_shots.
A market only appears if that bookmaker prices it. Check that a key exists before reading it.
In-play odds
Ask only while live_odds_available 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, with a price and a size.
Books suspend and reopen during a match, so bookmakers and markets come and go between calls. After full time the endpoint returns 404.
In-play prices follow your plan's live refresh, normally 15 seconds, so polling faster than that returns the same prices. Live matches has a polling loop you can reuse.
How far back odds go
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. For those,
openingisnullandlast_seenholds the closing price.
/coverage/leagues/{competition_id} shows which seasons have prices, with opening_odds, closing_odds and odds counted separately for each season. To find competitions that have odds at all, use /coverage/leagues with data_type=odds, opening_odds or closing_odds. For example, Premier League 26/27 has both opening and closing prices, while 25/26 has closing prices only.
Common mistakes
- Assuming a market or bookmaker is always there. Check that it exists in the response first.
- Treating
404as an error. On the odds endpoints it means the match has no prices of that kind. - Expecting prices for postponed or cancelled matches.
odds_availableis alwaysfalsefor them. - Mistyping a bookmaker slug. An unknown slug returns
400.
Related
# Odds
Pre-match and in-play prices from five bookmakers, and how to compare them.
Soccer Data collects prices from several bookmakers so you can compare them side by side, follow how they move before kickoff, and track them in play. This guide covers the two odds endpoints, the bookmakers and markets, and the checks worth making first.
Every example sends your key in the `X-Api-Key` header. Keep that key on your server.
## Which plan you need
| Endpoint | What it returns | Needs |
|---|---|---|
| [`/matches/{match_id}/odds`](/docs/reference/odds/prematch) | Pre-match prices, with opening and latest values. | Pro or Business plan |
| [`/matches/{match_id}/odds/live`](/docs/reference/odds/live) | In-play prices while the match is live. | Pro or Business plan |
On the Free plan, you get `403` with the code `PLAN_UPGRADE_REQUIRED`. You can upgrade on the [plans page](/dashboard/subscription) of your dashboard.
## Bookmakers
| Bookmaker | Slug |
|---|---|
| Bet365 | `bet365` |
| Paddy Power | `paddy-power` |
| BetMGM UK | `betmgm-uk` |
| Pinnacle | `pinnacle` |
| Betfair Exchange | `betfair-exchange` |
The pre-match endpoint takes a `bookmaker` filter with one or more slugs, comma-separated. Leave it out to get every bookmaker. An unknown slug returns `400`.
In-play prices come from Bet365, Paddy Power, BetMGM UK and Betfair Exchange.
## Odds format
All prices are decimal odds. If your users expect fractional or American odds, convert them in your app.
Pre-match and in-play prices are strings, such as `"1.380"`.
## Check 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. |
If a match has no prices of the kind you ask for, `/odds` and `/odds/live` return `404`. Treat that as "no odds for this match", not as a failure.
## Pre-match odds
```bash
curl "https://api.soccer-data.co/v1/matches/match_yoeml0e6ae37/odds?bookmaker=bet365,pinnacle" \
-H "X-Api-Key: YOUR_API_KEY"
```
```json
{
"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" }
},
"btts": {
"yes": { "opening": "2.000", "last_seen": "2.050" },
"no": { "opening": "1.750", "last_seen": "1.700" }
},
"total_goals": {
"2.5": {
"over": { "opening": "1.725", "last_seen": "1.800" },
"under": { "opening": "2.075", "last_seen": "2.000" }
}
}
}
},
{
"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 has two prices:
- `opening`: the first price we saw, or `null` if we don't have it.
- `last_seen`: the latest price. Once the match has kicked off, this is the closing price.
The endpoint works for upcoming and finished matches alike, so you can also look back at closing lines.
### Markets
The example shows three common markets:
- `match_odds`: home, draw or away.
- `btts`: both teams to score, yes or no.
- `total_goals`: a key per line, such as `"2.5"`, each with `over` and `under`.
Depending on the bookmaker and the match, you'll also find full-time and half-time results, corners, cards, Asian and European handicaps, team totals, shots, correct score, to qualify and penalty specials. On bigger matches Bet365 adds markets for shots, throw-ins, goal kicks, tackles and offsides, some with per-half versions such as `first_half_match_shots`.
A market only appears if that bookmaker prices it. Check that a key exists before reading it.
## In-play odds
Ask only while `live_odds_available` is `true`:
```bash
curl "https://api.soccer-data.co/v1/matches/match_0i2m68v7oaxa/odds/live" \
-H "X-Api-Key: YOUR_API_KEY"
```
```json
{
"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, with a `price` and a `size`.
Books suspend and reopen during a match, so bookmakers and markets come and go between calls. After full time the endpoint returns `404`.
In-play prices follow your plan's live refresh, normally 15 seconds, so polling faster than that returns the same prices. [Live matches](/docs/guides/live-matches) has a polling loop you can reuse.
## How far back odds go
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. For those, `opening` is `null` and `last_seen` holds the closing price.
[`/coverage/leagues/{competition_id}`](/docs/reference/coverage/league) shows which seasons have prices, with `opening_odds`, `closing_odds` and `odds` counted separately for each season. To find competitions that have odds at all, use [`/coverage/leagues`](/docs/reference/coverage/leagues) with `data_type=odds`, `opening_odds` or `closing_odds`. For example, Premier League 26/27 has both opening and closing prices, while 25/26 has closing prices only.
## Common mistakes
- **Assuming a market or bookmaker is always there.** Check that it exists in the response first.
- **Treating `404` as an error.** On the odds endpoints it means the match has no prices of that kind.
- **Expecting prices for postponed or cancelled matches.** `odds_available` is always `false` for them.
- **Mistyping a bookmaker slug.** An unknown slug returns `400`.
## Related
- [Live matches](/docs/guides/live-matches)
- [Coverage](/docs/concepts/coverage)
- [Rate limits](/docs/concepts/rate-limits)