---
title: "Next races to run"
method: GET
path: "/v1/racing/next-to-go"
tags: ["Racing"]
---

# Next races to run

`GET /v1/racing/next-to-go`

## Request

- Base URL: `https://api.puntersedge.online`
- URL: `https://api.puntersedge.online/v1/racing/next-to-go`
- Auth: API key in header `X-API-Key`

Cost: 2 credits for up to 20 races, 3 for 21–60, 4 for 61–200 — the default request of 10 races costs 2, and keys issued before 22 September 2026 pay 2 at any size. Returns the next races to run with runners and prices — EVERY country by default, not only AU and NZ. Britain, Ireland (`country=GB,IE`) and South Africa (`country=ZA`) carry smaller panels than Australia, and a race in any other country usually carries a single bookmaker (per-market books per race, measured: https://puntersedge.online/coverage-report.json), so pass `country=AU` (pairing it with `include_unresolved=true`, per that parameter) if you want an Australian card. `categories` can filter horse, greyhound and harness racing. NZ runs thoroughbred and harness only — there are no New Zealand greyhounds in the feed.

**One call can be the whole priced card.** `num_races=200` returns every race currently
carrying odds — the practical "bulk odds" pull. It looks 24
hours ahead, so on an Australian morning it returns the whole day's card. Which races carry prices that far out is a property
of the bookmakers, not of this API: several open their fixed-odds markets the night
before (TAB's own price history on a race probed 2026-09-03 ran back to the previous
evening) while others open close to the jump, so read `refresh_tier` and each
bookmaker's `age_seconds` rather than assuming every race has a full panel.
/v1/racing/events (hours_ahead up to 24) is the day's CARD, priced or not. To stop
schedule-polling entirely, subscribe a webhook to `race.odds_open` — it fires the moment
a race's first prices land, and this endpoint (or price-history by race_id) serves them
from then on.

**`venue` selects a specific meeting.** There is no race-class or black-type field anywhere
in this API, so "give me the Group 1s" cannot be expressed directly. The published
Group 1 calendar names the meeting and race number ahead of time, so
`venue=Randwick` plus the race number off `race_number` is how that selection is actually
made. Combine with `categories=horse` to drop the harness and greyhound cards that share
a venue name at some tracks.

**Freshness is reported worst-first.** `data_age_seconds` is the age of the OLDEST bookmaker
quote in the race and `stale` is true when that exceeds the race's `refresh_tier`
threshold, so the flag describes the
staleness you are exposed to rather than the best leg in the payload. `stale_bookmakers`
names the affected books, `freshest_age_seconds` is the best-case age, and every entry in
`bookmakers` carries its own `age_seconds`, `last_update`, `stale` and `refresh_tier`. A
stale book does not invalidate the race — drop that leg. `win_price` is the fixed win price
and `place_price` the bookmaker's place price; for the UK-licensed books (`ladbrokes_uk`,
`williamhill`, `betway_uk`) no place market is quoted, so `place_price` is DERIVED from the
each-way terms the book publishes for the race — 1 + (win − 1) × the each-way fraction — and
the race's `places_paid` is the number of places those terms pay. It is what the place half of
an each-way bet returns, not a quoted place market. Each entry's `age_basis` says where
its age counts from: `poll` from our own read of the book, `push_receipt` from when a pushed
update reached us, in which case the true age is longer by the sender's own delay. Every
age is recomputed when the response is sent, including on a race served from the stored
board (`cached: true`, `cache_age_seconds`). The full rules:
https://puntersedge.online/developers/freshness

**`refresh_tier` says which staleness rule applies.** Inside three hours of the jump a race
is `live`: every book re-prices it on every 8-20s poll and `stale` fires past 120 seconds.
Beyond three hours it is `card`: books re-price it about every 15 minutes, which is what
makes a full day's coverage affordable within their rate limits, and `stale` fires past
1800 seconds. `age_seconds` is the true age in both — only the threshold moves. The tier
is also published on EVERY bookmaker entry, and a book's tier can be `card` on a `live`
race: Betr's upstream rate-limits us, so it stops re-pricing every poll 30 minutes out
rather than three hours (PlayUp from 6 minutes out, on its request budget), and its
quotes on races 30-180 minutes from the jump carry
`refresh_tier: "card"`, are judged against 1800 seconds, and report their true
`age_seconds` (typically 5-8 minutes). If you apply your own freshness rule, branch on
each entry's `refresh_tier` rather than on a single number, or every price on the
morning card — and every Betr price outside half an hour — will read as stale.

