APIHorseRacing

Documentation / Races & markets

Search races

GET /v1/races/search

Filter by date, region, run type, going, class, field size. Cursor paginated.

Available on

Free key 24h – 7d
Live today + 3 days
Archive back to 2017
Complete 2017 → upcoming
Complete + Analyst 2017 → upcoming

Send your key in the X-API-Key header, or as ?key= if your tool cannot set one. A date outside your plan's window returns 403 outside_window naming the window, never an empty array.

Parameters

NameInTypeRequiredDescription
date_from query string no YYYY-MM-DD. Clamped to the oldest date your plan can read.
date_to query string no YYYY-MM-DD. Clamped to the newest date your plan can read.
region query string no Jurisdiction, e.g. GB. Values from /v1/countries.
course_id query string no Restrict to one course.
run_type query string no Flat, Hurdle, Chase or NH Flat. Values from /v1/reference.
class query string no Race class. Published in Britain and largely absent elsewhere.
going query string no Exact match on the going as published. Values from /v1/reference.
surface query string no Turf, Dirt, Polytrack and so on.
handicap query boolean no Whether the race was run off official ratings.
min_runners query integer no Smallest field to include.
max_runners query integer no Largest field to include.
result_available query boolean no Settled races only, or unsettled only.
limit query integer no 1 to 200. Defaults to 50.
cursor query string no Opaque. Pass next_cursor from the previous response; never construct one.

Request

curl https://api.apihorseracing.com/v1/races/search \
  -H "X-API-Key: $AHR_KEY"

Response

200 OK application/json harvested 8 days ago
{
    "meta": {
        "request_id": "22507b38757771cc",
        "data_as_of": "2026-09-12T05:10:27+00:00",
        "plan": "analyst",
        "window": "2017 → upcoming",
        "count": 3,
        "has_more": true,
        "next_cursor": "MjAxNy0wMS0wMXwxNTU4",
        "searched": {
            "from": null,
            "to": null
        }
    },
    "data": [
        {
            "race_id": "rc_3SDZ4F1",
            "meeting_id": "mt_E25224",
            "course": "Catterick",
            "time": "12:25",
            "off_time": "12:26:01",
            "name": "Happy New Year Novices' Hurdle",
            "run_type": "Hurdle",
            "class": "4",
            "distance": "2m 3f 66y",
            "going": "Good to Soft (Soft in places)",
            "surface": "TURF",
            "handicap": false,
            "runners": 9,
            "result_available": true,
            "date": "2017-01-01"
        },
        {
            "race_id": "rc_E9E5XG",
            "meeting_id": "mt_E25224",
            "course": "Catterick",
            "time": "13:00",
            "off_time": "13:00:15",
            "name": "Buy Your 2017 Annual Badge Today Handicap Hurdle",
            "run_type": "Hurdle",
            "class": "4",
            "distance": "1m 7f 156y",
            "going": "Good to Soft (Soft in places)",
            "surface": "TURF",
            "handicap": true,
            "runners": 9,
            "result_available": true,
            "date": "2017-01-01"
        },
        "… 1 more"
    ]
}

A real response from /v1/races/search?region=GB&limit=3, captured by calling the live API rather than written by hand.

Response fields

Each field is described once and rendered on every endpoint that returns it, so the wording cannot drift between pages.

FieldTypeWhat it is
race_id string Opaque race identifier. Stable, and safe to store.
meeting_id string Opaque meeting identifier. A meeting is one course on one day.
course string Course name.
time string Scheduled off time, local to the course.
off_time string Actual off time where recorded.
name string Race title as published, sponsor and all.
run_type string Flat, Hurdle, Chase or NH Flat.
class string Race class.
distance string Official distance as published, e.g. "3m 3f 119y".
going string Ground description at the off.
surface string Turf, Dirt, Polytrack and so on.
handicap boolean Whether the race was run off official ratings.
runners integer Runners who took part.
result_available boolean Whether a result has been published.
date date Race date, YYYY-MM-DD.

What varies by jurisdiction

Run type is filled in behind the results crawl

The code a race was run under lives on the horse profile rather than the race, so it arrives on a later pass. A null run_type means not yet fetched, not unclassified.

Race class is a British idea

Class is published in Britain and reliably absent elsewhere. Do not treat a null class as an ungraded race.

Errors this endpoint returns

CodeStatusWhen
unauthorized 401 No key, or one we do not recognise. Check the header name and that the key has not been rotated from your account page.
outside_window 403 You asked for a date your plan cannot see. The body names the window and the date you asked for. Search endpoints clamp instead of refusing, and report what they actually searched in meta.
invalid_param 422 A date that is not YYYY-MM-DD, a by= dimension we do not have, or a cursor that did not come from us. The message names the parameter.
invalid_id 422 The prefix does not match the kind of thing the path expects, or the identifier is malformed. A horse id passed where a race is wanted lands here rather than returning the wrong race.
rate_limited 429 Past your per-minute rate. Retry-After is set, and X-RateLimit-Remaining tells you where you stand on every successful response too.
server_error 500 Ours, not yours. Every response carries a request_id; quote it and we can find the exact request.

Run it live

A real request from your browser straight to api.apihorseracing.com with your own key. Nothing is proxied and nothing is logged by this page.

Remembered in this browser so you do not paste it on every page.

What this is for

Find races by anything the race itself carries. The workhorse of the group, and the endpoint most likely to be behind whatever you are building.

Pagination is a cursor on the race date and identifier rather than an offset. Both are fixed once a race exists, so a page cannot shift under you while the archive is loading behind it, which an offset absolutely would.

Dates are clamped rather than refused. Ask a free key for 2017 to today and it will search the seven days it can read and tell you so in meta.searched, instead of returning an error or, worse, quietly returning a different range than you asked for. Only a range entirely outside your window is refused.

Common uses

Building a study population

Filter to the shape of race you care about, then walk the cursor. Sixteen-runner handicap chases on soft ground is one request away.

Backfilling a subset

Cheaper than walking every date when you only want one region or one run type.

Paging safely

Follow next_cursor until has_more is false. Do not store a page number; there is no stable meaning for page four while races are still arriving.

Notes

meta.searched always states the range actually used after clamping. Read it rather than assuming your parameters were honoured verbatim.

Related in Races & markets