---
title: "Every runner in one race with its recent runs, from our own results"
method: GET
path: "/v1/racing/form"
tags: ["Racing"]
---

# Every runner in one race with its recent runs, from our own results

`GET /v1/racing/form`

## Request

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

Cost: 3 credits. Every runner in one race with its recent runs from our own settled AU
thoroughbred results, in one call: the field's form, not one horse's.

**Naming the race.** `race_id` from /v1/racing/next-to-go, /events, /acceptances or /results is
exact. Failing that, `venue` + `meeting_date` (the AET meeting date) + `race_number`. Only
thoroughbred races are matched, so a track that races gallops beside dogs or trots on the same day
is not ambiguous (from 13 October 2026); two thoroughbred races sharing those three answer 409
`race_ambiguous` and cost nothing. A race a bookmaker has not
framed yet has no race_id (every AU book frames a race 16 hours before its jump), so the evening
before a meeting name it by venue + meeting_date + race_number exactly as /v1/racing/acceptances
lists it: the answer comes from Racing Australia's acceptance card (`card_source: acceptances`,
`race_id` null), and the whole of tomorrow's card can be read the night before.

**The field.** While the race is open the runners come from the live card (`card_source: live`):
saddlecloth, barrier, jockey, trainer and allotted weight as the books and Racing Australia's
acceptances publish them, with a horse the card has since scratched kept in the field and marked
`scratched: true`. Once the race has been run and the live card purged, the field is the settled
result's (`card_source: results`). Until then Racing Australia's acceptance card is folded in
(`acceptances_folded`): every accepted runner, emergencies aside, is in the field even before the
books list it, named as RA names it, with RA's `last10` and `last_start_date`.

