Racing

Races and prices that changed since a timestamp

Cost: 2 credits. Everything that moved on the live racing board since since — one poll instead of re-downloading the whole card to diff it yourself. Never cached; serving a cached delta would hand you changes you already have and hide the ones you do not.

Poll contract. Call with a recent since, then send the server_time from each response as the since of the next call. server_time is deliberately set back 30 seconds from the server clock: ingest stamps a price change in Python and commits the batch afterwards, so a cursor taken at the exact clock would step over a write that was in flight and lose that price change permanently. The setback covers two racing poll cycles, which makes this feed AT LEAST ONCE — a change may arrive twice, never zero times. Deduplicate on (race_id, name, bookmaker_key, updated_at); that tuple is stable across a redelivery. Measured over two live polls 75 seconds apart, 2026-09-01: 487 of 530 runner deltas on the second poll were new and 43 were the deliberate replay.

since older than 30 minutes returns 422 and is not charged. The live board holds upcoming races only and purges a race once it jumps, so a gap wider than the board's own lifetime cannot be served from it. Do a full fetch from /v1/racing/next-to-go and resume polling here from the server_time it hands back.

What drives changed_runners. race_runners.updated_at, which is a genuine last-price-CHANGE stamp, not a poll stamp. Ingest rewrites every runner row on every poll but carries the previous timestamp forward when the win and lay prices are unchanged, so a steady price does not appear here. Measured on the live board 2026-09-01: 97.4% of runner rows were re-polled inside 2 minutes while 22.4% had an updated_at inside the same window. A move in place_price alone does not advance the column and is delivered on that runner's next win-price move — poll /v1/racing/next-to-go instead if place prices are your primary signal.

A race with an empty changed_runners is expected. A race is also returned when its own row was written since since, and that row's timestamp advances on Betfair market volume as well as on real state changes, so the race-level arm is a superset. On the first live poll of this endpoint, 46 of 61 races arrived on that arm alone. Read each race object as the CURRENT state of the race and compare against what you hold: scratchings is always the complete current list for the race, never a delta, so a runner leaving it is visible as an absence.

status is not a state signal and is documented as such on the response model: it is written once at row creation and never revised while a race is upcoming, so it reads "open" for every race here.

market_status, inplay and the market_closed_at / inplay_at lifecycle stamps are gated on WHICH FEED reported the state, the same rule /v1/racing/events applies. PointsBet closes its book at the real jump and its state is served to everybody; Betfair Exchange state is withheld from customer responses pending a data licence, so a race whose state only the exchange saw reads null for customers. market_state_source names the feed behind the values you receive. The race-level facts a customer can act on are scratchings, a revised start_time, and the lifecycle stamps.

Betfair prices are withheld from customer keys here exactly as they are on every other racing endpoint, so a Betfair-only price move produces no changed_runners entry.

Betslip links, Plus and above (since 21 September 2026). A changed_runners entry from a book with a runner-level betslip route (Unibet and TAB on the web, Ladbrokes and Neds in their apps) also carries betslip_win_url, betslip_place_url and betslip_app_only - the same fields, the same per-book rules and the same daily check as /v1/racing/next-to-go - so a poller holds the move and the link that acts on it in one object. The fields are absent on every other book, and on plans below Plus (a Standard or Hobby key issued before that date keeps them). source_url is not carried here; take it from next-to-go. With link templates stored through PUT /v1/link-templates (since 2026-10-14) the entry also carries betslip_win_link / betslip_place_link: the same URLs built through your templates, absent where there is nothing to link.

race_id is the same stable identifier used by /v1/racing/next-to-go, /v1/racing/price-history and /v1/racing/results.

get/v1/racing/changes

Request

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

Query parameters

sincestring required

ISO 8601 UTC timestamp, e.g. 2026-09-01T05:06:38Z. On your first call use a recent timestamp; after that send back the server_time from the previous response. Older than 30 minutes is refused.

categoriesstring

horse, greyhound, harness — comma-separated. Omit for all three. An unknown value is refused rather than silently ignored.

categorystring

Alias of categories. Both spellings are accepted on every racing endpoint. An unknown value is a free 422 naming the valid ones.

countrystring

ISO country codes, comma-separated (e.g. AU or AU,NZ). Omit for every country. Races whose country is not resolved yet carry country=null and are EXCLUDED by any value here — pair with include_unresolved=true or you will lose most of an Australian card.

include_unresolvedboolean

Include races whose country is not resolved yet (country is null). OFF by default, matching /v1/racing/events. 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 almost nothing.

Headers

X-API-Keystring
X-RapidAPI-Proxy-Secretstring

Response

Successful Response

server_timestring required

Send this back as since on your next poll. Deliberately set back 30 seconds from the server clock so no in-flight write is skipped — the feed is at-least-once, not exactly-once.

Changes

No recorded changes to this operation across all 1 revision of this API.