Best racing price per runner across books
Cost: 3 credits. The best win, place and tote price for every runner in the next races, with the bookmaker offering it and a deep link to their race page.
market_percentage is the sum of implied probabilities at the best available price. Under 100 means the best prices across books beat the field — a cross-book arbitrage. Any single book's market is always over 100.
price_spread_pct per runner is how much better the best price is than the worst currently quoted: how much a punter loses by not shopping around.
Win and place coverage are counted separately. books_quoting_win is the number of bookmakers with a WIN price on that runner and books_quoting_place the number with a PLACE price. They are not the same set — a book can quote the win and not the place on the same runner — so a place bet sized off a win-market count is sized off the wrong number. books_quoting is retained unchanged for existing clients and equals books_quoting_win; read the explicit names in new code. books_compared, at race level, is the number of distinct books contributing any win price to the whole race.
⚠️ best_tote is not comparable to best_win. A pre-race tote dividend is an estimate that moves as the pool fills, and taking the highest across books and products is upward biased by construction — the noisiest estimate wins. Greyhound pools are thinnest and swing hardest: measured 2026-08-15, greyhound tote-to-fixed ratios reached 8.8x on betright and 9.9x on tabtouch, against averages near 1.0 for horse and harness. A tote figure far above the fixed price is usually a thin pool, not an edge. Settle against a FINAL dividend, and read the product code — PROV is indicative, SP/MIDDIV/RD+/BT+SP are named products.
The comparison covers the Australian fixed-odds bookmakers, on New Zealand races as well as Australian ones. Not every book quotes New Zealand, so an NZ race is compared across fewer books than an AU race; the live per-country book counts are published at https://puntersedge.online/coverage-report. Betfair Exchange prices are withheld pending a Betfair Exchange data licence, and non-AU reference books are excluded because an Australian punter cannot bet them — so betfair_ex_au never appears as a best_win bookmaker, and market_percentage is the fixed-odds market rather than an exchange-inclusive one.
Hong Kong (country=HK, feature_requests #29, 2026-09-09). Sha Tin and Happy Valley are compared across whichever Australian books quote the meeting — win and place, the same best_win / best_place shape as an Australian race. PRICES ONLY: no results, no form, no race conditions, no runner_ref — horse_ref alone is present, because it is derived from the runner's name and needs no Racing Australia read. Unlike the rest of the foreign card this is a real cross-book comparison rather than one book against itself, but it is a smaller panel than an Australian race and only exists on the HKJC's nights — Happy Valley on Wednesday evenings, Sha Tin on Saturday or Sunday afternoons (AEST).
Scratched runners are never priced here. They appear in scratchings only, and are left out of market_percentage.
Placeholder prices are kept out of the best line (since 2026-09-10). A quote of $21 or more that is at least 4x the median of three or more other books for the same runner is treated as a placeholder, not a price: it is excluded from best_win / best_place, market_percentage and price_spread_pct, and listed on the runner as suspect_win / suspect_place (bookmaker, price, consensus) so nothing is hidden. A book that has not priced a runner yet can publish $101 against a $9 field; before this that was the best price. The same treatment, with a reason on the entry, covers a book quoting before the race's newest scratching (pre_scratching), a book whose whole win book sums under 95% (book_under_round), and since 2026-10-20 a book that has not re-priced the race for an hour while the other books have, on a runner where it is 1.6x or more the median of eight or more others (book_not_repriced, with consensus and behind_market_s).
Every published price carries its own age. best_win, best_place and best_tote each include last_update, age_seconds, stale and refresh_tier, because the book with the best price is not always the book that answered most recently. best_win and best_place both carry the offering book's race-page source_url. On Plus and above both also carry betslip_url, the runner-level betslip link of that same book - null when the book has no such route, and absent on plans below Plus. See the table below. Race level, data_age_seconds is the oldest of those winning quotes and stale_bookmakers names any past its own refresh_tier threshold (120s on live, 1800s on card; each published quote carries its own tier, and a Betr quote 30-180 minutes from the jump is card on a live race — see /v1/racing/next-to-go for what the tiers mean). Responses are cached for 20s and say so via cached / cache_age_seconds; ages are recomputed at serve time, so a cached payload reports its real age rather than the age it had when it was stored.
Same Race Multi legs (best_top2, best_top3, best_top4, since 2026-09-05). The best price across books for the runner to finish in the first two, three or four, in the same shape as best_place (price, bookmaker, source_url, age fields) plus books_quoting, the number of books offering that leg. Present only when at least one book quotes the leg: Ladbrokes, Neds and PointsBet carry all three, Sportsbet where its racecard offers them. Every published leg satisfied win >= top2 >= top3 >= top4 > 1.0 at its source.
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 and betslip_link on every quote object, 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 each quote also carries a ready-made twin of its URLs, built through them: put the *_link value on your page. A twin is absent where there is nothing to link, and an account with no stored templates sees none. The *_url fields are unchanged.
Request
- Base URL: https://api.puntersedge.online
- URL: https://api.puntersedge.online/v1/racing/best-odds
- Auth: API key in header X-API-Key
Query parameters
How many hours ahead to scan for priced races, from now. Default 6, which is what this endpoint has always used. The ceiling is your plan horizon, returned as X-Horizon-Hours: 24 Free and Hobby, 48 Standard, 168 Plus and above. Raising it past about 12h returns races no book has opened yet -- the first price lands a median 10.1h before the jump on AU thoroughbreds -- so a wide window is for building tomorrow's board, not for finding more prices today.
Single-day alias: date=YYYY-MM-DD scans that Australian Eastern meeting day instead of a rolling hours_ahead window. Mutually exclusive with hours_ahead, bounded by the same X-Horizon-Hours ceiling, forward-looking only. num_races still caps the rows returned, so pair a date with num_races=150 for a full card.
Up to 150. Pass 150 to compare every race on the day's card in one call instead of paging.
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.
horse,greyhound,harness or omit for all
Alias of categories. Both spellings are accepted on every racing endpoint. An unknown value is a free 422 naming the valid ones.
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. Same rule and same spelling as /v1/racing/next-to-go. This narrows the window the endpoint already serves; it does not look beyond it, so a venue with no race inside the window returns an empty list rather than an error. There is no race-number filter: venue selects the meeting and you read race_number off the races it returns.
Restrict the comparison to these bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing.
ISO country codes, comma-separated (e.g. AU; Britain is GB, not UK). Omit for every country. Outside AU, NZ, GB/IE and ZA a race is usually quoted by a single book, so a cross-book comparison on it compares nothing — Hong Kong (HK) is the exception among those countries, carrying several Australian books on the HKJC race nights, win and place, prices only. Pair with include_unresolved=true or you will silently lose races whose meeting is not confirmed yet.
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.
Headers
Response
Successful Response
Example response
[
{
"books_compared": 11,
"cache_age_seconds": 0,
"cached": false,
"category": "horse",
"country": "AU",
"data_age_seconds": 41,
"freshest_age_seconds": 3,
"market_percentage": 106.4,
"race_id": "race_123",
"race_name": "TAB ROSEBUD",
"race_number": 8,
"refresh_tier": "live",
"runners": [
{
"barrier": 3,
"best_place": {
"age_seconds": 4,
"betslip_url": "https://www.tab.com.au/racing/2026-09-03/REDCLIFFE/RED/R/4?runner=5",
"bookmaker": "tab",
"last_update": "2026-08-15T05:44:21Z",
"price": 2.15,
"source_url": "https://www.tab.com.au/racing/2026-09-03/REDCLIFFE/RED/R/4",
"stale": false
},
"best_tote": {
"bookmaker": "betright",
"price": 7.1,
"product": "RD+"
},
"best_win": {
"age_seconds": 6,
"bookmaker": "betright",
"last_update": "2026-08-15T05:44:19Z",
"price": 6.5,
"refresh_tier": "live",
"source_url": "https://...",
"stale": false
},
"books_quoting": 11,
"books_quoting_place": 8,
"books_quoting_win": 11,
"horse_ref": "pe:chillygirl",
"jockey": "J Mcdonald",
"name": "Chilly Girl",
"number": 7,
"price_spread_pct": 18.2,
"trainer": "C Waller"
}
],
"scratchings": [],
"stale": false,
"stale_bookmakers": [],
"start_time": "2026-08-15T05:45:00Z",
"track_condition": "Good (4)",
"venue": "Rosehill"
}
]Changes
No recorded changes to this operation across all 1 revision of this API.