Skip to content

BlogMatched betting data

Horse racing odds data and each-way, for builders

OddsRelay · · Updated · 5 min read

In a horse racing feed, a race is an event with a course instead of two teams, every runner is an outcome, and each bookmaker's each-way price carries its own place terms. Three of the matched boards are built around racing: each-way, extra-place and best odds guaranteed (BOG).

What makes horse racing odds data harder than football?

The field size and the place terms. A football match resolves to a home win, a draw and an away win, and that shape never changes. A race holds a variable number of runners, non-runners leave the field before the off, and each runner carries a win price plus an each-way settlement under terms the bookmaker sets for that race.

Those terms are two numbers: how many places are paid, and what fraction of the win odds the place part pays. One book can pay one-fifth the odds on three places while another pays one-quarter on the same race. Books quoting the same win price can settle the place part differently, so a model that hardcodes the terms is wrong on some races and never says so.

On top of that sit the racing concessions matched betting runs on. An extra-place offer pays more places than the race's usual terms. Best odds guaranteed (BOG) pays the starting price when it beats the price taken. Both change what a bet is worth after the fact, and both are set bookmaker by bookmaker.

A race keys on course and off time

On the racing boards an event has sport_key set to horse_racing and a venue field holding the course. There is no home_team or away_team. commence_time is the scheduled off, and each runner is one entry in outcomes, by name. A card is simply the events that share a venue on the same day.

Treat event_id as the feed's own handle for a race: stable within the feed's mapping, but opaque. Store it for the life of the race, and key anything long-lived on the course and off time too. To pull racing alone, the matched boards take sports=horse_racing and a kick-off window, both covered in the matched-board filters guide.

Place terms travel on each back offer

On the each-way and extra-place boards every back offer carries places, the number of places that bookmaker pays, and place_fraction, its place terms as a decimal (0.2 is one-fifth, 0.25 one-quarter). The lay side splits into win and place arrays, because the exchange runs them as separate markets, and every place lay carries its own places: the number the exchange's place market settles on.

GET /v2/odds/each-way · example, trimmed to one runner

{
  "meta": {
    "feed_type": "each-way", "region": "uk",
    "odds_format": "decimal",
    "processed_at": "2026-09-19T12:41:07.312Z",
    "last_seen": {
      "paddy_power": { "horse_racing": "2026-09-19T12:41:05Z" },
      "william_hill": { "horse_racing": "2026-09-19T12:41:04Z" }
    },
    "count": 1,
    "unreliable_links": [],
    "version": "v2",
    "next_cursor": null
  },
  "data": [{
    "event_id": "or_evt_4c19a07be2d3",
    "sport_key": "horse_racing",
    "sport_title": "Horse Racing",
    "commence_time": "2026-09-19T13:50:00Z",
    "venue": "Newmarket",
    "markets": [{
      "key": "each_way",
      "outcomes": [{
        "name": "Harbour Lights",
        "back": [
          { "bookmaker": "paddy_power", "price": 9.5, "places": 3, "place_fraction": 0.2, "link": null },
          { "bookmaker": "william_hill", "price": 9.0, "places": 3, "place_fraction": 0.25, "link": null }
        ],
        "lay": {
          "win": [{ "exchange": "betfair_exchange", "price": 10.0, "available": 48.2, "link": null }],
          "place": [{ "exchange": "betfair_exchange", "price": 2.9, "available": 63.5, "places": 3, "link": null }]
        }
      }]
    }]
  }]
}

The best win price is not the best each-way bet. The place part's price is 1 + (win − 1) × place_fraction, so the Paddy Power offer's place part is 2.7 while William Hill's, at one-quarter, is 3.0. Against a place lay at 2.9, one of those place parts beats the lay and the other does not. The terms have to be read per offer, every time.

Each pair is matched against exchange lay and liquidity-gated, with the lay side drawn from Betfair, Smarkets, Matchbook and BETDAQ. The rating and qualifying loss are simple arithmetic on the pair, done at your own commission rate, and the oddsmatcher widget shows them. For the each-way sums themselves, the each-way calculator works a bet through.

Customer-side arithmetic · example

// Your code, per back offer on an each-way or extra-place outcome
const placePart = 1 + (offer.price - 1) * offer.place_fraction;
const placeLay = outcome.lay.place.reduce((a, b) => (b.price < a.price ? b : a)); // lowest place lay
const extraPlaces = offer.places - placeLay.places; // above 0: the book pays places the exchange does not

Each-way, Extra place and Best odds guaranteed side by side

BoardWhat it pairsWhat to compare
Each-wayEach bookmaker's win price and terms against the exchange win and place laysA place part priced close to, or above, the place lay
Extra placeThe same shape as each-wayA bookmaker paying more places than the exchange's place market
Best odds guaranteedA win market: bookmaker back prices against exchange laysThe back price against the lay. Any starting-price uplift comes later, from the bookmaker

The extra-place board is where the terms matter most. If a bookmaker pays four places and the exchange's place market settles on three, a runner finishing fourth wins the bookmaker's place part while the place lay also wins. Comparing back[].places with lay.place[].places shows that gap directly, and the extra-place entry works one through.

BOG has the standard board's shape, with no place terms and a flat lay array. The starting price itself is not in the feed: it is set at the off, after the pre-match prices the feed carries, so a tool plans on the back and lay prices and the bookmaker pays any uplift later under its own terms.

The racing boards are on every plan. Offer rules such as qualifying races and maximum stakes stay with each bookmaker, and the feed carries prices and terms, never eligibility. The each-way term in the glossary covers the bet itself for anyone new to it.

Price age is yours to check near the off

Racing markets often move most in the last minutes before a race, then close at the off. The feed is pre-match: it carries prices up to the start of a race, and there is no in-play or in-running product.

Freshness is something you read from the reply. meta.processed_at is the board's time, and meta.last_seen gives each venue's last read per sport, so now minus last_seen.<bookmaker>.horse_racing is an upper bound on that bookmaker's racing price age. Every product on sale is held to a maximum update time of 10–20 s. The freshness guide covers both fields.

Polling a racing board near the off is cheap when nothing has changed. Every data reply carries an ETag, and sending it back as If-None-Match turns an unchanged board into a 304 that costs no tokens, as the conditional requests guide shows.

Start with one race

The coverage page shows which bookmakers are live for racing. Pick one meeting from those books and walk its each-way outcomes through the arithmetic above. When you want a key to run it against, request access.