**The runs.** Under each runner, its newest `runs` runs (default 10, at most 20) out of the same
store /v1/racing/horses/runs reads -- our own collected results plus the history collected behind
them -- and a starts, wins and places summary over every run held. Each run carries date, venue,
race, distance, class, track condition, saddlecloth, barrier, position, margin, official SP,
weight, jockey, trainer and field size, and since 29 September 2026 the race's winning time and
last-600m time, its total prize money and the place money this runner won (the winner's
`time_s` is the race time; RA publishes no other runner's time). ONLY RUNS BEFORE THIS RACE'S START are included
(`form_as_at`), so a race already run answers with what was knowable at the jump and never with
its own result. A runner with no run held is still in the field, with `runs: []` and
`runs_total: 0`; `source.coverage_from` is the floor of the store, and nothing earlier exists here.
`runs_total` counts STARTS (`status: ran`), the same figure as `stats.starts`, and
`held_rows_total` counts every held row, scratchings included, which is what `runs` is drawn
from: a horse scratched before its debut reads `runs_total: 0` with its scratchings in `runs`.
Before 2026-10-13 `runs_total` counted the scratchings too.

**Identity.** Runners are gathered on `horse_ref` (`pe:<name>`), the fold every thoroughbred
surface publishes. `runner_ref` is the Racing Australia code the card carries for this runner,
on race day the join to /v1/racing/next-to-go, /v1/racing/acceptances and /v1/racing/results;
it is not a horse identity, because Racing Australia issues a new code for the same horse each day.

**British and Irish races (from 2026-10-12).** The same call answers a GB or IE thoroughbred race.
The field is the UK bookmakers' card as we hold it -- William Hill's and Betway's rows carry the
stall draw (`barrier`), rider, trainer, weight in kg, the form figures (`last10`), the horse's
`age` and, from William Hill, the Racing Post `rating` -- and, once run, Betway's settled result.
No acceptance card exists for these races, so `acceptances_folded` is always false. The runs are
our own GB/IE results from Betway's settlement feed since 6 October 2026: position, SP and field
size, no times or prize money, and a starter Betway never ranked once the result was final reads
`abnormal: did not finish`; `source.region` reads `GB-IE`, `source.coverage_from` is that
floor, and most horses hold few or no runs yet -- the record grows with every meeting we settle.
`meeting_date` and each run's `date` are the London calendar date, and when you name a GB/IE race
by venue + meeting_date + race_number, meeting_date is the London date and the course is matched
by words ("Kempton" finds "Kempton Park").

Not a career record: this is what we hold, honestly bounded, and it grows with every meeting we
collect. For Racing Australia's own form page (career splits, sectionals, price paths) use
/v1/racing/horses/form, one horse at a time. A race we cannot find is a free 404, a race we hold
no field for a free 404, an unknown query parameter a free 422. Thoroughbreds only; greyhounds
have /v1/racing/greyhounds/form. Cached 10 minutes; the AU store refreshes hourly, the GB/IE runs
are read live.

## Query parameters

- `race_id` string, nullable: The race_id from /v1/racing/next-to-go, /events, /acceptances or /results. 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
- `runs` integer: Most recent runs to return per runner. The stats cover every held run regardless.

## Headers

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

## Response `200`

Successful Response

- FieldFormOut
  - `race_id` string, nullable: Our race_id. Null on an acceptance-only answer (`card_source: acceptances`): the race is on Racing Australia's card but no bookmaker has framed it yet, and every AU book frames a race 16 hours before its jump.
  - `venue` string, nullable
  - `race_number` integer, nullable
  - `start_time` string, nullable: Scheduled start, UTC. Null on an acceptance-only answer; see `start_time_local`.
  - `start_time_local` string, nullable: Racing Australia's published start on the venue's own clock ('1:35PM'). Set on an acceptance-only answer.
  - `meeting_date` string, nullable: Sydney calendar date of the meeting; the London date for a British or Irish race.
  - `race_name` string, nullable
  - `distance_m` integer, nullable
  - `track_condition` string, nullable: As the live card or the result carries it.
  - `race_status` string, nullable: The live race's status while it is open; the result's (`interim`/`final`) once it has been run.
  - `card_source` string, required: `live` -- the field came from the live card; `results` -- the race has been run, its live rows are gone, and the field is the settled result's; `acceptances` -- no bookmaker has framed the race yet (or no live field is held), and the field is Racing Australia's acceptance card.
  - `acceptances_folded` boolean: True when Racing Australia's acceptance card for this race was found and folded in: every accepted runner (emergencies aside) is in `runners`, named as RA names it.
  - `form_as_at` string, nullable: This race's start, UTC. Every run and every figure below is from BEFORE this instant, so a race already run answers with what was knowable at the jump and never with its own result. On an acceptance-only answer, Sydney midnight opening the meeting day.
  - `runs_per_runner` integer, required: The `runs` limit this answer was built with.
  - `runners_total` integer, required
  - `runners_with_runs` integer, required: Runners with at least one start held (`runs_total` of 1 or more). A runner whose only held rows are scratchings is not counted.
  - `runners` FieldRunner[], required
    - `number` integer, nullable: Saddlecloth number on this race's card.
    - `name` string, required: The horse's name as the card (or the result) spells it.
    - `horse_ref` string, nullable: `pe:<name>` -- the identity the runs were gathered on, the same value every thoroughbred surface publishes. Pass it to /v1/racing/horses/runs or /v1/racing/horses/form for the one-horse view.
    - `runner_ref` string, nullable: The Racing Australia code this runner carries on the live card or the result -- on race day the join to /v1/racing/next-to-go, /v1/racing/acceptances and /v1/racing/results. Not a horse identity: Racing Australia issues a new code for the same horse each day. Null when the card did not carry one.
    - `barrier` integer, nullable: Barrier drawn for THIS race, as the card publishes it (the stall draw on a British or Irish card).
    - `jockey` string, nullable: Rider declared for THIS race.
    - `trainer` string, nullable
    - `weight_kg` number, nullable: Weight allotted for THIS race, as the card publishes it; a British or Irish card's stones and pounds converted to kg, one decimal.
    - `age` integer, nullable: The horse's age as the card prints it. British and Irish cards (William Hill, Betway) publish it; the Australian books do not, so it is null on an AU race.
    - `rating` integer, nullable: The rating the card prints: on a British or Irish race the Racing Post rating as William Hill's card carries it. Null when no card we read publishes one (every AU race).
    - `scratched` boolean: True when the card has scratched the horse. It stays in the field so the runners match what the acceptances published.
    - `last10` string, nullable: Racing Australia's last-ten figures for the horse, as its acceptance card prints them; on a British or Irish race the form figures the bookmaker's card prints (William Hill, Betway). Null when no card lists this runner, or lists no figures.
    - `last_start_date` string, nullable: The horse's last start as Racing Australia's acceptance card gives it, YYYY-MM-DD, including starts older than `source.coverage_from`: a runner with `runs_total` 0 and a last start before that floor has history we do not hold (read it with /v1/racing/horses/form). Null with `last10` null: the card shows no previous start.
    - `runs_total` integer, required: STARTS we hold for this horse before this race's start: runs with `status` `ran`, the same count as `stats.starts`. A scratching is not a start. 0 is honest: no start held -- a first starter, or a horse whose starts predate `source.coverage_from` (on an Australian race `last_start_date` tells the two apart). Before 2026-10-13 this counted every held row, scratchings included, so a horse scratched before its debut read 1 or more: on 2026-10-12, 442 of the 31,163 horses in the store held scratchings and no start. That count is `held_rows_total`.
    - `held_rows_total` integer, required: Every row we hold for this horse before this race's start: its starts plus its scratchings (rows whose `status` is not `ran`). `runs` is drawn from these rows, so a horse with `runs_total` 0 can still carry `runs` entries -- its scratchings. Measured on 2026-10-12, 61,719 of the store's 282,639 rows (21.8%) were scratchings.
    - `stats` HorseRunsStats, required
      - `starts` integer, required: Runs with status `ran` — scratchings are excluded from every figure here. Equal to `runs_total` since 2026-10-13; `held_rows_total` counts the scratchings too, which is why it can be higher.
      - `wins` integer, required
      - `places` integer, required: Finishes in the first three, wins included.
      - `win_pct` number, required
      - `place_pct` number, required
    - `runs` HorseRunRow[], required: Newest first, at most `runs` of them, every one before this race's start. Scratchings are included and read `status: scratched`.
      - `date` string, nullable: Sydney calendar date of the race; the London date for a British or Irish race (/v1/racing/form, 2026-10-12).
      - `start_time` string, nullable: Scheduled start, UTC.
      - `venue` string, nullable
      - `race_number` integer, nullable
      - `distance_m` integer, nullable
      - `race_name` string, nullable
      - `track_condition` string, nullable: As published for the meeting, e.g. 'Good 4'.
      - `number` integer, nullable: Saddlecloth number.
      - `box` integer, nullable: Barrier. Null on a scratching — a runner that never jumped has no barrier.
      - `position` integer, nullable: Finishing place. Null when the horse did not start (see `status`) or started and did not finish (see `abnormal`).
      - `status` string, nullable: `ran` or `scratched`, exactly as /v1/racing/results publishes it.
      - `abnormal` string, nullable: Racing Australia's own Finish cell whenever it is not a number, passed through verbatim (Fell, PulledUp, TailedOff, LR, FF, LP, and 'SB' for scratched at the barrier). THERE IS NO CLOSED LIST — an unseen code reaches you unchanged. Read it with `status`.
      - `margin` string, nullable: Lengths behind the winner as RA printed it ('0.75L'); null where it prints none.
      - `time_s` number, nullable: The horse's own time in seconds, which RA publishes for the WINNER only: since 29 September 2026 a position-1 run carries the race time from RA's race header (the same value as `race_time_s`); every other run's is null, because RA's results table prints no per-runner time and a beaten horse's time is not the race time. Null on runs stored before then until that meeting is re-read.
      - `sp` number, nullable: Official starting price, decimal return on $1. Null = unknown, never zero.
      - `sp_note` string, nullable: RA's favouritism marker: 'F' favourite, 'EF' equal favourite; null otherwise.
      - `dead_heat` boolean
      - `trainer` string, nullable: Trainer of the day for THIS run, as the result published it.
      - `jockey` string, nullable: Rider of the day for THIS run.
      - `field_size` integer, nullable: Horses that ran in that race.
      - `weight_kg` number, nullable: Allotted weight in kg, as RA's results page prints it. Kept since 28 September 2026. On origin tabtouch runs, and on runs from 10 August to 27 September 2026 whose result lacked one, it is the weight TABtouch's race page printed for the starter. Null on a scratching and where neither source had one.
      - `carried_kg` number, nullable: Weight the horse actually carried, printed by RA only when it differs from the allotted weight ('58kg (cd 56kg)'): usually an apprentice's claim (under), sometimes a rider's overweight (over), so do not assume the sign. Null when RA printed none; read coalesce(carried_kg, weight_kg) for what was carried.
      - `race_class` string, nullable: The race's class as RA's own leading conditions term -- 'Maiden', 'BenchMark 64', 'Class 1', 'Group 1' -- mechanical, never inferred, on runs stored since 28 September 2026 and on origin history runs. On origin tabtouch runs, and on runs from 10 August to 27 September 2026, it is TABtouch's class line as printed -- 'BM58 HCP', 'MDN-SW', 'GROUP 1 3UP HCP' -- so normalise the two wordings before comparing classes across runs. Null where neither source had one.
      - `race_time_s` number, nullable: The race's winning time in seconds, from RA's race header ('Time: 0:58.69' -> 58.69). The same on every run of that race. Kept since 29 September 2026: null on a run stored before then until that meeting is re-read.
      - `last_600m_s` number, nullable: The race's last-600m time in seconds, from the same header ('Last 600m: 0:34.49' -> 34.49) -- the leader's closing sectional as RA times it, a RACE figure, not this horse's own. Kept since 29 September 2026.
      - `prize_total` integer, nullable: Total prize money of the race in AUD, from RA's prize line ('Of $30,000.'). Kept since 29 September 2026.
      - `prize_won` number, nullable: Place money this horse won in AUD, read off the same prize line by its finishing place ('1st $15,000, 2nd $5,300, ...'); dead-heaters share the places they tie for equally. 0 for a finish outside the places RA lists as paid; null for a horse with no finishing place (scratched, fell) or a race whose prize line was not read. Excludes bonuses and welfare-fund amounts. Kept since 29 September 2026.
      - `runner_ref` string, nullable: The Racing Australia code our results collector published for this run, from race day — the same value on the matching /v1/racing/results runner and in the archives for that race. Racing Australia issues a new code for the same horse each day, so it differs from run to run: group a horse's runs on `horse_ref`, not on this.
      - `horse_ref` string, nullable: The horse this run belongs to: `pe:<name>`, the registered name folded to letters and digits. Identical on every run of the same horse, and the same value /v1/racing/results runners and placings, the live board, the closing-line and price-path archives and /v1/racing/horses/form carry — the key that joins a horse across meetings, where `runner_ref` joins the rows about one race.
      - `result_id` string, nullable: Id of the /v1/racing/results row this run came from. A run with origin 'history' or 'tabtouch' carries a synthetic 'hist:...' id that joins to /v1/racing/results on nothing.
      - `origin` string, nullable: 'results' -- from our own settled results (/v1/racing/results). 'history' -- collected afterwards from Racing Australia's results page for a race held before our live results began: same fields, no /v1/racing/results row behind it. 'tabtouch' -- collected afterwards from TABtouch's public race page for a race older than Racing Australia's results pages reach (about fifteen months back): number, rider, barrier, weight, trainer and the first four placings, so position null there means unplaced; no margin, time or sp.
  - `source` RunsSourceBlock, required
    - `feed` string: Where the data comes from: our own collection of published AU thoroughbred results (Racing Australia's results pages, read hourly by our results collector), flattened to one row per horse per race and refreshed hourly. Not Racing Australia's form page — that is /v1/racing/horses/form.
    - `region` string
    - `sport` string
    - `coverage_from` string: The first complete card in the store. A horse's record here starts here — it is not a career.
    - `coverage_to` string, nullable: Sydney date of the latest race in the store.
    - `collectors` string[]: Which of our collectors can contribute a run. AU thoroughbred results come from one: the Racing Australia results pages.
    - `refreshed_hourly` boolean: The store is rebuilt from our own results at :59 each hour, after the results pass.

## 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 matched, or we hold no field for it. Nothing was charged.
- `409`: Two thoroughbred races share that venue, date and number (a venue split). Nothing was charged.
- `422`: The race was not named. 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)
