The complete guide to matched-betting odds data
OddsRelay · · Updated · 5 min read
Matched-betting data is a pair: a bookmaker's back price and an exchange's lay price for the same selection, with the money available to lay beside it. Everything else a matched-betting product shows, from an oddsmatcher's rating to a qualifying loss, is arithmetic on that pair.
One price is a quote, two are an opportunity
A bookmaker price on its own tells a matched bettor nothing. The decision depends on the gap between the back price at the bookmaker and the lay price on an exchange for the same event, market and selection. That relationship is the difference between an odds list and an oddsmatcher dataset.
A lay price also needs money behind it. An exchange quote with a few pounds available cannot cover a real lay stake, so every lay offer carries available, the amount that can be matched at that price. The liquidity post covers why thin markets mislead.
What goes into a matched-betting data feed?
Three parts:
- The books. The back side of every pair. OddsRelay covers 140+ bookmakers, 60+ of them live in the UK & Ireland. What is live right now, per bookmaker and sport, is on the coverage page.
- The exchanges. The lay side comes from Betfair, Smarkets, Matchbook and BETDAQ. More than one source matters because the best lay price and the deepest liquidity are often on different exchanges. See lay and exchange coverage.
- The matching step. Aligning a bookmaker's selection with the right exchange selection for the same event and market.
The rating and qualifying loss are yours to compute
OddsRelay's matched boards carry source facts only: prices, venues and liquidity. The rating and qualifying loss are simple arithmetic on the pair, and the oddsmatcher widget shows them.
The qualifying loss depends on each user's exchange commission rate and stake, which a supplier cannot know. A figure fixed by the supplier would be wrong for most of your users. The ratings and qualifying loss post walks through both, and the qualifying loss definition is in the glossary.
One matched row, prices only
Here is one selection from the standard board, in the envelope format. The values are made up for illustration.
{
"meta": {
"feed_type": "standard", "region": "uk",
"odds_format": "decimal",
"processed_at": "2026-09-20T12:18:11.402Z",
"last_seen": {
"william_hill": { "soccer": "2026-09-20T12:18:09Z" },
"betfair_exchange": { "soccer": "2026-09-20T12:18:10Z" },
"smarkets": { "soccer": "2026-09-20T12:18:07Z" }
},
"unreliable_links": [],
"count": 1,
"version": "v2",
"next_cursor": null
},
"data": [{
"event_id": "or_evt_917dd44bce05",
"sport_key": "soccer_epl",
"sport_title": "Premier League",
"commence_time": "2026-09-20T14:00:00Z",
"home_team": "Arsenal",
"away_team": "Chelsea",
"markets": [{
"key": "h2h",
"outcomes": [{
"name": "Arsenal",
"back": [{ "bookmaker": "william_hill", "price": 2.9, "link": null }],
"lay": [
{ "exchange": "betfair_exchange", "price": 3.0, "available": 175, "link": null },
{ "exchange": "smarkets", "price": 3.05, "available": 60, "link": null }
]
}]
}]
}]
}The back array holds bookmaker prices. The lay array holds exchange prices, each with its own available. Your code picks the lay offer it wants and runs the numbers at the user's commission:
// the smarkets offer above, at a 2% commission const back = 2.9, lay = 3.05, commission = 0.02, stake = 10 const rating = (back / lay) * 100 // 95.1 const layStake = (stake * back) / (lay - commission) // 9.57 const qualifyingLoss = layStake * (1 - commission) - stake // -0.62
Different offers need different boards
The matching logic changes with the offer, so each offer type is its own board rather than a flag. There are six matched boards, and every plan carries all of them plus raw bookmaker odds. Plans differ only in tokens, as the pricing page sets out.
- Standard: the everyday qualifying-bet and free-bet pairs, in the shape shown above.
- Each-way and Extra place: racing pairs where each back offer adds
placesandplace_fraction, and the lay side splits into win and place markets. Each-way and extra-place data covers both. - Best odds guaranteed (BOG): racing pairs for BOG offers, in the standard offer shape. See the BOG post.
- Dutching: back offers only, grouped by a
dutch_id, two or three legs, one per outcome, each at a different venue (an exchange's back price can be a leg). Dutching data explains the grouping. - 2Up: pairs for the early-payout football offer, in the standard shape. The 2Up definition has the offer itself.
BOG and 2Up share the standard grammar, so a front end built against the standard board reads their offers without new parsing. Each-way, extra-place and dutching need their own handling for places and dutch groups.
Matching is never finished
Pairing a bookmaker selection with the correct exchange selection sounds like a join. In practice names differ between every book and every exchange, markets appear and disappear, and a mapping that held last week drifts this week. Each exchange is its own integration with its own liquidity to read.
A matcher is a process you run and repair for as long as the product lives. For a matched-betting product, the matcher is plumbing your users never see, and owning it rarely pays. How to build an oddsmatcher lays out the work if you want to own it anyway, and buy vs build sets out the costs.
| Build the matcher | License matched data | |
|---|---|---|
| Exchange coverage | You integrate each exchange | four exchanges, already paired |
| Selection matching | You build and maintain the mappings | Done before the data reaches you |
| Liquidity check | You read and threshold each exchange's depth | Lay offers already liquidity-gated |
| Ongoing maintenance | Yours, as books and markets shift | The supplier's job |
The oddsmatcher-ready guide says what a feed must deliver before it earns that label.
Freshness is in the response
A stale pair looks as usable as a fresh one, so check its age from the data in hand.
Every response carries the board time, meta.processed_at (also the X-Processed-At header). A matched envelope also carries meta.last_seen, the last read per venue and sport. Now minus last_seen is an upper bound on a price's age, so your product can decide what to show (polling efficiently covers how to fetch it cheaply). Every product on sale meets a maximum update time of 10–20 s. The freshness guide covers both fields.
Start with the docs
The quickstart takes you from a key to your first matched board, and the reference lists every field above. To query the boards yourself, request access.