Scratched runners are returned in `scratchings` only. They are never in `runners`, even
while a book is still publishing a price for them. A scratching no Australian bookmaker has
reported, while two or more of them still price the runner, is held back: the runner stays
in `runners`, and the scratching is listed once a bookmaker reports it.

**Every bookmaker leg carries `source_url` — a deep link to that book's own page for this
race.** Built at ingest from the book's native race id, on the same route its iOS/Android
app-links use, so on a phone it opens the bookmaker's app on the exact race. It is a plain
race-page URL: you may append your own query parameters (affiliate or campaign tags) —
whether a bookmaker's programme credits them is between you and that programme, and nothing
here promises attribution. The field is absent when no reliable URL can be built — a
guessed link that lands a punter on the wrong page is worse than a missing one — which
today means betgold, boostbet and the Betfair exchange (no provable race-page route;
measured 2026-09-16 over 48 hours, every other book links 100% of its quotes).
**This list moves, so do not code against it.** betdeluxe published no usable route when
this was last measured on 2026-09-01 and links 100% of its quotes now; the Betfair
exchange was missing from the sentence entirely. Branch on whether `source_url` is
present on the quote in front of you, never on this list.

**Same Race Multi legs** (`top2_price`, `top3_price`, `top4_price`, since 2026-09-05).
The price for the runner to finish in the first two, three or four — the legs a Same
Race Multi is built from. Present on a bookmaker entry only when that book publishes
the leg: Ladbrokes, Neds and PointsBet carry all three, Sportsbet where its racecard
offers the SRM markets; every other book omits the keys. Guaranteed
`win_price >= top2_price >= top3_price >= top4_price > 1.0` — a leg that breaks the
order at the source is dropped rather than published. A Top 3 on a race paying three
places is the same bet as the place and prices the same; on a race paying two it is
not. These legs do not advance `updated_at` — /v1/racing/changes keys on the win price.

**Runner-level betslip links** (`betslip_win_url`, `betslip_place_url`,
`betslip_app_only`). Where `source_url` opens the book's race PAGE, these open the book's
BETSLIP with this runner already selected. **Plus and above** (since 21 September 2026):
the betslip fields are served on Plus, Business, Platform and Scale, and a Standard or
Hobby key issued before that date keeps them - a key never loses what it was sold. On every
other plan the fields are simply absent, exactly as on a book with no route; `source_url`,
the race-page link, is on every plan and is unaffected. Coverage, honestly:

| book | opens in | preselects |
|---|---|---|
| `unibet` | web browser | runner **and** market (fixed win or fixed place) |
| `tab` | web browser | runner only - the slip shows a win box and a place box, so `betslip_win_url` and `betslip_place_url` are deliberately the same URL |
| `ladbrokes_au` | **native app only** (iOS/Android 8.69.0+) | runner and market |
| `neds` | **native app only** | runner and market |

Every other bookmaker has no such route and carries none of these fields.
`betslip_app_only` is `true` for Ladbrokes and Neds: in a desktop browser those URLs
redirect to the app-download page, so send them to a phone with the app installed, or
fall back to `source_url`. Measured 2026-09-03. The Ladbrokes pattern is vendor
documented; the Neds one is **inferred** from it (same platform, same entrant ids) and
has not been tap-tested on a Neds install.

These routes are read from the bookmakers' own site code. **None of them is published or
supported by the bookmaker**, so any can change without notice. A daily automated check
loads a live Unibet link and a live TAB link in a real browser and confirms the betslip
populates; when a book fails that check its betslip fields go **null** until it passes
again, so a broken route degrades to "no link" rather than sending a member to a dead
page. Ladbrokes and Neds cannot be checked this way - a headless browser cannot open a
native app - and ship on the vendor's documentation instead.

