Race Data API

Resulted Australian thoroughbred races with exchange odds before the start, starting prices and results.

Base URLhttps://racedata.prospec-au.com
FormatJSON, or CSV for the runners table
AuthenticationAPI key in the X-API-Key header
Limits60 requests per minute, 31 days per request

Overview

The API gives read-only access to resulted Australian thoroughbred races recorded from the Betfair Exchange. For every race you get:

A race is identified by its race id, which is the Betfair WIN market id, for example 1.264460108. All dates and times are UTC.

Quick start

Replace YOUR_KEY with the key you were given.

curl -H "X-API-Key: YOUR_KEY" "https://racedata.prospec-au.com/v1/coverage"
{"first_date": "2026-10-19", "last_date": "2026-10-29", "days": 11, "races": 408}

Then list a day's races, and request one race by its id:

curl -H "X-API-Key: YOUR_KEY" "https://racedata.prospec-au.com/v1/races?date=2026-10-27"
curl -H "X-API-Key: YOUR_KEY" "https://racedata.prospec-au.com/v1/races/1.264460108"

Or download a whole day as a spreadsheet-ready CSV file:

curl -H "X-API-Key: YOUR_KEY" -o races.csv "https://racedata.prospec-au.com/v1/runners?date=2026-10-27&format=csv"

Authentication

Send your key in the X-API-Key header with every request. Requests without a valid key get 401 Unauthorized.

Keep your key private. Do not put it in public code, websites or shared files. If you think someone else has it, ask for a new key; the old one can be turned off at once.

Endpoints

Coverage

GET/v1/coverage

The first and last dates that have resulted races, the number of days with data, and the total number of races. Use it to find out which dates you can request.

List races

GET/v1/races

A summary of each resulted race (no runners or odds), ordered by start time.

ParameterExampleDescription
date2026-10-27One day (UTC). Use either date, or from and to.
from2026-10-20First day of a range.
to2026-10-27Last day of a range (included). At most 31 days per request.
venueFlemingtonOptional. Only races at this venue (not case-sensitive).

With no dates, the endpoint returns yesterday's races.

{
  "from": "2026-10-27",
  "to": "2026-10-27",
  "count": 23,
  "races": [
    {
      "race_id": "1.264460108",
      "date": "2026-10-27",
      "start_utc": "2026-10-27T02:15:00",
      "venue": "Hawkesbury",
      "race": "R1 1100m Mdn",
      "race_no": 1,
      "distance_m": 1100,
      "class": "Mdn",
      "field_size": 10,
      "places_paid": 3,
      "win_market_id": "1.264460108",
      "place_market_id": "1.264460109"
    }
  ]
}

Get one race

GET/v1/races/{race_id}

One race with every runner, including scratched runners. Each runner has its result, its starting prices, and its WIN and PLACE prices at every time point.

{
  "race_id": "1.264460108",
  "venue": "Hawkesbury",
  "race": "R1 1100m Mdn",
  "start_utc": "2026-10-27T02:15:00",
  "field_size": 10,
  "places_paid": 3,
  "...": "(other race fields as in the list above)",
  "runners": [
    {
      "selection_id": 2714086,
      "saddlecloth": 1,
      "horse": "Time To Dance",
      "win_result": "WINNER",
      "place_result": "PLACED",
      "win_bsp": 19.36,
      "place_bsp": 5.7,
      "win_prices": {
        "T-30m": {"back": 18.5, "back_size": 26.98, "lay": 130.0, "lay_size": 27.52, "ltp": 19.5},
        "T-90s": {"back": 21.0, "back_size": 33.05, "lay": 27.0,  "lay_size": 50.73, "ltp": 23.0}
      },
      "place_prices": {
        "T-90s": {"back": 4.3, "back_size": 79.85, "lay": 80.0, "lay_size": 15.32, "ltp": 5.0}
      }
    }
  ]
}

The example is shortened: a full response has all 8 time points for each market, when they were recorded.

Runners table

GET/v1/runners

A flat table with one row per horse for all races in the date range. This is the easiest way to load data into a spreadsheet, pandas or R.

ParameterExampleDescription
date, from, to, venueAs for List races. At most 31 days per request.
formatcsvjson (default) or csv. CSV is sent as a file download.

The JSON form returns {"from", "to", "count", "columns", "rows"}, where each row has the CSV columns as keys.

Time points

Prices are recorded at these times before the scheduled start. Races often start a little after the scheduled time, so the real time before the start can be longer.

LabelTime before scheduled start
T-60m60 minutes
T-30m30 minutes
T-15m15 minutes
T-5m5 minutes
T-2m2 minutes
T-90s90 seconds
T-60s60 seconds
T-30s30 seconds

Fields

Race

FieldTypeDescription
race_idstringRace identifier (the WIN market id). Use it with /v1/races/{race_id}.
datestringDate of the scheduled start (UTC), YYYY-MM-DD.
start_utcstringScheduled start time (UTC).
venuestringTrack name.
racestringRace name, for example R1 1100m Mdn.
race_nointegerRace number at the meeting.
distance_mintegerDistance in metres.
classstringRace class, for example Mdn (maiden), Hcap (handicap), CL1, Grp1.
field_sizeintegerNumber of runners that started (scratched runners not counted).
places_paidintegerNumber of places paid in the PLACE market (usually 3, or 2 in small fields).
win_market_id, place_market_idstringExchange market ids of the WIN and PLACE markets.

