---
title: "Upcoming race list"
method: GET
path: "/v1/racing/events"
tags: ["Racing"]
---

# Upcoming race list

`GET /v1/racing/events`

## Request

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

Cost: 1 credit. The CARD - every race scheduled in the requested window, optionally
filtered by racing category and country. EVERY country is served by default, not only
AU and NZ — pass `country=AU` (pairing it with `include_unresolved=true`, per that
parameter) if you want an Australian card.

**How far ahead you can see (2026-09-22).** `hours_ahead` reaches 24h on Free and
Hobby, 48h on Standard, and 168h (7 days) on Plus and above. Every response carries
your ceiling as `X-Horizon-Hours` and the window actually used as
`X-Horizon-Used-Hours`, so you never have to discover it by trial. Asking past it is a
403 naming the tier that reaches further, and costs no credit -- it is never a silent
clamp to a shorter window. `date=YYYY-MM-DD` is the single-day alias for an Australian
Eastern meeting day; a day is reachable when its last moment is inside your horizon,
so it is bounded by the same ceiling rather than being a second entitlement.

Nothing lost anything here: 24h was the hard cap for every plan, free included, until
this landed, and Free and Hobby still have exactly that. AU cards carry
thoroughbred, harness and greyhound; NZ cards carry thoroughbred and harness.

