API reference / Racing
GET /v1/racing/consensusThe market consensus on one race: margin-free probability per runner
The market's consensus on one race while its market is open: for every runner, the implied probability after each book's margin is removed and the books are combined, the price that probability corresponds to, the median, mean, best and worst price, and how far the best available price sits from the consensus. The inputs are printed beside the answer — which books were counted, which were left out and why. **Naming the race.** `race_id` from /v1/racing/next-to-go or /v1/racing/events is exact. Failing that, `venue` + `meeting_date` (the AET meeting date) + `race_number`; two races sharing those three answer 409 `race_ambiguous` and cost nothing. **How the number is made.** Each included book's win prices on the field are turned into implied probabilities (1 / price) and its margin removed — `power` (default) raises them to the exponent that makes them sum to one, `multiplicative` divides them by their sum — so every book contributes a field that sums to 1. The consensus for a runner is the MEDIAN of those per-book probabilities, and the medians are scaled once so the field sums to 1 again; `market.consensus_sum_before_normalisation` shows how far from 1 they were before scaling. Every included book counts once. Nothing is weighted by an opinion of which book is sharp. **Which books count.** A book is included when it is visible to you (the exchange and the non-Australian reference books are withheld here as on every racing endpoint), its quote is inside its refresh-tier threshold (the same per-book rule next-to-go publishes: 120 s inside three hours of the jump, 1800 s on the morning card, with Betr and NextBet on their own horizons), and it prices the complete field — `market.field_size` is the number of runners a complete-field book quotes, and a book pricing fewer is `partial_field`. Placeholder prices are dropped quote by quote on the rule /v1/racing/best-odds applies (a long price at least four times the median of three or more other books) and listed in `excluded_quotes`. Scratched runners are dropped and listed under `scratchings`. **Reading `consensus_prob`.** It is the market's implied probability after margin, not a probability of winning and not a fair price. `best_vs_consensus_pct` is best_price / consensus_price − 1: negative is normal, because every quoted price carries margin and the best quote usually sits below the panel's margin-free price (about −20 % on a 130 % greyhound market); it turns positive only when one book prices a runner above the panel's margin-free view. It says where the best quote sits, nothing more. The fields a reader would use to judge how much to trust a figure — `books`, the spread, the ages, `secs_to_jump` — are separate on purpose; there is no single confidence score. **Live only.** The consensus is read from the open market. A race that has left the live board answers 404; the consensus at the close will arrive as columns on the permanent archive, where it belongs. A race inside the ten minutes after its jump is still served (`status: jumped`) while the books drop off, with a note. Every plan, Free included. 3 credits, charged after the race is found; a race we cannot find, an unparseable parameter and an unknown bookmaker key are free.
X-API-Key
3 credits
curl 'https://api.puntersedge.online/v1/racing/consensus' \
-H 'X-API-Key: YOUR_KEY'
| Name | In | Type | Required | Description |
|---|---|---|---|---|
race_id |
query | string | no | The race_id from /v1/racing/next-to-go or /events. The primary form; use venue + meeting_date + race_number only when you do not hold it. |
venue |
query | string | no | Venue name as the feeds spell it (case-insensitive). Needs meeting_date and race_number. |
meeting_date |
query | string | no | AET meeting date, YYYY-MM-DD. |
race_number |
query | string | no | |
method |
query | string | no | How each book's margin is removed before the books are combined. `power` (default) raises the implied probabilities to the exponent that makes them sum to one, which takes more margin off the longshots and is calibrated across the price range. `multiplicative` divides them by their sum and reproduces from the published overround. |
bookmakers |
query | string | no | Comma-separated bookmaker keys to build the consensus from. Omit for every visible book. |
min_books |
query | integer | no | Included books a runner needs before it carries a consensus probability. |
include_stale |
query | boolean | no | Count books whose quote is older than their refresh-tier threshold. Off by default; they are listed under books_excluded with reason stale. |
Status codes: 200, 401, 402, 404, 409, 422, 429, 500. Response bodies are JSON; the full schema is in /openapi.json.
200 application/json
— this call costs 3 credits.
Field names and types are as the API returns them; values are a real sample, trimmed to a few items.
{
"race": {
"race_id": "2e33fc28-13a5-421b-a6f3-6f99283c5cb6",
"venue": "Menangle",
"race_number": 2,
"category": "harness",
"country": "AU",
"start_time": "2026-10-06T01:43:00Z",
"secs_to_jump": 771,
"refresh_tier": "live",
"status": "upcoming",
"as_of": "2026-10-06T01:30:08Z"
},
"method": {
"normalisation": "power",
"aggregation": "median_probability",
"renormalised": true,
"stale_rule": "refresh_tier",
"field_rule": "complete_field",
"min_books": 2,
"include_stale": false
},
"books_quoting": 13,
"books_in_consensus": 13,
"books_excluded": [],
"bookmakers": [
{
"bookmaker": "betdeluxe",
"runners_priced": 10,
"overround_pct": 135.08,
"age_seconds": 35,
"last_update": "2026-10-06T01:29:32.828100Z",
"refresh_tier": "live",
"stale": false,
"included": true,
"reason": null
},
{
"bookmaker": "betgold",
"runners_priced": 10,
"overround_pct": 143.72,
"age_seconds": 35,
"last_update": "2026-10-06T01:29:33.273348Z",
"refresh_tier": "live",
"stale": false,
"included": true,
"reason": null
},
{
"bookmaker": "betr_au",
"runners_priced": 10,
"overround_pct": 139.49,
"age_seconds": 48,
"last_update": "2026-10-06T01:29:20.528874Z",
"refresh_tier": "live",
"stale": false,
"included": true,
"reason": null
},
{
"bookmaker": "betright",
"runners_priced": 10,
"overround_pct": 129.01,
"age_seconds": 37,
"last_update": "2026-10-06T01:29:31.051977Z",
"refresh_tier": "live",
"stale": false,
"included": true,
"reason": null
}
],
"market": {
"median_overround_pct": 137.08,
"best_price_market_pct": 124.44,
"median_runner_spread_pct": 20.33,
"consensus_sum_before_normalisation": 0.9933,
"field_size": 10
},
"runners": [
{
"number": 10,
"name": "Alpha Stride",
"runner_ref": null,
"horse_ref": null,
"scratched": false,
"books": 13,
"consensus_prob": 0.2652,
"consensus_price": 3.77,
"median_price": 3.1,
"mean_price": 3.14,
"best_price": 3.6,
"best_bookmaker": "playup",
"worst_price": 2.9,
"price_spread_pct": 24.14,
"best_vs_consensus_pct": -4.54,
"freshest_age_seconds": 32,
"oldest_age_seconds": 168,
"quotes": [
{
"bookmaker": "playup",
"price": 3.6,
"no_vig_prob": 0.2213,
"age_seconds": 168
},
{
"bookmaker": "ladbrokes_au",
"price": 3.3,
"no_vig_prob": 0.2508,
"age_seconds": 36
},
{
"bookmaker": "neds",
"price": 3.3,
"no_vig_prob": 0.2508,
"age_seconds": 36
}
]
},
{
"number": 2,
"name": "Call Me Jack",
"runner_ref": null,
"horse_ref": null,
"scratched": false,
"books": 13,
"consensus_prob": 0.2021,
"consensus_price": 4.95,
"median_price": 3.8,
"mean_price": 3.88,
"best_price": 4.0,
"best_bookmaker": "betright",
"worst_price": 3.7,
"price_spread_pct": 8.11,
"best_vs_consensus_pct": -19.17,
"freshest_age_seconds": 32,
"oldest_age_seconds": 168,
"quotes": [
{
"bookmaker": "betright",
"price": 4.0,
"no_vig_prob": 0.2053,
"age_seconds": 37
},
{
"bookmaker": "ladbrokes_au",
"price": 4.0,
"no_vig_prob": 0.2007,
"age_seconds": 36
},
{
"bookmaker": "neds",
"price": 4.0,
"no_vig_prob": 0.2007,
"age_seconds": 36
}
]
},
{
"number": 1,
"name": "Bertinelli",
"runner_ref": null,
"horse_ref": null,
"scratched": false,
"books": 13,
"consensus_prob": 0.149,
"consensus_price": 6.71,
"median_price": 5.0,
"mean_price": 5.04,
"best_price": 5.5,
"best_bookmaker": "betright",
"worst_price": 5.0,
"price_spread_pct": 10.0,
"best_vs_consensus_pct": -18.08,
"freshest_age_seconds": 32,
"oldest_age_seconds": 168,
"quotes": [
{
"bookmaker": "betright",
"price": 5.5,
"no_vig_prob": 0.1427,
"age_seconds": 37
},
{
"bookmaker": "betdeluxe",
"price": 5.0,
"no_vig_prob": 0.152,
"age_seconds": 35
},
{
"bookmaker": "betgold",
"price": 5.0,
"no_vig_prob": 0.1416,
"age_seconds": 35
}
]
}
],
"excluded_quotes": [],
"scratchings": [],
"note": null
}
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