# Horse racing and each-way odds data

> How horse racing odds data is shaped: a course instead of teams, runners as outcomes, and each bookmaker's place terms on its each-way price.

Published 1 July 2026 · updated 5 November 2026

**In horse racing odds data, 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 [horse racing odds data](https://oddsrelay.io/sports/horse-racing): each-way, extra-place and best odds guaranteed (BOG).

## Why racing data is 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. [Best odds guaranteed (BOG)](https://oddsrelay.io/blog/best-odds-guaranteed-explained) pays the starting price when it beats the price taken. Extra places, covered below, and BOG are both 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 race card is the events that share a course on the same day. Backing every runner in one race so any winner returns the same amount is [dutching](https://oddsrelay.io/blog/dutching-data-explained), the same arithmetic over a longer list of outcomes.

Treat `event_id` as the feed's own handle: opaque, the same on every board, and never changed once served, so it can key the race in your own records. To pull racing alone, the matched boards take `sports=horse_racing` and a kick-off window, both covered in the [matched-board filters guide](https://oddsrelay.io/docs/guides/matched-board-filters).

## Each-way and extra-place odds data explained

Each-way and extra-place odds data carry each bookmaker's place terms and two exchange lays, one on the win market and one on a place market. An each-way bet is two bets, staked as two equal parts. The win part pays only if the runner wins, and it is laid on the exchange's win market. The place part pays if the runner finishes in one of the paying places, at a fraction of the win odds, and it is laid on an exchange place market, which settles on its own number of places.

### What is an extra-place offer?

An extra-place offer is a bookmaker paying each-way place terms on more places than a race's standard terms, five where four is usual, say. The exchange's place market still settles on the usual number, and that gap is what the extra-place board finds. If the runner finishes in one of the extra places, the bookmaker's place part wins and the place lay wins too, because the exchange market did not pay that position. Any other finish settles like an ordinary each-way qualifying bet, and [the extra-place entry](https://oddsrelay.io/glossary/extra-place) works one through with stakes.

The extra-place board has the each-way board's shape, so code written for one reads the other.

## 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 · trimmed to one runner · example:

```json
{
  "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" },
      "fitzdares": { "horse_racing": "2026-09-19T12:41:04Z" }
    },
    "count": 1,
    "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": "fitzdares", "price": 9.0, "places": 3, "place_fraction": 0.25, "link": null }
        ],
        "lay": {
          "win": [{ "exchange": "smarkets", "price": 10.0, "available": 48.2, "link": null }],
          "place": [{ "exchange": "smarkets", "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 the Fitzdares offer'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.

The lay side comes from Betfair, Smarkets and Matchbook, and a pair appears only where the exchange side has liquidity behind it. `lay.win` and `lay.place` each list offers lowest price first, and the two can come from different exchanges. One outcome's `lay.place` can hold place markets with different place counts, so pick the place lay by `places`: on each-way, the market whose `places` equals the offer's, and on extra place, one with fewer.

Rating a pair and working out its qualifying loss happen on your side, at your commission rate. Each part is sized like a standard lay: the win part against the win lay at the win price, and the place part against the place lay at the place part's price. The [each-way calculator](https://oddsrelay.io/widgets/each-way-calculator) and the [extra-place calculator](https://oddsrelay.io/widgets/extra-place-calculator) take one row's terms and both lay prices and show the result on every finish.

Customer-side arithmetic · example:

```ts
// Your code, per back offer on an each-way or extra-place outcome
const placePart = 1 + (offer.price - 1) * offer.place_fraction;
// lay.place is lowest price first: take the first one with the right place count
const placeLay = board === "each-way"
  ? outcome.lay.place.find((l) => l.places === offer.places) // the bookmaker's own count
  : outcome.lay.place.find((l) => l.places < offer.places); // extra place: fewer places
if (!placeLay) return; // no place lay with that count: skip this offer
const extraPlaces = offer.places - placeLay.places; // above 0: the book pays places the exchange does not
```

Raw has carried each-way terms too since 4 November 2026, on winner markets sold each-way (`outright_winner`) in horse racing, greyhound racing and golf. They sit on the venue's market as `places`, `place_fraction` and `extra_place`, which is `true` when the venue pays more places than its standard terms for the event. Any other set of terms a bookmaker offers for the same event is a market of its own, `outright_winner_ew_<places>`. Raw leaves the pairing to you, and the each-way and extra-place boards stay horse racing only.

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

| Board | What it pairs | What to compare |
| --- | --- | --- |
| [Each-way](https://oddsrelay.io/products/each-way-feed) | Each bookmaker's win price and terms against the exchange win and place lays | A place part priced close to, or above, the place lay |
| [Extra place](https://oddsrelay.io/products/extra-place-feed) | The same shape as each-way | How many places the bookmaker adds: `back[].places` minus `lay.place[].places` |
| [Best odds guaranteed](https://oddsrelay.io/products/bog-feed) | A `win` market: bookmaker back prices against exchange lays | The back price against the lay |

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. A [tipster proofing](https://oddsrelay.io/blog/tipster-proofing) record meets the same gap: it stores the price on offer when a selection goes out and takes the starting price from a results source.

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](https://oddsrelay.io/glossary/each-way-term) in the glossary covers the bet itself for anyone new to it. The 2Up board, a football board, sits in this category too, and [2Up odds data](https://oddsrelay.io/blog/2up-odds-data) walks through one of its replies.

## Price age is yours to check near the off

Racing markets often move most in the last minutes before a race, then the pre-match prices end at the off. The feed carries a race's prices up to its start, and none in running.

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 meets a maximum update time of 10–20 s for match odds, and for every market within six hours of kick-off. Further from kick-off, prices in the other markets can be older. The [freshness guide](https://oddsrelay.io/docs/guides/freshness) covers both fields.

## An unchanged board costs nothing to poll

Every data reply carries an `ETag`. Send it back as `If-None-Match` on your next poll, and a reply that has not changed comes back as a `304` that costs no tokens. The [horse racing API tutorial in Python](https://oddsrelay.io/blog/horse-racing-api-in-python) uses it to post a race's prices to Discord or Telegram only when they change.

A `format=bare` reply is the events alone, so its `ETag` moves only when your events do, and a new board version that leaves them unchanged is still a `304`. The default envelope carries `meta.processed_at`, so its `ETag` moves with every new board version. Near the off most new board versions change some runner's price, so expect most calls there to be `200`s at the board's full token cost, whichever form you ask for.

A bare reply moves the board's time to the `X-Processed-At` header and leaves out `meta.last_seen`: read the same times from the keyed `/v2/coverage` call, which costs no tokens. The racing boards cost the same whatever you filter, so on bare a filter only makes a `304` likelier. The [conditional requests guide](https://oddsrelay.io/docs/guides/conditional-requests) shows the request loop.

## Start with one race

[The coverage page](https://oddsrelay.io/coverage) lists the bookmakers the feed covers for racing. Pick one meeting and walk its each-way outcomes through the arithmetic above.

## Sources

- [Sky Bet: Best Odds Guaranteed](https://support.skybet.com/app/answers/detail/best-odds-guaranteed/), checked 29 September 2026
- [Sky Bet: Horse Racing Rules (each-way place terms)](https://support.skybet.com/app/answers/detail/horse-racing-rules/), checked 29 September 2026

18+ · OddsRelay supplies odds data only and takes no bets. Please gamble responsibly.

Source: https://oddsrelay.io/blog/horse-racing-and-each-way-odds-data