Hong Kong (`country=HK`, feature_requests #29, 2026-09-09) is listed too, on the HKJC's
own nights — Happy Valley on Wednesday evenings and Sha Tin on Saturday or Sunday
afternoons, AEST — whenever an Australian bookmaker quotes the meeting. Thoroughbred
only, and PRICES ONLY on /v1/racing/next-to-go and /v1/racing/best-odds: a Hong Kong
race carries no results, form, race conditions or `runner_ref`, because those come from
Racing Australia and cover AU thoroughbreds only (`horse_ref` is derived from the
runner's name, so it is there). Foreign meetings other than Hong Kong
still appear here whenever a book in the panel lists them; this endpoint has never
filtered them out.

This is the schedule endpoint, and it lists a race before any book has priced it. Since
2026-09-01 the feed reads the early markets (Sportsbet's full card 3-12h out, BetRight's
early lane, a PointsBet wide sweep), so the FIRST price on a race now lands well ahead of
the jump: measured over the 48h to 2026-09-03 11:00 UTC, median 10.1h before the jump on AU
thoroughbreds (79 of 80 races priced 3h+ out), 5.3h on AU greyhounds, 6.8h on AU harness.
That first price is one or two books; the full board still fills inside about an hour of
the jump. A race listed here with no prices yet is one no book has opened -
/v1/racing/next-to-go carries the priced window, and the `race.odds_open` webhook fires
the moment a race's first prices land.

`scratchings` is served here as well as on /v1/racing/next-to-go, so a scratching is visible
from the moment a book reports it rather than only once the race enters the priced window.
Its entries are {name, number, barrier, scratched_at, late, emergency}, merged across
every reporting book — `scratched_at` (UTC) is the book's own stamp where it sends one
(Ladbrokes, Neds, BetRight) and Racing Australia's official scratching sheet otherwise
for AU thoroughbreds, read every two hours; `late` and `emergency` come from the same
sources. On greyhound
cards some entries are box-vacancy placeholders - a literal '....' or 'Vacant Box' - and
several books can report the same empty box separately, so count boxes by `number`, not by
list length.

`status` is written when the race row is created and never revised, so every race in this
window reads 'open'. `market_status` and `inplay` are the live betting-market state.

`market_closed_at` and `inplay_at` are the race lifecycle: when the market was first seen
shut, and when the race was first seen in-play — the actual off, where `start_time` is the
advertised one and moves. Both are POLL-OBSERVED: each is the first poll at which the state
was seen, so it lands up to one polling pass after it, and passes are most frequent in the
minutes before the jump. Both are SET ONCE, so a market
that flickers SUSPENDED -> OPEN -> SUSPENDED reports the first suspension. Null never means
the market stayed open. This endpoint serves upcoming races and the live row is purged 10
minutes after the jump; /v1/racing/results carries the same fields permanently.

**Who reports market state, and what you see.** Two feeds do. PointsBet closes its book at
the real jump — measured 2026-09-03 over 9 live races followed across their jump, 0.2 to
1.4 minutes AFTER the advertised start on AU/NZ cards — and that is what populates `market_closed_at` for most races. The
Betfair Exchange reports state while it quotes the market, which ends about two minutes
BEFORE the jump (measured 2026-09-01 on Horsham R3, advertised 05:24 UTC: betfair last seen
119s before the off), which is why these fields read null on every race before 2026-09-03.
Exchange-sourced market state is withheld from customer plans pending a data licence, so a
race whose state only the exchange saw reads null for customers; bookmaker-sourced state is
served to everybody. `market_state_source` names the feed behind the values you receive, and
is itself null whenever they are withheld.

`inplay_at` stays exchange-only: bookmakers do not run racing in-play, so a bookmaker feed
can say when it CLOSED its book but not when the race went. For a race the exchange has
stopped quoting, `market_closed_at` is the closest measured instant to the off.

## Query parameters

- `hours_ahead` integer: How many hours ahead to list, from now. Your ceiling depends on the plan and is returned on every response as X-Horizon-Hours: 24 on Free and Hobby, 48 on Standard, 168 (7 days) on Plus and above. Asking past your ceiling is a free 403 naming it, never a silent clamp. Mutually exclusive with date.
- `date` string, nullable: Single-day alias: date=YYYY-MM-DD lists that Australian Eastern meeting day instead of a rolling hours_ahead window. Mutually exclusive with hours_ahead. A day is reachable when its last moment is inside your horizon, so this is bounded by the same X-Horizon-Hours ceiling and is not a separate entitlement. Forward-looking only -- a past date is a free 422 pointing at /v1/racing/results and /v1/racing/closing-lines.
- `categories` string
- `category` string: Alias of `categories`. Both spellings are accepted on every racing endpoint. An unknown value is a free 422 naming the valid ones.
- `country` string: ISO country codes, comma-separated (e.g. AU, AU,NZ, GB,IE for the British and Irish card or ZA for the South African card). Omit for every country. HK lists Sha Tin and Happy Valley on the HKJC race nights (prices only, no results or form). Pair with include_unresolved=true or you will silently lose races whose meeting is not confirmed yet.
- `include_unresolved` boolean: Include races whose country is not resolved yet (country is null). OFF by default. Most of an Australian card is unresolved until the meeting is confirmed — 58.8% of horse races in a measured window — so country=AU alone can return far fewer races than are running, or none at all. Pass true to get them, and read `country` per race to see which are still unresolved.

## Headers

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

## Response `200`

Successful Response

- RaceEventOut[]
  - `race_id` string, required
  - `venue` string, nullable, required
  - `venue_id` string, nullable
  - `venue_canonical` string, nullable
  - `venue_site` string, nullable
  - `race_number` integer, nullable, required
  - `category` string, nullable, required
  - `start_time` string, required
  - `country` string, nullable, required
  - `race_name` string, nullable
  - `distance_m` integer, nullable
  - `track_condition` string, nullable
  - `weather` string, nullable
  - `race_class` string, nullable: The first term of RA's official conditions line ('BenchMark 64', 'Maiden', 'Group 1'). AU thoroughbred only.
  - `conditions` string, nullable: RA's official conditions line, verbatim.
  - `prize_total` integer, nullable: Total advertised prize, whole AUD.
  - `rail` string, nullable: Rail position for the meeting, as RA publishes it.
  - `track_name` string, nullable: RA's own name for the course this race runs on ('Hillside', 'Randwick', 'Main'). AU thoroughbred only; null elsewhere.
  - `track_condition_changed_at` string, nullable: UTC instant of the most recent logged track-condition, weather or rail transition for this race's meeting; null when nothing has changed since we began watching it. The sequence is on /v1/racing/track-conditions.
  - `status` string, nullable: Set when the race row is created and revised exactly once, if at all: to 'abandoned' when Racing Australia posts an official abandoned, postponed or transferred notice for the meeting (2026-09-06). Otherwise it reads 'open' for every race in this window. It is not a live market-state signal - /v1/racing/results carries the settled status (interim, final, abandoned).
  - `scratchings` object[]: Scratched runners for this race: {name, number, barrier, scratched_at, late, emergency}. `scratched_at` (UTC) is the book's own stamp where it sends one (Ladbrokes, Neds, BetRight) and Racing Australia's official scratching sheet otherwise (AU thoroughbreds, read two-hourly); absent when neither knows. `late` marks a late scratching, `emergency` an emergency that did not gain a start. Merged across every book that reports one. Greyhound cards also carry box-vacancy placeholders here (a literal '....' or 'Vacant Box'), which are the reporting book naming an empty box rather than a withdrawn runner; match on `number` and treat a name from that set as a vacant box.
  - `market_status` string, nullable: Betting-market state (OPEN, SUSPENDED, CLOSED). Reported by PointsBet, which sends CLOSED when it shuts its book at the jump, and by the Betfair Exchange, which is withheld from customer responses pending a data licence — so a value last written by the exchange reads null. Most races are quoted by both: the exchange holds the state until about two minutes before the jump, PointsBet reports the close.
  - `inplay` boolean, nullable: True once the market is in-play. Exchange-only — bookmakers do not run racing in-play — so it carries the same licence hold as `market_status` and reads null for customers.
  - `market_closed_at` string, nullable: UTC time at which the market was FIRST seen shut — the book stopped taking bets on this race. Poll-observed: this is the first poll that saw the state, not the exact instant, so it lands up to one polling pass after it, and passes are most frequent in the minutes before the jump. Set once: a market that flickers SUSPENDED -> OPEN -> SUSPENDED keeps the FIRST suspension. TWO SOURCES report market state. PointsBet closes its book at the real jump — measured 2026-09-03 over 9 live races followed across their jump, 0.2 to 1.4 minutes AFTER the advertised start on AU/NZ cards — and the Betfair Exchange reports it while it quotes the market, which is until about two minutes before the jump. `market_state_source` names the one that produced this value. Exchange-sourced stamps are withheld from customer plans, so a race seen shut only by the exchange reads null. Bookmaker-sourced stamps are served to everybody. Null also means no feed observed a transition — it never means the market stayed open.
  - `inplay_at` string, nullable: UTC time at which the race was FIRST seen in-play — the actual off, as opposed to `start_time`, which is the ADVERTISED off and moves. Same poll-observed, set-once semantics as `market_closed_at`, and withheld on the same source rule. Reported by the Betfair Exchange only: bookmakers do not run racing in-play, so a bookmaker feed says when it CLOSED its book, not when the race went. For a race the exchange has stopped quoting, `market_closed_at` from a bookmaker is the closest measured instant to the off.
  - `market_state_source` string, nullable: Which feed's market state produced `market_closed_at` / `inplay_at` on this race — 'pointsbetau' for the bookmaker, 'betfair_ex_au' for the exchange. Set once, alongside the first stamp. Null when no transition has been observed, and null for customer plans whenever the stamps themselves are withheld, so a non-null value here is always the provenance of values you can see.

## 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)