Runner

FieldTypeDescription
selection_idintegerExchange runner id (the same in the WIN and PLACE markets).
saddleclothintegerSaddlecloth (race book) number.
horsestringHorse name.
win_resultstringWINNER, LOSER or REMOVED (scratched).
place_resultstringPLACED, UNPLACED or REMOVED.
win_bsp, place_bspnumberBetfair Starting Price in the WIN and PLACE markets.
win_prices, place_pricesobjectPrices by time point (see below).

Prices at a time point

FieldDescription
backBest price available to back (bet on the horse).
back_sizeAmount available at that back price (AUD).
layBest price available to lay (bet against the horse).
lay_sizeAmount available at that lay price (AUD).
ltpLast traded price.

Prices are decimal odds (for example 3.5 returns $3.50 for each $1, including the stake). null means no price was available.

CSV columns (runners table)

One row per horse. The first columns describe the race and the result:

date, start_utc, venue, race, race_no, distance_m, class, field_size, places_paid,
win_market_id, place_market_id, selection_id, saddlecloth, horse,
win_result, place_result, win_bsp, place_bsp

Then 48 price columns, named {market}_{price}_{time point}:

For example win_back_T-90s is the best back price in the WIN market 90 seconds before the scheduled start. The amounts available (back_size, lay_size) are only in the per-race JSON. Empty cells mean no price was available.

date,start_utc,venue,race,race_no,distance_m,class,field_size,places_paid,win_market_id,place_market_id,selection_id,saddlecloth,horse,win_result,place_result,win_bsp,place_bsp,win_back_T-60m,win_lay_T-60m,win_ltp_T-60m,win_back_T-30m,...
2026-10-27,2026-10-27T02:15,Hawkesbury,R1 1100m Mdn,1,1100,Mdn,10,3,1.264460108,1.264460109,2714086,1,Time To Dance,WINNER,PLACED,19.361193061783,5.7,22.0,1000.0,23.0,18.5,...

Data notes and limits

Errors

Errors return a JSON body: {"detail": "description of the problem"}.

StatusMeaning
400Bad request: a date is not YYYY-MM-DD, to is before from, the range is more than 31 days, or the race id is not valid.
401The key is missing, wrong or turned off.
404No resulted race with that id.
429Too many requests. Wait a minute and try again.
5xxTemporary server problem. Try again later.

Code examples

Python: one month into a pandas table

import io
import requests
import pandas as pd

API = "https://racedata.prospec-au.com"
HEADERS = {"X-API-Key": "YOUR_KEY"}

resp = requests.get(f"{API}/v1/runners",
                    params={"from": "2026-10-01", "to": "2026-10-31", "format": "csv"},
                    headers=HEADERS, timeout=60)
resp.raise_for_status()
runners = pd.read_csv(io.StringIO(resp.text))

winners = runners[runners.win_result == "WINNER"]
print(winners[["date", "venue", "race", "horse", "win_back_T-90s", "win_bsp"]])

Python: every race on a day, with all prices

import requests

API = "https://racedata.prospec-au.com"
HEADERS = {"X-API-Key": "YOUR_KEY"}

day = requests.get(f"{API}/v1/races", params={"date": "2026-10-27"}, headers=HEADERS, timeout=30).json()
for summary in day["races"]:
    race = requests.get(f"{API}/v1/races/{summary['race_id']}", headers=HEADERS, timeout=30).json()
    for runner in race["runners"]:
        late = runner["win_prices"].get("T-90s") or {}
        print(race["venue"], race["race"], runner["horse"], late.get("back"), runner["win_result"])

Stay under 60 requests per minute. For a whole day, the runners table needs only one request.

JavaScript (Node.js 18+)

const API = "https://racedata.prospec-au.com";
const res = await fetch(`${API}/v1/races?date=2026-10-27`, { headers: { "X-API-Key": "YOUR_KEY" } });
if (!res.ok) throw new Error(`HTTP ${res.status}: ${(await res.json()).detail}`);
const { races } = await res.json();
console.log(races.map(r => `${r.start_utc} ${r.venue} ${r.race}`));

Excel (Power Query)

Data → Get Data → From Other Sources → Blank Query → Advanced Editor, then paste:

let
    Source = Web.Contents("https://racedata.prospec-au.com/v1/runners",
        [Query = [from = "2026-10-01", to = "2026-10-31", format = "csv"],
         Headers = [#"X-API-Key" = "YOUR_KEY"]]),
    Table = Csv.Document(Source, [Delimiter = ",", Encoding = 65001]),
    Promoted = Table.PromoteHeaders(Table)
in
    Promoted

Interactive docs

The interactive documentation lists every endpoint and lets you try requests in the browser: click Authorize, or add your key in the X-API-Key field of a request.