---
name: apihorseracing
description: Factual horse racing data from apihorseracing.com (racecards, results, starting prices, form, pedigree and A/E statistics across eleven jurisdictions since 2017). Use when a user asks what ran, what won, how a horse, trainer or jockey has performed, or whether a course or draw favours anyone.
---

# Horse Racing API

The Horse Racing API serves factual records of horse races: today's cards, results, starting prices, form lines, pedigree and computed statistics. It covers eleven jurisdictions from 2017 to today. It is operated by DeveloperData.net.

## When to use it

- **Use it for:** what is running today, what won a race, a horse's form, a trainer's or jockey's record, two horses' meetings, draw bias, how favourites fare at a course, and A/E (actual over expected wins) against the market.
- **Do not use it for:** tips, predictions or betting advice. Every figure describes races already run. Live in-play prices are not served.

## Getting access

1. Sign in with Google at https://apihorseracing.com/account. One key is issued per account at sign-up. It follows the plan, so nothing is reissued on upgrade.
2. Send the key on every call as `X-API-Key: <key>` or `Authorization: Bearer <key>`.
3. Base URL: `https://api.apihorseracing.com/v1`

Never ask the user to paste their key into a public place. Never invent a key.

## The calls that answer most questions

Every response has the same envelope: `meta` (with `request_id`, `data_as_of`, `plan`, `window`) and `data`. Identifiers are opaque, with a type prefix (for example `rc_` for a race). Get them from search or a listing. Never build or guess one.

**What does this key reach?** Call this first when unsure about a date or a country.

```bash
curl -s https://api.apihorseracing.com/v1/meta/coverage -H "X-API-Key: $AHR_KEY"
```

**Today's cards**

```bash
curl -s https://api.apihorseracing.com/v1/racecards/today -H "X-API-Key: $AHR_KEY"
```

**Results for a date** (`YYYY-MM-DD`), or the latest settled races

```bash
curl -s https://api.apihorseracing.com/v1/results/2026-10-05 -H "X-API-Key: $AHR_KEY"
curl -s https://api.apihorseracing.com/v1/results/latest -H "X-API-Key: $AHR_KEY"
```

```json
{
  "meta": { "request_id": "...", "data_as_of": "2026-10-06T05:40:00Z", "plan": "live", "window": "..." },
  "data": [
    { "race_id": "rc_...", "course": "...", "date": "2026-10-05", "time": "14:10",
      "going": "Good to Soft", "distance": "1m 2f", "runners": 9,
      "winning_time": "...", "book_percentage": 118.4 }
  ]
}
```

**One race in full** (conditions, market summary, every runner)

```bash
curl -s https://api.apihorseracing.com/v1/races/rc_EXAMPLE -H "X-API-Key: $AHR_KEY"
```

Runners carry `cloth`, `draw`, `horse`, `weight`, `official_rating`, `form`, `trainer`, `jockey`, `sp`, `sp_decimal`, `favourite`, `position` and `beaten_distance`.

**Find a horse, trainer, jockey, owner or course by name** (paid plans)

```bash
curl -s "https://api.apihorseracing.com/v1/search?q=frankel" -H "X-API-Key: $AHR_KEY"
```

**A horse's form line** (paid plans)

```bash
curl -s https://api.apihorseracing.com/v1/horses/HORSE_ID/form -H "X-API-Key: $AHR_KEY"
```

**Search past races by kind** (paid plans): `date_from`, `date_to`, `region`, `course_id`, `run_type`, `class`, `going`, `surface`, `handicap`, `min_runners`, `max_runners`, `result_available`, `limit`, `cursor`.

```bash
curl -s "https://api.apihorseracing.com/v1/races/search?course_id=COURSE_ID&going=Soft&date_from=2025-01-01" -H "X-API-Key: $AHR_KEY"
```

