Documentation / Guides
Getting started
From nothing to a working request in about two minutes.
Two minutes, honestly
There is no application form, no sales call and no card. Sign in with Google, take the key that appears, and make a request. If the request works, everything else in this API will work the same way, because there is only one authentication scheme and one response shape.
1. Get a key
Sign in with Google. Your key appears on your account page immediately, on the free plan. It reads real races on a delayed window: nothing newer than 24 hours, nothing older than seven days. It is the same database a paying customer reads, not a sandbox.
2. Make a request
Every request needs your key in the X-API-Key header. That is the whole of
authentication.
curl https://api.apihorseracing.com/v1/meta/coverage \
-H "X-API-Key: $AHR_KEY"
Start with /v1/meta/coverage rather than something more interesting. It needs
no parameters, it works on every plan, and it tells you what exists and what your own key can
reach in one response. If it returns, your integration is sound.
3. Read the envelope
Every response has the same two keys. meta carries the request id, the
freshness stamp, your plan and your window. data carries the answer.
{
"meta": {
"request_id": "979954038cb35c98",
"data_as_of": "2026-09-09T08:50:32+00:00",
"plan": "free",
"window": "24 hours to 7 days"
},
"data": { ... }
}
Quote request_id if you ever need to ask us about a response. It lets us find
your exact request rather than guess at a reproduction.
4. Fetch a real race
Take a date inside your window and ask for the card, then follow one of the race identifiers.
curl "https://api.apihorseracing.com/v1/racecards/2026-09-05" \
-H "X-API-Key: $AHR_KEY"
curl "https://api.apihorseracing.com/v1/races/rc_1WNM9X2" \
-H "X-API-Key: $AHR_KEY"
The three things to read next
In this order, because each saves you a category of mistake.
| Guide | What it saves you from |
|---|---|
| Coverage by country | Building against a field that does not exist in the jurisdiction you care about. Nine times in ten a null is the source, not us. |
| Errors | Treating a plan refusal as a network failure and retrying it forever. |
| Timezones and dates | Assuming a race time is UTC. It is not; it is local to the course, and everything else is UTC. |
What to do when something is empty
An empty array and a refusal mean very different things here, and we never blur them.
- An empty array means we looked and there was nothing. No racing that day, no runs in that window, no dividends published. It is an answer.
- A 403 means your plan could not look. The body names the window or the plans that reach the endpoint. It is not an answer and retrying will not change it.
- A null field means the jurisdiction did not publish it. Data coverage says which fields exist where, counted rather than claimed.
Before you go to production
- Read
meta.data_as_ofand show it, or at least log it. Racing data has a freshness and pretending otherwise is how a stale result gets displayed as live. - Handle
429by respectingRetry-Afterrather than backing off blindly.X-RateLimit-Remainingis on every successful response too. - If you are polling for results, stop, and take a webhook instead. It is faster and costs nothing against your quota.
- Cache. There is no requirement to re-fetch anything, and a settled race never changes.