API reference / Racing
GET /v1/racing/acceptancesFull-day AU thoroughbred acceptance card: every meeting, race and runner
The day's complete AU thoroughbred card as Racing Australia publishes it — meeting, then race, then runners in saddlecloth order — with no dependency on any bookmaker having framed a market. Per runner: saddlecloth number, barrier, jockey, trainer, weight, the stable `runner_ref`, Last-10 figures, Hcp Rating, the trainer/raceentry codes, the `emergency` flag, and `scratched` plus a `scratching` block (RA's own scratch time, late flag, "(Eliminated)" marker and note) for a runner already off. Per race: official name, local start time, distance, the conditions line verbatim, class, prize total and per-place breakdown, bonus note, track name/type, field limit, and the counts. Per meeting: rail, track type, condition — as RA's raw `track_condition` and, parsed from it, `going` plus `going_rating`, the same pair /v1/racing/horses/form publishes — weather, penetrometer, its own `as_of`, `emergency_data`, `scratching_data` and `withheld_acceptance_data`. **No prices, on purpose.** This is the entry list, not a market. It is available about two days out, which is the point of it — /v1/racing/next-to-go and /v1/racing/best-odds carry prices when books open, and this endpoint stays readable long before they do. **Scratched runners are flagged, never removed.** They keep their place in the card so it can be reconciled line-for-line against RA's official sheet. **`emergency` is true, false or null.** Null means the meeting was scraped before the emergency parser went live (2026-09-08 01:01:39 UTC) — its emergency acceptances are absent from the source table entirely, so "false" would be a fabrication. The meeting says which case it is in `emergency_data`: "parsed" or "unknown". **`scratched` is true, false or null too, for the same reason.** RA's scratching sheet does not exist for every date in the window — measured 2026-09-08, 2026-09-05 and 2026-09-06 have no scratching row at all while 09-07 has 96 and 09-08 has 77 — and the scraper fetches each meeting's sheet in its own try/except, so one meeting can lose its sheet while its acceptances still write. A row from the sheet is the only positive evidence it was read, so each meeting publishes `scratching_data`: * `"parsed"` — the sheet was read for this meeting; `scratched` is a real boolean and the counts resting on it are real numbers. `scratching_sheet_as_of` says when. * `"unknown_date"` — no meeting on this date has a scratching row. No sheet exists for the day; `scratched` is null everywhere on it. * `"unknown_meeting"` — other meetings on this date have a sheet and this one does not: either nobody was scratched here or this meeting's fetch failed, and nothing on this card tells them apart, so `scratched` is null. Do not read a null as "not scratched", and do not read `scratching_data` other than "parsed" as "clean meeting". **The counts name their population.** An emergency acceptance runs only if it is PROMOTED and RA does not publish whether it was, so a count that mixes emergencies with declared runners overstates the field. Every level (race, meeting, day) carries: `runner_count` (rows held, scratched and emergencies included — a floor wherever `runner_set_status` is not "checks_passed"), `emergency_count`, `declared_count` (runner_count minus emergency_count), `scratched_count`, `unscratched_count` (runner_count minus scratched_count, a reconciliation figure that STILL includes emergencies), `declared_scratched_count`, and `declared_remaining_count` — the horses expected to jump barring a promotion, which is the number to render as the field. Worked example, Moe R1 on 2026-09-08: 17 acceptances, 5 emergencies (#1, #14-#17), 12 declared, 6 scratchings all of declared runners — 11 rows are unscratched but only 6 declared horses remain. Anything resting on an unknown fact is null, not a guess, and the day totals are null unless every meeting with rows knows the fact. With NO meeting on the card publishing acceptances there is nothing to total: runner_count is a real 0 and every other day count is null, matching what those meetings each report, and `counts_cover_every_published_meeting` is false. **On a race that is not "checks_passed", every count is a FLOOR.** All seven are computed over the acceptance rows this card HOLDS, and such a race is one the card has itself declared short. Two of them looked exact and were not. `scratched_count` and `declared_scratched_count` count sheet rows MATCHED to an acceptance row, so a scratching with no row here is in `missing_runners` and not in the count: Townsville R6 on 2026-09-08 reads `scratching_data: "parsed"`, `scratched_count: 0`, while RA's sheet lists #4 Five Star King and #9 Kickoff — at least 2. And `declared_remaining_count` is the number to render as the field ONLY on a "checks_passed" race: Townsville R5 that day is "gap_detected" with saddlecloth #6 unaccounted for and NOT on the sheet — a horse that may well be jumping — so its 11 is "at least 11", never an exact field. Each such race's `runner_set_note` states the bound explicitly. **Completeness is cross-checked, and the note says what was OBSERVED.** ra_race_fields is keyed (meeting_date_aet, runner_key) without a race number, so a horse accepted in two races on one Sydney day has ONE slot — and since 2026-09-08 the acceptance pass gives that slot to NEITHER race rather than publish one race's barrier and weight against the other race's runner. It does not "survive in only one", which is what this documentation said until 2026-09-10 and which cost a caller every one of the 14 horses / 29 slots withheld on that date alone. **Do not de-duplicate by `runner_key` to recover them — there is nothing to de-duplicate.** Read `missing_runners` instead: every withheld slot is named there, in its own race, with its REAL saddlecloth number, `source` `"withheld_acceptance"`, and a `cause_detail` naming the slot published in its place where one was. Each race is checked three ways — for saddlecloth holes, against the independently-keyed scratching sheet where one exists, and against the withheld slots the scraper recorded as it discarded them — then reports `runner_set_status` ("checks_passed", "gap_detected" or "unverified"), a `runner_set_note` and any `missing_runners`. Each missing runner carries the cause actually observed for it — `duplicate_runner_key` (the collision is visible, not assumed: the same runner_key is held elsewhere on this card, or the scraper recorded discarding the slot), `declared_preferred` (a declared acceptance elsewhere on the day won the key and this race's emergency slot was dropped — nothing collided, so do not hunt a second race), `eliminated_emergency` (RA's sheet marks it an emergency and no acceptance row exists) or `not_determinable` — plus a `cause_detail` naming the observation. The endpoint does not guess which of several indistinguishable causes applies. **The withheld check is the only one that can see a loss above the top of the numbering**, which is exactly where Racing Australia numbers its emergencies: drop #13e from a 1..14 field and 1..12 is perfectly contiguous. Measured 2026-09-10, that silence certified Warwick R2 and Bunbury R2 as "checks_passed" with `emergency_count` 0 while RA listed 14 and 13 entries against the 12 served each — false statements under a schema that calls `declared_remaining_count` exact on a checks_passed race. Each meeting publishes `withheld_acceptance_data`: `"parsed"` (this card placed this meeting's withheld rows, so its discards are accounted for below), `"unplaced"` (the pass recorded discards here and this card could NOT place a single one — the meeting's acceptance fields are unpublished, or the race the row names is not on this card; `withheld_acceptance_slots_unplaced` counts them and `completeness.withheld_acceptance_slots_unplaced` totals the card) or `"unknown"` (no row at all — either nothing was withheld, or the pass that read this meeting predates the record). A row is the only positive evidence, so do NOT read `"unknown"` as "nothing was lost"; `completeness.withheld_acceptances` says whether the whole date holds any record at all. Every `missing_runners` entry carries `withheld_acceptance` (true/false) — the provenance of the loss, and the field `completeness.withheld_acceptance_slots` counts. Read THAT, not `source`: one slot can be named by RA's scratching sheet AND recorded in ra_withheld_acceptances, and it is listed once, keeping the sheet's `source` (the sheet saw the scratching) while carrying the withheld cause and `withheld_acceptance: true`. A withheld slot whose horse this card already holds in that SAME race under another saddlecloth is not listed at all: RA renders one horse twice in one race under two saddlecloths, only one row can be published, and no runner is missing from the race. **A past-date card is a snapshot and will never complete.** `date_status` is "past", "today" or "future". The scraper only ever fetches today-and-forward Form keys, so on a PAST date: a meeting at `acceptances_published: false` will NEVER fill in (live on 2026-09-05: sportsbet bundaberg and sportsbet mareeba) — calling again cannot help, and the meeting's `publication_note` says so; and the scratchings are frozen at the last scrape of that day, roughly midday race-day, with no later movement recoverable from anywhere, /v1/racing/changes included. On "today" or "future" the next scrape CAN add both, every two hours — but that is not a promise that the card converges, and this documentation used to make one. Racing Australia removes a meeting from home.aspx once it is UNDERWAY, and home.aspx is the scraper's only discovery surface, so a meeting's rows stop advancing around 12:15-14:15 AEST while its own races run on to 17:00-19:30. Log-verified 2026-09-09: the 02:15Z pass discovered all six of that day's meetings, the 04:15Z pass discovered one, the 06:15Z pass discovered none. The in-band signal is per meeting — its own `as_of` and `scratching_sheet_as_of`. A stamp that stops moving while later passes land on other meetings is a meeting that has gone underway, not a quiet one, and from that point `/v1/racing/changes` carries the race-day movement. **Why 2 credits.** A whole day is one request assembled from four reference tables: a midweek card is a few hundred runners over a handful of meetings and a Saturday well over a thousand across seventeen or more (measured: 372 runners / 4 meetings on Tuesday 2026-09-08; 1,259 / 17 on Saturday 2026-09-05). That is materially more than /v1/racing/track-conditions (1 credit, tens of rows) and is priced level with /v1/racing/next-to-go; it does no cross-book price assembly, so it is not a 3. The underlying scrape only moves every two hours — calling this once or twice a day is the intended shape, not polling it. **Coverage, honestly.** Australian THOROUGHBRED meetings only. Racing Australia publishes no acceptance card for greyhounds or harness, and none for New Zealand, so those meetings are absent here — not empty, absent. There is no history: the reference tables are pruned on a 3-day rule, so a date more than 3 days back is a free 422 rather than a truthful-looking empty card. **How current it is.** `as_of` is the newest RA read backing the meetings actually returned (a venue filter narrows it, so one stale meeting cannot borrow another's freshness), and each meeting carries its own plus `scratching_sheet_as_of`. A malformed or out-of-window `date`, and an unknown query parameter, are both a free 422 — nothing is charged.
X-API-Key
2 credits
curl 'https://api.puntersedge.online/v1/racing/acceptances' \
-H 'X-API-Key: YOUR_KEY'
| Name | In | Type | Required | Description |
|---|---|---|---|---|
date |
query | string | no | Meeting day, Australia/Sydney, YYYY-MM-DD. Defaults to today. Up to 3 days back and 3 days ahead. |
venue |
query | string | no | One meeting, case-insensitive ('Moe', 'sandown hillside'). Omit for every meeting on the day. |
Status codes: 200, 401, 402, 422, 429, 500. Response bodies are JSON; the full schema is in /openapi.json.
200 application/json
— this call costs 2 credits.
Field names and types are as the API returns them; values are a real sample, trimmed to a few items.
{
"as_of": "2026-09-08T02:16:38.306760Z",
"completeness": {
"counts_cover_every_published_meeting": true,
"known_missing_runners": 6,
"meetings_acceptances_unpublished": 0,
"meetings_emergency_data_unknown": 0,
"meetings_scratching_data_unknown": 0,
"meetings_scratching_sheet_absent_for_whole_date": 0,
"meetings_withheld_acceptance_data_unknown": 4,
"races_checks_passed": 24,
"races_gap_detected": 5,
"races_unverified": 0,
"runners_without_emergency_data": 0,
"runners_without_scratching_data": 0,
"withheld_acceptance_slots": 0,
"withheld_acceptance_slots_unplaced": 0,
"withheld_acceptances": "unknown_date"
},
"date": "2026-09-08",
"date_status": "today",
"declared_count": 334,
"declared_remaining_count": 272,
"declared_scratched_count": 62,
"emergency_count": 38,
"meeting_count": 4,
"meetings": [
{
"acceptances_published": true,
"as_of": "2026-09-08T02:16:38.306760Z",
"declared_count": 89,
"declared_remaining_count": 59,
"declared_scratched_count": 30,
"emergency_count": 18,
"emergency_data": "parsed",
"race_count": 7,
"races": [
{
"conditions": "Maiden, Set Weights, Three-Years-Old and Upwards, Apprentices can claim.",
"declared_count": 12,
"declared_remaining_count": 6,
"declared_scratched_count": 6,
"distance_m": 1108,
"emergency_count": 5,
"field_limit": "12 + 6 EM",
"missing_runners": [],
"prize_breakdown": {
"1st": 17600,
"2nd": 5760,
"3rd": 2880
},
"prize_total": 32000,
"race_class": "Maiden",
"race_name": "Moe Denture Clinic Maiden Plate",
"race_number": 1,
"runner_count": 17,
"runner_set_status": "checks_passed",
"runners": [
{
"barrier": 14,
"emergency": false,
"jockey": "Teo Nugent",
"last10": "2x3x4",
"number": 2,
"raceentry_code": "MzE2ODU1MTEwOTY",
"runner_key": "backupbarbie",
"runner_name": "Back Up Barbie",
"runner_ref": "ra:MTA3MjIxMjU2MzI",
"scratched": true,
"scratching": {
"emergency": false,
"late": false,
"scratched_at": "2026-09-07T21:29:00Z"
},
"trainer": "Ciaron Maher",
"trainer_code": "NDU4MDA0NzI",
"weight_kg": 57.5
}
],
"scratched_count": 6,
"start_time_local": "2:00PM",
"track_name": "Main",
"track_type": "Turf",
"unscratched_count": 11
}
],
"runner_count": 107,
"runner_set_status": "checks_passed",
"scratched_count": 34,
"scratching_data": "parsed",
"scratching_sheet_as_of": "2026-09-08T02:16:38.306760Z",
"track": {
"dual_track": "N",
"going": "Heavy",
"going_rating": 9,
"penetrometer": "5.96",
"rail": "Out 3m Entire Circuit",
"track_condition": "Heavy 9",
"track_info": "Inspection 5:30AM 8/9;",
"track_type": "Turf",
"weather": "Fine"
},
"unscratched_count": 73,
"venue": "Moe",
"venue_id": "moe",
"venue_norm": "moe",
"venue_site": "moe",
"withheld_acceptance_data": "unknown",
"withheld_acceptance_slots_unplaced": 0
}
],
"note": "…",
"race_count": 29,
"runner_count": 372,
"scratched_count": 72,
"unscratched_count": 300
}
GET /v1/racing/best-odds — Best racing price per runner across booksGET /v1/racing/changes — Races and prices that changed since a timestampGET /v1/racing/events — Upcoming race listGET /v1/racing/greyhounds/form — Greyhound form history for one dogGET /v1/racing/greyhounds/stats — Greyhound record by track, distance, box or gradePOST /v1/racing/horses/backfill — Queue a batch of horses for paced form collectionDELETE /v1/racing/horses/backfill/{job_id} — Cancel the horses a job has not collected yetGET /v1/racing/horses/backfill/{job_id} — Progress of a backfill jobThe free tier needs no credit card, and the sandbox endpoints need no key at all.
Get a free API key Quickstart