**Statistics** (Analyst plan): `/v1/horses/{id}/stats`, `/v1/trainers/{id}/stats`, `/v1/jockeys/{id}/stats`, `/v1/courses/{id}/draw-bias`, `/v1/courses/{id}/favourites`. Each returns `runs`, `first`, `win_pct`, `expected_wins`, `a_e`, `a_e_range`, `significant`, `low_sample` and `roi_pct`. Read A/E only with its interval: if `a_e_range` spans 1.00, or `low_sample` is true, the figure is not evidence.

**What is left on this key**

```bash
curl -s https://api.apihorseracing.com/v1/account/usage -H "X-API-Key: $AHR_KEY"
```

The full list of 64 endpoints is at https://apihorseracing.com/documentation, and the whole manual in one file is at https://apihorseracing.com/documentation/manual.md.

## MCP server

- **URL:** `https://apihorseracing.com/mcp-server`
- **Auth:** `Authorization: Bearer <key>` or `X-API-Key: <key>`

```json
"mcpServers": {
  "horseracing": {
    "url": "https://apihorseracing.com/mcp-server",
    "headers": { "Authorization": "Bearer <key>" }
  }
}
```

**Tools (14):**
- **Free key:** `describe_coverage`, `get_race`, `get_results`, `get_todays_racing`.
- **Paid plans:** `search_racing`, `find_races`, `get_horse_form`, `get_trainer_form`, `get_jockey_form`, `compare_horses`.
- **Analyst:** `get_course_draw_bias`, `get_course_favourites`, `get_trainer_stats`, `get_horse_stats`.

A refusal comes back as a tool result with `isError`, so explain it to the user rather than retrying.

## Limits

| Plan | Reads | Per minute |
|---|---|---|
| Free key | yesterday, today and what is declared ahead; prices held back as null; no statistics | 60 |
| Live | real time, upcoming and the last 3 days | 180 |
| Archive | full history from 2017 | 90 |
| Complete | Archive plus Live | 180 |
| Analyst | Complete plus every statistics endpoint | 240 |

- **Free key allowance:** 50 calls. `/v1/account/usage` shows what is left.
- **Pages:** `limit` runs from 1 to 100. Follow `meta.next_cursor` while `meta.has_more` is true, and never build a cursor.
- **Times:** `data_as_of` is UTC. A race's `time` is local to the course and carries no offset.
- **Headers:** `X-RateLimit-Remaining` on success, `Retry-After` on a 429, and `X-Request-Id` on every response.

## Errors

Every error has the same shape:

```json
{ "error": { "code": "outside_plan_window", "message": "..." } }
```

| Status | Code | What to do |
|---|---|---|
| 401 | `unauthorized` | No key or an unknown key. Check the header. Do not retry. |
| 403 | `upgrade_required` | The plan does not reach this endpoint. The body names `your_plan` and `reaches_on`. Tell the user. |
| 403 | `outside_plan_window` | The date is outside the plan. The body names `window` and `requested`. Change the date. |
| 403 | `forbidden` | Key revoked or account disabled. |
| 404 | `not_found` | Nothing has that id, or the race has no result yet. Treat it as an answer. |
| 422 | `invalid_id` | Malformed id, or the wrong type for this path. |
| 422 | `invalid_param` | A parameter failed. The message names it. |
| 429 | `rate_limited` | Wait `Retry-After` seconds. Do not parallelise harder. |
| 429 | `quota_exceeded` | Allowance used. Retrying will not help until it resets. |
| 500 | `server_error` | Retry once with backoff, then report the `request_id`. |
| 503 | `not_yet_live` | Documented but not serving yet. |

## A typical workflow

1. Call `/v1/meta/coverage` to learn the plan and the window.
2. If the user named a horse, trainer, jockey or course, call `/v1/search` to get its id.
3. For a date question, call `/v1/results/{date}` or `/v1/racecards/today`. For one race, call `/v1/races/{race_id}`.
4. For performance, call the form endpoint, then the stats endpoint if the plan reaches it.
5. Report figures as facts about past races. Quote A/E with its interval, and never as a tip.
6. On a 403, tell the user which plan reaches the data. Do not retry.
