How to build an oddsmatcher (and which half to license)
OddsRelay · · Updated · 5 min read
Building an oddsmatcher splits into two jobs: pairing every bookmaker back price with an exchange lay price, and the product your users actually see. You can license the pairing and build the product, because the pairing is the part that never finishes.
What does an oddsmatcher actually do?
An oddsmatcher lines up a bookmaker's back price with the exchange lay price for the same selection, then sorts the results so a matched bettor can find a cheap qualifying bet or a strong free-bet conversion. A user filters to the books they hold accounts with, enters a stake and their commission, and reads a sorted list. Behind that list sit two layers: the paired data, and the product built on it.
What a feed has to deliver for the first layer is set out in what oddsmatcher-ready means.
Five systems behind the pairs
Collecting a price once is easy. Keeping prices accurate and complete across the UK & Ireland is a standing obligation that grows with every book you add. Each item below is a system you would own:
- Breadth: UK matched bettors expect every book they hold an account with, bet365 among them.
- The exchange side: a pair needs a lay price and the money on offer behind it, so you integrate with each exchange and decide how much liquidity is enough to show.
- The match itself: the same fixture and selection are named differently across books, so they must be reconciled before any back price can meet a lay price. That reconciliation is its own engine.
- Freshness: a stale price is worse than a missing one, because it looks usable. Every price needs an age your users can trust.
- Drift: coverage that is whole in January degrades by March as books change.
None of that is the visible product. Your users never see it unless it breaks. We don't publish how prices are collected, and this guide skips it on purpose: collection is not where an oddsmatcher wins or loses.
What a paired board looks like
On the standard board, each outcome carries back offers (a bookmaker's price) and lay offers (an exchange's price, from Betfair, Smarkets, Matchbook and BETDAQ, with available, the money on offer at that price). The pairs are liquidity-gated. Here is one event:
{
"meta": {
"feed_type": "standard", "region": "uk",
"odds_format": "decimal",
"processed_at": "2026-09-17T02:20:57.271Z",
"last_seen": { "william_hill": { "soccer": "2026-09-17T02:20:55Z" } },
"count": 26,
"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 }]
}]
}]
}]
}The wire carries source facts only. meta.processed_at is when the board was built, and meta.last_seen gives the last read per venue and sport, so now minus that time is an upper bound on a price's age. The freshness guide covers both.
Rating and qualifying loss are your arithmetic
There is no rating field, because the qualifying loss depends on a stake and a commission rate that only your user knows. Both are simple arithmetic on the pair. Two users on different commission rates should see different numbers for the same bet. So you build the board yourself: cross every back offer with every lay offer on each outcome, rate each pair, and sort.
// Your code, not the feed's. board is the response above.
const rows = [];
for (const event of board.data)
for (const market of event.markets)
for (const outcome of market.outcomes)
for (const b of outcome.back)
for (const l of outcome.lay)
rows.push({
event_id: event.event_id, name: outcome.name,
bookmaker: b.bookmaker, exchange: l.exchange,
back: b.price, lay: l.price, available: l.available,
rating: (b.price / l.price) * 100,
});
rows.sort((x, y) => y.rating - x.rating);
// Qualifying loss for one row. commission is your user's exchange rate, e.g. 0.02.
const { back, lay } = rows[0];
const layStake = (back * stake) / (lay - commission);
const qualifyingLoss = stake - layStake * (1 - commission);On the example above, a 2.9 back price against a 3.0 lay price comes out at a rating of 96.7. At a stake of 10 and 2% commission, the lay stake is 9.73 and the qualifying loss is 0.46. The ratings and qualifying loss guide walks through the free-bet versions too.
If you would rather not build the display at all, the oddsmatcher widget is a sortable, filterable board over the same matched data, with a per-row calculator that works out profit at your visitor's stake. It previews sample data on the site and goes live on yours under an origin-locked key.
You differentiate in the product layer
Two oddsmatchers reading identical pairs can feel completely different. This is where your engineering time earns a return:
- Filtering and sorting: by book, by sport, by your own rating, by minimum
available, by the accounts a user actually holds. Filtering is the difference between a list and a tool. - Alerts: your service polls the board and notifies a user when a bet crosses their threshold, so nobody sits refreshing a screen.
- Account and profit tracking: remember what a user has done, show progress, surface the next sensible offer.
- Onboarding: a clear UI that explains the lay step and the qualifying loss turns a curious visitor into a subscriber.
Some of the filtering can happen before the data reaches you. Matched boards accept sports, bookmakers, exchanges, markets and a kick-off window. On the standard board, each sport, book, exchange or market you name narrows the price of the call. The kick-off window is never priced. The matched-board filters guide has the details, and filters and tokens explains the pricing.
Start with the product, not the matcher
With a licensed feed, day one is the product layer. Your first authenticated call returns a paired board. You render it, add your arithmetic and build your filters. After that, poll with the ETag you were given as If-None-Match: an unchanged board comes back as a 304 and costs no tokens, and ?quote=true prices a call before you spend anything.
Building is sometimes right. If odds collection is your core product, owning it can make sense. For a team whose product is the oddsmatcher itself, the matcher is undifferentiated infrastructure. We work through that decision in buy versus build, and the wider context sits in the matched-betting data guide.
Point your oddsmatcher at the feed
Check which books and sports are live right now on the coverage page. When you are ready to build against the real boards, request access and start from the quickstart.