Racing

Next races to run

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:

bookopens inpreselects
unibetweb browserrunner and market (fixed win or fixed place)
tabweb browserrunner 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_aunative app only (iOS/Android 8.69.0+)runner and market
nedsnative app onlyrunner 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).

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

Query parameters

num_racesinteger

Up to 200. Pass 200 to bulk-pull every currently quoted race in one call instead of paging.

limitinteger

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.

categoriesstring

horse,greyhound,harness or omit for all

categorystring

Alias of categories. Both spellings are accepted on every racing endpoint. An unknown value is a free 422 naming the valid ones.

bookmakersstring

Comma-separated bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing.

include_unresolvedboolean

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.

countrystring

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.

venuestring

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-Keystring
X-RapidAPI-Proxy-Secretstring

Response

Successful Response

race_idstring required
source_idstring nullable required
venuestring nullable required
venue_idstring nullable

Stable slug for this venue spelling ('sandown-hillside'). Spelling variants of one venue share one id.

venue_canonicalstring nullable

Canonical display name for venue_id ('Sandown Hillside').

venue_sitestring 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_numberinteger nullable required
categorystring nullable required
start_timestring required
countrystring nullable required
race_namestring nullable
distance_minteger nullable
track_conditionstring nullable
weatherstring nullable
places_paidinteger 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_atstring 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_classstring nullable

The first term of RA's official conditions line for this race ('BenchMark 64', 'Maiden', 'Group 1'). AU thoroughbred only; null elsewhere.

conditionsstring nullable

RA's official conditions line, verbatim — class, race type, minimum weight, age/sex restrictions, claiming allowances.

prize_totalinteger nullable

Total advertised prize for the race, whole AUD.

railstring nullable

Rail position for the meeting, as RA publishes it ('+3m Entire'). AU thoroughbred only.

track_namestring 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_atstring 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_atstring 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_sourcestring 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.

scratchingsobject[]
runnersobject[] required
data_age_secondsinteger 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_secondsinteger nullable

Age in seconds of the freshest bookmaker quote in this race.

refresh_tierstring

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.

staleboolean

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_bookmakersstring[]

Bookmaker keys in this race whose quote is older than the refresh_tier threshold (120s live, 1800s card).

cachedboolean nullable
cache_age_secondsinteger nullable

Example response

[
  {
    "category": "horse",
    "country": "AU",
    "data_age_seconds": 7,
    "race_id": "race_123",
    "race_number": 4,
    "refresh_tier": "live",
    "runners": [
      {
        "bookmakers": [
          {
            "age_seconds": 7,
            "key": "sportsbet",
            "refresh_tier": "live",
            "source_url": "https://www.sportsbet.com.au/horse-racing/australia-nz/eagle-farm/race-4-10861234",
            "stale": false,
            "win_price": 3.2
          },
          {
            "age_seconds": 9,
            "betslip_app_only": false,
            "betslip_place_url": "https://www.unibet.com.au/racing?competitorKey=202609030600.T.AUS.mackay.4.emilia_jane&priceCode=FXD&marketName=Place&priceBetType=FixedPlace#/event/202609030600.T.AUS.mackay.4/Place/FixedPlace",
            "betslip_win_url": "https://www.unibet.com.au/racing?competitorKey=202609030600.T.AUS.mackay.4.emilia_jane&priceCode=FXD&marketName=Win&priceBetType=FixedWin#/event/202609030600.T.AUS.mackay.4/Win/FixedWin",
            "key": "unibet",
            "place_price": 1.55,
            "refresh_tier": "live",
            "source_url": "https://www.unibet.com.au/racing#/event/202609030600.T.AUS.mackay.4",
            "stale": false,
            "win_price": 3.3
          },
          {
            "age_seconds": 11,
            "betslip_app_only": true,
            "betslip_place_url": "https://www.ladbrokes.com.au/racing/casino/7d3bfac5-f5b2-4ad1-82dc-e00ae2220a36/1a51322a-65bf-4f96-88b2-c294faf9199c/fp",
            "betslip_win_url": "https://www.ladbrokes.com.au/racing/casino/7d3bfac5-f5b2-4ad1-82dc-e00ae2220a36/1a51322a-65bf-4f96-88b2-c294faf9199c/fw",
            "key": "ladbrokes_au",
            "place_price": 1.5,
            "refresh_tier": "live",
            "source_url": "https://www.ladbrokes.com.au/racing/casino/7d3bfac5-f5b2-4ad1-82dc-e00ae2220a36",
            "stale": false,
            "top2_price": 1.9,
            "top3_price": 1.5,
            "top4_price": 1.3,
            "win_price": 3.1
          }
        ],
        "horse_ref": "pe:examplerunner",
        "name": "Example Runner",
        "number": 1
      }
    ],
    "source_id": "src_a1b2c3d4e5",
    "stale": false,
    "start_time": "2026-06-07T04:10:00Z",
    "venue": "Eagle Farm"
  }
]

Changes

No recorded changes to this operation across all 1 revision of this API.