Skip to content

BlogMatched betting data

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.

GET /v2/odds/standard · example, not live data

{
  "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:

Your arithmetic on the pair · example

// 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 places and place_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 matcherLicense matched data
Exchange coverageYou integrate each exchangefour exchanges, already paired
Selection matchingYou build and maintain the mappingsDone before the data reaches you
Liquidity checkYou read and threshold each exchange's depthLay offers already liquidity-gated
Ongoing maintenanceYours, as books and markets shiftThe 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.