---
title: "The market consensus on one race: margin-free probability per runner"
method: GET
path: "/v1/racing/consensus"
tags: ["Racing"]
---

# The market consensus on one race: margin-free probability per runner

`GET /v1/racing/consensus`

## Request

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

Cost: 3 credits. 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`, with
`country` when two meetings share the name on one date (Ascot in Perth and in Berkshire
on 2026-10-17); two races still sharing those answer 409 `race_ambiguous`, naming each,
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, at most 1.25 (a book that needs more, usually
one with a very short favourite, has the rest divided out in proportion), and
`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.

## Query parameters

- `race_id` string, nullable: 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` string, nullable: Venue name as the feeds spell it (case-insensitive). Needs meeting_date and race_number.
- `meeting_date` string, date, nullable: AET meeting date, YYYY-MM-DD.
- `race_number` integer, nullable
- `country` string, nullable: Alpha-2 country of the meeting (AU, NZ, GB, IE, ZA, ...). Only needed with venue when two meetings share the name on one AET date — Ascot (Perth) and Ascot (Berkshire) on 17 October 2026 — and the 409 names each candidate's country.
- `method` string: 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, at most 1.25 (past it the rest of the margin is divided out in proportion), 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` string, nullable: Comma-separated bookmaker keys to build the consensus from. Omit for every visible book.
- `min_books` integer: Included books a runner needs before it carries a consensus probability.
- `include_stale` boolean: Count books whose quote is older than their refresh-tier threshold. Off by default; they are listed under books_excluded with reason stale.

## Headers

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

## Response `200`

Successful Response

- ConsensusOut
  - `race` ConsensusRaceOut, required
    - `race_id` string, required
    - `venue` string, nullable
    - `race_number` integer, nullable
    - `category` string, nullable
    - `country` string, nullable
    - `start_time` string, nullable
    - `secs_to_jump` integer, nullable: Negative once the race has jumped.
    - `refresh_tier` string: The race's own refresh rule — live inside three hours of the jump, card beyond it. Each book's tier is on its own entry.
    - `status` string: upcoming · jumped.
    - `as_of` string, required
  - `method` ConsensusMethodOut, required
    - `normalisation` string: power (the default) or multiplicative.
    - `aggregation` string
    - `renormalised` boolean
    - `stale_rule` string
    - `field_rule` string
    - `min_books` integer
    - `include_stale` boolean
  - `books_quoting` integer, required
  - `books_in_consensus` integer, required
  - `books_excluded` ConsensusExclusionOut[]
    - `bookmaker` string, required
    - `reason` string, required
    - `age_seconds` integer, nullable
    - `runners_priced` integer, nullable
  - `bookmakers` ConsensusBookOut[]
    - `bookmaker` string, required
    - `runners_priced` integer, required
    - `overround_pct` number, nullable: Σ 1/price × 100 over the runners this book priced; null below two.
    - `age_seconds` integer, nullable
    - `last_update` string, nullable
    - `refresh_tier` string
    - `stale` boolean
    - `included` boolean, required: Whether this book entered the consensus.
    - `reason` string, nullable: Why not: stale · partial_field · single_runner.
  - `market` ConsensusMarketOut, required
    - `median_overround_pct` number, nullable
    - `best_price_market_pct` number, nullable: Σ 1/best_price × 100 over the runners with a consensus. Under 100 means the best prices across the included books beat the field.
    - `median_runner_spread_pct` number, nullable
    - `consensus_sum_before_normalisation` number, nullable: The sum of the per-runner medians before the field was scaled to one. Close to 1.0 when the books agree on the field; printed so the scaling is visible rather than silent.
    - `field_size` integer, nullable: Runners a complete-field book prices; books quoting fewer are excluded as partial_field.
  - `runners` ConsensusRunnerOut[]
    - `number` integer, nullable: Saddlecloth (box for greyhounds), voted across the books.
    - `name` string, nullable
    - `runner_ref` string, nullable: Registry id where known: ra:<code> for a thoroughbred (Racing Australia's code, new each day), grv:/grsa: for a dog.
    - `horse_ref` string, nullable: pe:<name>, the stable thoroughbred identity. Null off AU thoroughbreds.
    - `scratched` boolean
    - `books` integer: Included books quoting this runner.
    - `consensus_prob` number, nullable: The market's implied probability for this runner after margin is removed from every included book and the books are combined by median, renormalised so the field sums to one. Null when fewer than `min_books` included books quote the runner. It describes the market, not the outcome.
    - `consensus_price` number, nullable: 1 / consensus_prob.
    - `median_price` number, nullable
    - `mean_price` number, nullable
    - `best_price` number, nullable: Highest price among the included books.
    - `best_bookmaker` string, nullable
    - `worst_price` number, nullable
    - `price_spread_pct` number, nullable: best / worst - 1, in percent.
    - `best_vs_consensus_pct` number, nullable: best_price / consensus_price - 1, in percent. Negative is normal: every quoted price carries margin, so the best quote usually sits below the market'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. Not a value judgement.
    - `freshest_age_seconds` integer, nullable
    - `oldest_age_seconds` integer, nullable
    - `quotes` ConsensusQuoteOut[]
      - `bookmaker` string, required
      - `price` number, required
      - `no_vig_prob` number, nullable: This book's probability for the runner after its own margin is removed by the stated method.
      - `age_seconds` integer, nullable
  - `excluded_quotes` object[]: Placeholder prices left out of the consensus quote by quote, on the rule /v1/racing/best-odds uses: a long price at least four times the median of three or more other books on the same runner.
  - `scratchings` object[]
  - `note` string, 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
- `404`: No race on the live board matched. Nothing was charged.
- `409`: Two races share that venue, date and number (a venue split); the body names each one. Pass country= or race_id. Nothing was charged.
- `422`: Unparseable parameter or unknown bookmaker key. Nothing was charged.
- `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)
