---
title: "Track condition, weather and rail: current state and today's change log"
method: GET
path: "/v1/racing/track-conditions"
tags: ["Racing"]
---

# Track condition, weather and rail: current state and today's change log

`GET /v1/racing/track-conditions`

## Request

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

Cost: 1 credit. Per meeting: the CURRENT official track condition, weather and rail
position (Racing Australia, AU thoroughbred meetings, refreshed every two hours) and the
day's CHANGE LOG — every transition we observed, with the previous value beside the new
one, when it was seen and which feed saw it.

**Two kinds of change row, told apart by `source`.** `racing_australia` rows come from
RA's own meeting page, so they are the official rating and carry the rail; bookmaker
rows (`betright`, `ladbrokes_au`, ...) come from a book's race page moving between two
of our polls and carry `race_id`. A book row can land minutes before RA re-publishes,
and a book's spelling ("Soft (6)") differs from RA's ("Soft 6"); the log compares
normalised values per feed, so the same rating spelled two ways never appears as a
change, and a book that merely lags another produces no row.

**Baselines.** The first RA row for a meeting has every `prev_*` null: it is the state
when we first read the page (about two days out), not a change. Bookmaker rows are only
ever written for a genuine transition, so they always carry a `prev_track_condition`.

**Coverage.** Official state is AU thoroughbred only — RA publishes nothing for
greyhounds or harness, so those meetings appear here only when a book reported a
change, with `current` labelled by that book. New Zealand meetings likewise.

**One entry per meeting, whatever each feed calls it** (2026-09-10). RA welds the
sponsor into its own venue key ('Picklebet Park Warwick') and the books send the bare
town ('Warwick'); both spellings are folded
onto one meeting by venue_id, by the sponsor word-subset rule, and (for a course inside
a parent racecourse, e.g. the books' 'Randwick' against RA's 'Kensington') by
venue_site, each fold refusing to act unless exactly one candidate meeting qualifies
and every change row on the bookmaker key is positively categorised thoroughbred.
`merged_from` lists every raw venue_norm folded in and `official_venue_norm` names RA's
spelling, so a join on either old string still finds the meeting, and `venue` selects it
by any of them. A merged entry is published under RA's spelling, because a bookmaker's
can be ambiguous about which course of a complex ran ('sandown' for a Sandown HILLSIDE
card) and `venue_id` must never name the wrong track; where RA's spelling is not yet a
venue-registry entry its `venue_id` is that spelling's slug
('picklebet-park-warwick'), which is unmapped rather than wrong.

**Mixed `current`.** `current` is built field by field: RA's official block wherever RA
published a value, a book filling only a field RA left null, and RA's own later row
superseding either. `current.sources` names the feed behind each value field and
`current.field_as_of` when it was observed; the block-level `source` reads `mixed` when
more than one feed is behind the block, and the block-level `as_of` is the OLDEST of
the per-field instants, so neither overstates what the block knows. `changes` keeps
every row from both feeds in one sequence.

**`going` / `going_rating`** sit beside `track_condition` (and `prev_going` /
`prev_going_rating` beside `prev_track_condition`) as the same vocabulary
/v1/racing/horses/form publishes, so 'Soft 6', 'Soft (6)' and 'Good4' need one parser
and not three. The raw string is unchanged.

The live endpoints carry the same signal per race as `track_condition_changed_at` — the
instant of the latest logged transition for that race's meeting — so a poller reading
/v1/racing/changes need not call this to know something moved; call this for the
sequence.

**How far back** (changed 2026-09-29). Any day from `coverage_from` — the first meeting day
the store holds, read from the tables on every call — up to three days ahead. Two floors sit
under it and the response names both: the change log reaches back to 2026-09-07, while
Racing Australia's own meeting block (`official_block_from`) is held only from 2026-09-26,
because until 2026-09-29 it was deleted three days after each meeting; it is kept
permanently since. On a day between the two, `current` and `changes` come from the log
alone — RA did publish that day, its block is simply gone. A `date` before `coverage_from`
is a free 422 whose detail names the floor, and so is a malformed one.

## Query parameters

- `date` string, nullable: Meeting day, Australia/Sydney, YYYY-MM-DD. Defaults to today. Any day from the store's first day (`coverage_from` in the response; an earlier date is a free 422 naming it) up to 3 days ahead.
- `venue` string, nullable: One meeting, case-insensitive ('Belmont', 'sandown hillside'). Omit for every meeting on the day.

## Headers

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

## Response `200`

Successful Response

- TrackConditionsOut
  - `date` string, required: Meeting day, Australia/Sydney calendar date.
  - `meetings` object[], required
  - `note` string, required
  - `coverage_from` string, nullable: The first meeting day (Australia/Sydney) this store holds anything for -- the earlier of the change log's first day and Racing Australia's meeting block's first day, computed from the tables on every call, never a constant. Any `date` from here up to three days ahead is served; an earlier one is a free 422 naming this same day. Null only if the store could not be read, and then the pre-2026-09-29 rule (7 days back) applies.
  - `official_block_from` string, nullable: The first meeting day Racing Australia's own meeting block (the `racing_australia` values in `current`: rail, track type, penetrometer, the official rating) is held for. Earlier than this the change log alone answers: until 2026-09-29 the block was deleted three days after each meeting, so a day before this date is NOT one on which RA published nothing -- its block is simply gone. Computed like coverage_from.

## 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
- `422`: Validation Error
- `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)