The fields are **absent** on a book that has no link, exactly as `source_url` is; treat
absent and `null` alike as "no betslip link". Nothing about `source_url` changed.

Like `source_url` these are plain URLs, and extra query parameters you append are passed
through untouched. A link that opens a native app leaves the browser, so cookie-based
attribution set by your own site does not travel with it.

**Your own links** (`source_link`, `betslip_win_link`, `betslip_place_link`, since
2026-10-14, Plus and above). Store your bookmaker link templates once with
`PUT /v1/link-templates` (for example your affiliate tracking link per bookmaker, with
`{url}` where the bookmaker's URL goes) and every leg also carries a ready-made twin of each
URL, built through them: put the `*_link` value on your page. A twin is absent where there
is nothing to link (no URL from the book, or your rule for that book is "none"), and an
account with no stored templates sees none. The `*_url` fields are unchanged, and the
`ETag` changes when your templates do.

**Panels differ by market.** Australian and New Zealand races carry the full Australian
panel. British and Irish (`country=GB,IE`) and South African (`country=ZA`) races carry
smaller panels of their own — locally licensed bookmakers plus the Australian books that
quote the meeting. A race in any other country usually carries a single bookmaker, so a
cross-book comparison there is comparing one price with itself. The books per race for
every market, measured over a trailing window and dated, are published at
https://puntersedge.online/coverage-report.json (`coverage_summary`).

**Hong Kong** (feature_requests #29, 2026-09-09). Sha Tin and Happy Valley are served
under `country=HK` whenever an Australian bookmaker quotes them. **Prices only** — win
and place, from the books that carry the meeting. No results, no form, no race
conditions and no `runner_ref`: those come from Racing Australia and cover AU
thoroughbreds only, so every form field on a Hong Kong race is null. `horse_ref` is the
exception, because it is derived from the runner's name rather than read from anywhere:
a Hong Kong thoroughbred carries one, and it joins that runner to itself across Hong
Kong meetings. Coverage follows
the HKJC's own calendar rather than ours — Happy Valley on Wednesday evenings and Sha
Tin on Saturday or Sunday afternoons (AEST), and nothing at all on the days between.
Depth is the foreign-race depth described above, not the Australian panel.

**`country=AU` also drops Australian races that are not labelled yet.** A race's country
is set when the meeting is resolved, so the later races on an Australian card sit in the
window with `country` null for a while. In a sample window 33 of 65 races were unlabelled
and every one was at an Australian venue — Angle Park, Menangle, Ballarat, Albury and the
like, most of them the same venues carrying labelled races earlier in the day. Filter on
`country=AU` and you lose them; leave it off and read `country` per race instead.

**An Australian race gathers books as it approaches the jump**, so depth depends on how far
out you look rather than sitting at one number. Measured 2026-08-17: a median of 10
bookmakers inside 30 minutes of the jump, 9 at 30-60 minutes, 5 at one to two hours, and 2
beyond that. Since this endpoint returns the NEXT races, expect the near-jump end of that
range. A window-wide average is not meaningful here — it just tracks whichever mix of
start times happens to be loaded.

How much of the card is foreign is NOT a fixed ratio: the foreign share of the window runs
near zero through the Australian afternoon and reaches 100% overnight. Filter on `country`
rather than assuming a mix.

**Form fields and race conditions are AU/NZ-only, and which of them you get is decided by
the racing code, not by the country.** For AU thoroughbreds, Racing Australia's own
acceptance pages fill the gaps: runner `jockey`/`trainer`/`weight`/`form` where
no book supplies them, `runner_ref` (Racing Australia's code for the runner, from its
latest read of the field — on race day the same value `/v1/racing/results` and the archives
carry for this race; Racing Australia issues a new code for the same horse each day, so join
a horse across meetings on `horse_ref`), and the race-level `race_class`,
`conditions` (RA's line verbatim), `prize_total` and `rail`. Otherwise the runner fields
`barrier`, `jockey`, `trainer`,
`weight` and `form`, and the race-level `race_name`, `distance_m`, `track_condition`,
`weather` and `places_paid`, are supplied by exactly one bookmaker in the panel, and that
connector fetches Australian and New Zealand meetings only. A race outside Australia and
New Zealand therefore carries none of them, and this is a structural ceiling rather than a
thin sample: across 13 days of snapshots that book appears on 2,813 Australian and 166 New
Zealand races and on none of the 5,624 races held in the 31 other countries the panel
quotes.

**barrier: for AU thoroughbreds the drawn barrier from Racing Australia; for greyhounds
and harness as the book publishes it — BetRight, BetGold and BoostBet renumber after
scratchings, BetDeluxe does not (measured 2026-09-28).** That renumbering was measured on
thoroughbred fields against Racing Australia's draw (39% of those three books' rows one or
more gates LOWER than RA's), which is why, since
2026-09-29, RA's draw REPLACES every book's own value on an AU thoroughbred RA's
acceptances name, rather than only filling a gap. A runner RA does not name — any NZ
race, or a name RA spells differently — still carries whatever the books sent.
**`jockey` is a person's name** (2026-09-29): an apprentice's weight claim is stripped
rather than served glued to it ("Sharni Webster(A3.0)" is served "Sharni Webster").
Greyhound runners carry the trainer in `trainer` and `jockey` null; harness runners carry
the driver in `jockey`. A greyhound `jockey` value you stored from a response before
2026-09-29 may hold the trainer.

**`horse_ref` is the one runner field that is DERIVED rather than reported** (2026-09-21).
Every thoroughbred runner here carries `horse_ref`, `pe:<name>` — the registered name
folded to letters and digits — because it is computed from the name the panel already
agreed on rather than fetched from anywhere, so it is present on races no book enriched
and on foreign meetings alike. It is the same value on /v1/racing/results runners and
placings, the closing-line and price-path archives, /v1/racing/horses/form and
/v1/racing/horses/runs, and it is the key to join this runner to itself at another
meeting — which `runner_ref` cannot do. Null on greyhound and harness runners, where
`runner_ref` (`grv:`/`grsa:`) is already a per-dog registry id that persists.

On Australian races the fields you get depend on the code. Measured 2026-08-17 over the
same runner merge this endpoint serves: greyhound runners carried `trainer` 99%, `form` 99%
and `barrier` 99%, with `jockey` 0% and `weight` 16%; harness runners carried `trainer`
100%, `form` 96% and `jockey` — the driver — 100%, with `barrier` and `weight` 0%. Those
zeroes are what that book publishes per code, not gaps in Australian coverage. The measured
window held no Australian thoroughbred meeting, so do not read these as fixed rates.
Null-check every one of these fields rather than inferring its presence from `country`. Conditional requests: every 200 carries an `ETag`; send it back as `If-None-Match` and an unchanged board is answered `304 Not Modified` for 0 credits (served from the stored board, which a paid key reuses for at most 20 s and a Free key for at most 65 s; a poll that lands on a cache miss is billed as usual).

## Query parameters

- `num_races` integer: Up to 200. Pass 200 to bulk-pull every currently quoted race in one call instead of paging.
- `limit` integer: Alias for `num_races`, accepted because it is the near-universal REST spelling and because /v1/racing/results already takes it — learning `limit` on one racing endpoint and being refused on the next is the inconsistency this closes. Same bounds and same meaning; when both are given, `limit` wins. Measured 2026-09-14: callers sent `limit` here 28 times and were refused every time.
- `categories` string: horse,greyhound,harness or omit for all
- `category` string: Alias of `categories`. Both spellings are accepted on every racing endpoint. An unknown value is a free 422 naming the valid ones.
- `bookmakers` string: Comma-separated bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing.
- `include_unresolved` boolean: Include races whose country is not resolved yet (country is null). OFF by default. Most of an Australian card is unresolved until the meeting is confirmed — 58.8% of horse races in a measured window — so country=AU alone can return far fewer races than are running, or none at all. Pass true to get them, and read `country` per race to see which are still unresolved.
- `country` string: ISO country codes, comma-separated (e.g. AU, AU,NZ, GB,IE or ZA). Omit for every country. GB,IE is the British and Irish card (the code is GB: `UK` is not an ISO code and matches no race, so it returns an empty list), priced by the UK-licensed bookmakers (region gb) alongside the Australian books that quote it; UK and Irish racecards number races by running order, and the UK books' place_price is derived from their each-way terms (see the field notes). ZA is the South African card, priced by the South African bookmakers (region za) alongside the Australian books that quote it; races are numbered in running order, easybet_za and topbet_za carry a place_price with places_paid on the race, betway_za does not (no each-way market); South African thoroughbred results (the placed runners ranked, no official starting price) are served from the 2026-10-15 card, see /v1/racing/results. HK serves Sha Tin and Happy Valley (prices only, no results or form) on the HKJC race nights. Most of an AU card is unresolved until the meeting is confirmed, so pair this with include_unresolved=true or you will silently lose them. Outside AU, NZ, GB/IE and ZA a race usually carries a single bookmaker; the measured books per race for every market, and for every other country, are on https://puntersedge.online/coverage-report.json (coverage_summary). NOTE that this filter also excludes Australian races whose country is not labelled yet — typically the later races on the card — so it is not a clean AU/foreign split. Omit it and read `country` per race if you need those.
- `venue` string: Venue names, comma-separated and case-insensitive (e.g. Randwick or Randwick,Flemington). Matched exactly against the race's `venue` after case folding, so pass the venue as this API spells it -- read `venue` off an unfiltered call rather than guessing. Omit for every venue. This narrows the SAME window the endpoint always serves (24 hours ahead since 2026-09-03, six before that); it does not look beyond it, so a venue with no race inside the window returns an empty list rather than an error.

## Headers

- `X-API-Key` string
- `X-RapidAPI-Proxy-Secret` string

## Response `200`

Successful Response

- NextRaceOut[]
  - `race_id` string, required
  - `source_id` string, nullable, required
  - `venue` string, nullable, required
  - `venue_id` string, nullable: Stable slug for this venue spelling ('sandown-hillside'). Spelling variants of one venue share one id.
  - `venue_canonical` string, nullable: Canonical display name for venue_id ('Sandown Hillside').
  - `venue_site` string, nullable: Physical-complex key: tracks sharing one venue complex share one site ('sandown' covers Sandown, Sandown Hillside, Sandown Lakeside and the Sandown Park greyhound track).
  - `race_number` integer, nullable, required
  - `category` string, nullable, required
  - `start_time` string, required
  - `country` string, nullable, required
  - `race_name` string, nullable
  - `distance_m` integer, nullable
  - `track_condition` string, nullable
  - `weather` string, nullable
  - `places_paid` integer, nullable: Places the race pays, as the bookmaker supplying it states. For British and Irish races this is the each-way terms' place count (2 or 3, 4 on a 16-plus handicap) and the UK books' place_price is derived from the same terms; for Australian races it is the quoted place market's count. Null when no bookmaker on the race supplies it.
  - `track_condition_changed_at` string, nullable: UTC instant of the most recent logged track-condition, weather or rail transition for this race's meeting — Racing Australia's re-published rating or a bookmaker's race page moving between two of our polls. Null when nothing has changed since we began watching the meeting. /v1/racing/track-conditions serves the sequence.
  - `race_class` string, nullable: The first term of RA's official conditions line for this race ('BenchMark 64', 'Maiden', 'Group 1'). AU thoroughbred only; null elsewhere.
  - `conditions` string, nullable: RA's official conditions line, verbatim — class, race type, minimum weight, age/sex restrictions, claiming allowances.
  - `prize_total` integer, nullable: Total advertised prize for the race, whole AUD.
  - `rail` string, nullable: Rail position for the meeting, as RA publishes it ('+3m Entire'). AU thoroughbred only.
  - `track_name` string, nullable: Racing Australia's own name for the course this race runs on, verbatim: 'Hillside' at Sandown, 'Randwick' vs the Kensington inner course, 'Main' elsewhere. Combine with venue_site to tell dual-course meetings apart. AU thoroughbred only; null elsewhere.
  - `market_closed_at` string, nullable: UTC time at which the market was FIRST seen shut — the book stopped taking bets on this race. Poll-observed: this is the first poll that saw the state, not the exact instant, so it lands up to one polling pass after it, and passes are most frequent in the minutes before the jump. Set once: a market that flickers SUSPENDED -> OPEN -> SUSPENDED keeps the FIRST suspension. TWO SOURCES report market state. PointsBet closes its book at the real jump — measured 2026-09-03 over 9 live races followed across their jump, 0.2 to 1.4 minutes AFTER the advertised start on AU/NZ cards — and the Betfair Exchange reports it while it quotes the market, which is until about two minutes before the jump. `market_state_source` names the one that produced this value. Exchange-sourced stamps are withheld from customer plans, so a race seen shut only by the exchange reads null. Bookmaker-sourced stamps are served to everybody. Null also means no feed observed a transition — it never means the market stayed open.
  - `inplay_at` string, nullable: UTC time at which the race was FIRST seen in-play — the actual off, as opposed to `start_time`, which is the ADVERTISED off and moves. Same poll-observed, set-once semantics as `market_closed_at`, and withheld on the same source rule. Reported by the Betfair Exchange only: bookmakers do not run racing in-play, so a bookmaker feed says when it CLOSED its book, not when the race went. For a race the exchange has stopped quoting, `market_closed_at` from a bookmaker is the closest measured instant to the off.
  - `market_state_source` string, nullable: Which feed's market state produced `market_closed_at` / `inplay_at` on this race — 'pointsbetau' for the bookmaker, 'betfair_ex_au' for the exchange. Set once, alongside the first stamp. Null when no transition has been observed, and null for customer plans whenever the stamps themselves are withheld, so a non-null value here is always the provenance of values you can see.
  - `scratchings` object[]
  - `runners` object[], required
  - `data_age_seconds` integer, nullable: Age in seconds of the OLDEST bookmaker quote in this race — the staleness you are exposed to if you act on any price here. Since 2026-08-15 this is the worst leg, not the freshest; `freshest_age_seconds` carries the best-case figure.
  - `freshest_age_seconds` integer, nullable: Age in seconds of the freshest bookmaker quote in this race.
  - `refresh_tier` string: Which refresh rule this race is on, and therefore what the race-level `stale` means. `live` = inside 3 hours of the jump: books re-price it on every 8-20s poll, and `stale` fires past 120 seconds. `card` = further out: books re-price it about every 15 minutes so a full day fits inside their rate limits, and `stale` fires past 1800 seconds. Every published quote carries ITS OWN `refresh_tier`, and it can be `card` on a `live` race: Betr's upstream rate-limits us, so that book stops re-pricing every poll 30 minutes before the jump rather than 3 hours (PlayUp, on a 6-requests-a-minute budget, at 6 minutes), and its quotes between there and 3 hours are refreshed every 5-15 minutes, published as `card`, and judged against 1800 seconds (since 2026-09-07; before that they were marked stale). A book's tier is never tighter than its race's. `age_seconds` is the TRUE age in every case — only the threshold changes.
  - `stale` boolean: True when the oldest contributing bookmaker is older than this race's `refresh_tier` threshold — 120s on `live`, 1800s on `card`. It does NOT mean the whole race is dead — read `stale_bookmakers` and each bookmaker's own `stale`/`age_seconds`.
  - `stale_bookmakers` string[]: Bookmaker keys in this race whose quote is older than the `refresh_tier` threshold (120s live, 1800s card).
  - `cached` boolean, nullable
  - `cache_age_seconds` integer, nullable

## Other responses

- `401`: Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors
- `402`: Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing
- `422`: Validation Error
- `429`: Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits
- `500`: Unexpected server error. Safe to retry with backoff.

---

[API](https://skmtc.dev/puntersedge/apis/puntersedge-odds-api.md) · [All operations](https://skmtc.dev/puntersedge/apis/puntersedge-odds-api/llms.txt) · [OpenAPI document](https://skmtc.dev/puntersedge/apis/puntersedge-odds-api/revisions/f35b6aab1946?raw)
