Matching bookmaker and exchange odds
Matching bookmaker and exchange odds means proving that a bookmaker's back price and an exchange's lay price describe the same fixture, market, selection, line and terms, with enough money behind the lay to take a stake.
Miss one and the pair looks usable on screen, then fails when your user acts on it.
That join sits under every oddsmatcher and every tool that sets a bookmaker's price against an exchange's. Most of it can be read straight off a matched reply. Two parts cannot, because they live in each venue's own rulebook: what happens when a match moves, and how many places an each-way bet pays after a non-runner. For the product around the join, see building an oddsmatcher.
Six checks on every pair
A pair is two offers from two venues, and each check below has to hold for both at once.
| Check | How it breaks | Where the reply carries it |
|---|---|---|
| Fixture | A cup final listed with the teams the other way round, or a race joined to the wrong start time | One event_id per fixture on every board |
| Market | A 90-minute result paired with a market that counts extra time | One set of market keys, such as h2h and totals |
| Selection | The draw named differently at each venue, or a runner's name spelled two ways | One name per outcome, with every venue's offer under it |
| Line | Over 2.5 goals paired with Over 3.5 | The line in the outcome's point |
| Each-way terms | The bookmaker pays a different number of places from the exchange's place market | places and place_fraction on each back offer, places on each place lay |
| Money behind the lay | A close price with too little behind it to take the stake | available on every lay offer |
The first four are identity. Get one wrong and you are comparing two different bets. The last two are terms: the bet is the same, but the pair costs more, or fills less, than it appears to.
Fixtures and names
Team names are the familiar problem. Abbreviations, suffixes and punctuation differ from venue to venue, and normalising odds across bookmakers covers the event, market and selection layers in general. Between a bookmaker and an exchange, two more details catch builders who join on names and kick-off alone.
- Home and away order is not fixed everywhere. Smarkets lists the home team first, but at neutral venues and in international competitions its rules let the teams appear in either order (rule 12.3). A join that maps outcomes by position, home first, pairs the wrong side of a cup final.
- A race is a course and a start time. It has no teams, and its selections are the runners. On the racing boards an event carries
venuefor the course andcommence_timefor the off, and each runner is an outcome.
Are bookmaker and exchange markets settled the same way?
Mostly, for the common football markets, but postponement rules differ from venue to venue. Sky Bet settles football on 90 minutes including time added for stoppages unless a market states otherwise, and Smarkets does the same for its football markets (rule 12.1). On both, extra time counts only where the market says so.
Postponement is where two rulebooks split one pair. Sky Bet lets bets stand on a match confirmed to take place within the current or following 3 days of its original kick-off date, provided the new start time is confirmed within 3 hours of the original kick-off. Smarkets voids every bet on an event whose start moves more than 36 hours from the original time (general rule 6.3). Read side by side, as both were published on 29 September 2026, a match moved back two days and confirmed in time leaves the bookmaker bet standing and the exchange bet void.
A data feed cannot settle that for your users. The prices pair, and the rules stay each venue's. A product can keep the rules one click away: every back and lay offer carries link, the venue's page for the event (it can be null), and a line in the interface can say whose rules apply to each side.
Lines are part of the selection
On a line market the selection is the side and the line together. Over at 2.5 goals and Over at 3.5 are different bets, and one venue often prices several lines on the same fixture, so a join on the outcome's name alone pairs across lines. On the matched boards the line sits in the outcome's point, and every offer under that outcome is for that line.
{
"name": "Over",
"point": 2.5,
"back": [
{ "bookmaker": "paddy_power", "price": 1.91, "link": null },
{ "bookmaker": "sky_bet", "price": 1.88, "link": null }
],
"lay": [
{ "exchange": "smarkets", "price": 1.95, "available": 240.5, "link": null },
{ "exchange": "betfair_exchange", "price": 1.96, "available": 612.0, "link": null }
]
}back comes best price first and lay lowest price first, so back[0] and lay[0] are the closest pair on the board for that line. Whether your user can use it is the next two checks.
Do non-runners change each-way places on an exchange?
Not on Smarkets, whose rules fix a place market's places when the market is created and say a non-runner does not change them (rules 13.2.1 and 13.2.2). A bookmaker works the other way. Sky Bet's rules set its each-way terms by the number of horses that come under starter's orders, not by the declared runners when the price was taken.
Take a non-handicap race with 8 declared runners. Sky Bet pays 3 places at 1/5 of the odds, and the exchange's place market counts 3 places. One horse is withdrawn. Under Sky Bet's own table, 7 runners pay 2 places at 1/4, while the place market still counts 3. A pair that was level on places when it was read now has the bookmaker paying fewer places than the exchange, and a horse finishing third loses the user both the bookmaker's place part and the place lay.
The each-way board puts both sets of terms in one outcome, so the comparison is a field read rather than a trip through two rulebooks:
{
"event_id": "or_evt_2b8e61f0a93c",
"sport_key": "horse_racing",
"sport_title": "Horse Racing",
"commence_time": "2026-10-03T14:25:00Z",
"venue": "Ascot",
"markets": [{
"key": "each_way",
"outcomes": [{
"name": "Northern Quay",
"back": [
{ "bookmaker": "sky_bet", "price": 7.0, "places": 3, "place_fraction": 0.2, "link": null }
],
"lay": {
"win": [{ "exchange": "smarkets", "price": 7.4, "available": 55.0, "link": null }],
"place": [{ "exchange": "smarkets", "price": 2.3, "available": 81.2, "places": 3, "link": null }]
}
}]
}]
}back[].places and place_fraction are the bookmaker's terms as it showed them when read, and lay.place[].places is the count the exchange's place market settles on. Compare them on every poll instead of caching them from the declarations, because a withdrawal can move the bookmaker's side while the exchange's stays put.
const back = outcome.back[0];
const placeLay = outcome.lay.place[0];
// More places at the bookmaker is the extra-place case. Fewer is a pair to flag.
const terms =
back.places > placeLay.places ? "extra place" :
back.places < placeLay.places ? "fewer places at the bookmaker" :
"level";A runner where the bookmaker pays more places belongs on the extra-place board, which keeps the each-way shape and lists only those runners. Horse racing and each-way odds data prices the place part, 1 + (win − 1) × place_fraction, against a place lay.
A close price with little behind it
A lay price holds only up to the money behind it, so the last check is size. Every lay offer carries available, the money on offer at that price, and one outcome's lay list can hold offers from several exchanges. The closest price and the offer that takes your user's stake are often different entries.
The boards also leave out a lay price with very little behind it, so no pair rests on an offer that could barely take a bet. That floor is one setting for the whole feed and cannot know a user's stake, so compare the lay stake your code works out with available on each offer. Why lay liquidity matters sets out three ways a product can handle a short lay.
What stays in your code
A matched reply arrives joined. Every matched board except dutching pairs bookmaker back offers with lay offers from Betfair, Smarkets, Matchbook and BETDAQ, and a bookmakers or exchanges filter drops any outcome left with no back offer or no lay offer, so a filtered reply holds only outcomes with both sides from the venues you named. What depends on your users stays with you:
- Commission. Each exchange charges its own and each user pays their own rate, so every sum on a pair takes it as an input. The formulas are in qualifying loss and ratings.
- The stake against the money. Work out the lay stake from the user's back stake, and compare it with
availablebefore the pair is shown as usable. - Terms that move. Re-read
placeson both sides on every poll, and flag a runner where the bookmaker pays fewer places than the exchange. - Whose rules apply. Settlement, postponement and non-runner rules belong to each venue. Say whose they are, and link them.
- How old a pair may be. A pair is as old as its older side.
meta.last_seengives the last read of each venue and sport, and the cut-off you show is your call.
// groupOf maps each sport_key to its key base (soccer, horse_racing), built once from GET /v2/sports
const base = groupOf[event.sport_key];
const readAt = (venue) => Date.parse(meta.last_seen[venue][base]);
const pairAgeSeconds = (Date.now() - Math.min(readAt(back.bookmaker), readAt(lay.exchange))) / 1000;Raw is the other route. GET /v2/odds/raw lists each venue under an event with its own market and outcome names, and an exchange's outcomes carry back_price, lay_price and available side by side. Raw keeps each venue's own market key, its market type lowercased, and only the matched boards share one set of market keys, so pairing from raw means running all six checks yourself. Choose it when your product needs every venue's own market list, not only the markets the boards pair. The walking raw guide shows how to page through it with a cursor, and the complete guide to matched betting odds data sets the matched boards side by side.
Compare the terms on a real board
Price an each-way call with quote=true first, which costs nothing, then pull the board for your users' bookmakers and exchanges and compare back[].places with lay.place[].places on every runner. The matched feed describes each board, and the matched-board filters guide narrows the call to the venues your users hold.
Sources, checked 29 September 2026
- Smarkets Help Centre: 1-11. General Smarkets betting rules (opens in a new tab)
- Smarkets Help Centre: 12. Football rules (opens in a new tab)
- Smarkets Help Centre: 13. Horse Racing rules (opens in a new tab)
- Sky Bet: Football - General Rules (opens in a new tab)
- Sky Bet: Football - Abandoned or Postponed Match Rules (opens in a new tab)
- Sky Bet: Horse Racing Rules (each-way place terms) (opens in a new tab)
Related posts
- What is an oddsmatcher?An oddsmatcher ranks back prices against exchange lay prices. The data it needs, and the sums it runs.
- Why lay liquidity matters in a matched odds feedA lay price with no money behind it cannot be taken. How to check a pair against your stake.
- Horse racing and each-way odds dataRunners, courses and each-way place terms: how horse racing looks in an odds feed.
18+ · OddsRelay supplies odds data only and takes no bets. Please gamble responsibly.