Returns the next races to run with runners and prices — EVERY country by default, not only AU and NZ. Measured over 10.5 days, 60.8% of the race catalogue is non-AU, and a race outside AU/NZ carries a median of ONE bookmaker against 13 for an Australian one, 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. Since 2026-09-03 this endpoint looks 24 hours ahead rather than six, so on an Australian morning it returns the whole day's card where it used to stop mid-afternoon. 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. **`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. **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. **A foreign race usually has nothing to compare.** Measured over 13 days, a race outside Australia and New Zealand carries a median of one bookmaker's price and at most three — about one in nine has a second book. Australia and New Zealand are the only two countries with a real panel: re-measured 2026-09-02 over 7 days across the books served to customers, an Australian race carries a median of 13 quoting bookmakers and a New Zealand race a median of 10. So a cross-book comparison on foreign racing is usually comparing one price with itself. **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 this ENTRY — it joins this runner to RA's form page and to our acceptance rows for THIS meeting, and it changes from meeting to meeting: measured 2026-09-21, 1,450 of the 1,452 horses with two or more runs in our own results carried a different code on each), 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`. Until 2026-09-29 two books wrote a greyhound TRAINER into `jockey`, so a greyhound `jockey` read from an older response or cache was a 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`.
X-API-Key
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
curl 'https://api.puntersedge.online/v1/racing/next-to-go' \
-H 'X-API-Key: YOUR_KEY'
| Name | In | Type | Required | Description |
|---|---|---|---|---|
num_races |
query | integer | no | Up to 200. Pass 200 to bulk-pull every currently quoted race in one call instead of paging. |
limit |
query | integer | no | 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 |
query | string | no | horse,greyhound,harness or omit for all |
category |
query | string | no | Alias of `categories`. Both spellings are accepted on every racing endpoint since 2026-09-24; before that, using one family's spelling on the other was a hard 422. An unknown value is a free 422 naming the valid ones. |
bookmakers |
query | string | no | Comma-separated bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing — it used to return a billed empty response. |
include_unresolved |
query | boolean | no | 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 |
query | string | no | ISO country codes, comma-separated (e.g. AU or AU,NZ). Omit for every country. 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. A race outside AU/NZ carries a median of ONE bookmaker (max 3; about one in nine has a second), while an Australian race gathers books toward the jump (median 10 inside 30 minutes, 2 at 2-4 hours). 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 |
query | string | no | 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. |
Status codes: 200, 401, 402, 422, 429, 500. Response bodies are JSON; the full schema is in /openapi.json.
200 application/json
— this call costs 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.
Field names and types are as the API returns them; values are a real sample, trimmed to a few items.
[
{
"race_id": "2f0e2df8-5386-4aae-bbdb-7b784dee3e43",
"source_id": "src_9e2b7c41aa",
"venue": "Grafton",
"venue_id": "grafton",
"venue_canonical": "Grafton",
"venue_site": "grafton",
"race_number": 1,
"category": "horse",
"start_time": "2026-09-07T03:20:00Z",
"country": "AU",
"race_name": "TEAM AIDAN MARKETS @ CRJC 22 NOVEMBER CB MDN PLT",
"distance_m": 1700,
"track_condition": "Soft (5)",
"weather": "Clear Sky",
"places_paid": 3,
"track_condition_changed_at": null,
"race_class": "MDN",
"conditions": "Maiden Plate",
"prize_total": 27000,
"rail": "True",
"track_name": "Grafton",
"market_closed_at": null,
"inplay_at": null,
"market_state_source": null,
"scratchings": [
{
"name": "Airhawk",
"number": 1,
"barrier": 9,
"scratched_at": "2026-09-03T11:47:44Z"
},
{
"name": "Sweet September",
"number": 9,
"barrier": 3,
"scratched_at": "2026-09-06T18:58:41Z"
}
],
"runners": [
{
"name": "Fabulous Fiano",
"number": 2,
"barrier": 6,
"jockey": "Matthew McGuren",
"trainer": "Matthew Dunn",
"horse_ref": "pe:fabulousfiano",
"bookmakers": [
{
"key": "tab",
"win_price": 8.0,
"place_price": 2.6,
"age_seconds": 6,
"stale": false,
"refresh_tier": "live",
"source_url": "https://www.tab.com.au/racing/2026-09-07/GRAFTON/GRA/R/1"
},
{
"key": "sportsbet",
"win_price": 7.5,
"place_price": 2.5,
"top2_price": 2.5,
"top3_price": 1.53,
"top4_price": 1.2,
"age_seconds": 9,
"stale": false,
"refresh_tier": "live",
"source_url": "https://www.sportsbet.com.au/horse-racing/australia-nz/grafton/race-1-10895656"
},
{
"key": "ladbrokes_au",
"win_price": 7.0,
"place_price": 2.6,
"top2_price": 2.6,
"top3_price": 1.6,
"top4_price": 1.22,
"age_seconds": 11,
"stale": false,
"refresh_tier": "live",
"source_url": "https://www.ladbrokes.com.au/racing/grafton/1564cb51-fcbb-4038-b488-372027041b0e",
"betslip_win_url": "https://www.ladbrokes.com.au/racing/grafton/1564cb51-fcbb-4038-b488-372027041b0e/fw",
"betslip_place_url": "https://www.ladbrokes.com.au/racing/grafton/1564cb51-fcbb-4038-b488-372027041b0e/fp",
"betslip_app_only": true
},
{
"key": "betright",
"win_price": 7.0,
"place_price": 2.0,
"age_seconds": 14,
"stale": false,
"refresh_tier": "live",
"source_url": "https://www.betright.com.au/racing/grafton/1/62386616/win"
},
{
"key": "unibet",
"win_price": 6.0,
"place_price": 2.4,
"age_seconds": 12,
"stale": false,
"refresh_tier": "live",
"source_url": "https://www.unibet.com.au/racing#/event/202609070300.T.AUS.grafton.1"
}
]
},
{
"name": "Foxwedge Arrow",
"number": 3,
"barrier": 2,
"jockey": "Ben Looker",
"trainer": "Kris Lees",
"horse_ref": "pe:foxwedgearrow",
"bookmakers": [
{
"key": "tab",
"win_price": 51.0,
"place_price": 11.0,
"age_seconds": 6,
"stale": false,
"refresh_tier": "live",
"source_url": "https://www.tab.com.au/racing/2026-09-07/GRAFTON/GRA/R/1"
},
{
"key": "ladbrokes_au",
"win_price": 46.0,
"place_price": 9.5,
"age_seconds": 11,
"stale": false,
"refresh_tier": "live",
"source_url": "https://www.ladbrokes.com.au/racing/grafton/1564cb51-fcbb-4038-b488-372027041b0e"
},
{
"key": "sportsbet",
"win_price": 41.0,
"place_price": 9.0,
"age_seconds": 9,
"stale": false,
"refresh_tier": "live",
"source_url": "https://www.sportsbet.com.au/horse-racing/australia-nz/grafton/race-1-10895656"
}
]
}
],
"data_age_seconds": 14,
"freshest_age_seconds": 6,
"refresh_tier": "live",
"stale": false,
"stale_bookmakers": [],
"cached": false,
"cache_age_seconds": 0
}
]
GET /v1/racing/acceptances — Full-day AU thoroughbred acceptance card: every meeting, race and runnerGET /v1/racing/best-odds — Best racing price per runner across booksGET /v1/racing/changes — Races and prices that changed since a timestampPOST /v1/racing/clv — Score bets against the closing line (CLV)GET /v1/racing/events — Upcoming race listGET /v1/racing/form — Every runner in one race with its recent runs, from our own resultsGET /v1/racing/greyhounds/form — Greyhound form history for one dog, from our own resultsGET /v1/racing/greyhounds/stats — Greyhound record by track, distance, box or grade, from our own resultsThe free tier needs no credit card, and the sandbox endpoints need no key at all.
Get a free API key Quickstart