A preview or a review panel
One call gives you enough to write about a race without a model.
Documentation / Races & markets
/v1/races/{race_id}/analysis
Market shape, movers, and what this course and distance usually produces.
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.
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| race_id | path | string | yes | Opaque race identifier. |
curl https://api.apihorseracing.com/v1/races/rc_1P005G7/analysis \ -H "X-API-Key: $AHR_KEY"
$ch = curl_init('https://api.apihorseracing.com/v1/races/rc_1P005G7/analysis'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['X-API-Key: ' . getenv('AHR_KEY')], ]); $data = json_decode(curl_exec($ch), true)['data'];
const res = await fetch( 'https://api.apihorseracing.com/v1/races/rc_1P005G7/analysis', { headers: { 'X-API-Key': process.env.AHR_KEY } } ); const { meta, data } = await res.json();
import os, requests r = requests.get( "https://api.apihorseracing.com/v1/races/rc_1P005G7/analysis", headers={"X-API-Key": os.environ["AHR_KEY"]}, ).json()
curl https://api.apihorseracing.com/v1/races/rc_1P005G7/analysis?key=$AHR_KEY
$url = 'https://api.apihorseracing.com/v1/races/rc_1P005G7/analysis' . '?key=$AHR_KEY'; $data = json_decode(file_get_contents($url), true)['data'];
const res = await fetch( 'https://api.apihorseracing.com/v1/races/rc_1P005G7/analysis?key=$AHR_KEY' ); const { meta, data } = await res.json();
import os, requests r = requests.get( "https://api.apihorseracing.com/v1/races/rc_1P005G7/analysis?key=$AHR_KEY", ).json()
{ "meta": { "request_id": "fbb0948873d18864", "data_as_of": "2026-09-12T05:10:27+00:00", "plan": "analyst", "window": "2017 → upcoming" }, "data": { "race_id": "rc_1P005G7", "course": "Fontwell", "date": "2021-02-28", "conditions": { "run_type": null, "distance": "3m 1f 210y", "going": "Soft (Good to Soft in places)", "surface": "TURF", "class": "6", "handicap": false, "runners": 16 }, "market": { "book_percentage": 127.62, "shape": { "shortest": 1.4, "longest": 251, "median": 66, "spread": 179.3, "priced": 16 }, "favourites": [ { "horse_id": "hs_SJX1WR", "horse": "Sametegal", "sp": "2/5", "position": 2 } ], "movers": [ { "horse_id": "hs_YBW9N7", "horse": "Le Reve", "opening": "18/1", "sp": "50/1", "drift": 1.6842, "direction": "drifted", "position": null }, { "horse_id": "hs_3GX9FQ", "horse": "Heaney", "opening": "40/1", "sp": "100/1", "drift": 1.4634, "direction": "drifted", "position": null }, "… 6 more" ] }, "at_this_course_and_distance": { "races": 132, "typical_field": 6.7, "typical_book": 112.2, "favourite_runs": 144, "favourite_wins": 45, "favourite_strike": 0.3125 }, "note": "Context for one race. For statistics across the archive see /v1/analysis/angles and the course endpoints." } }
A real response from /v1/races/rc_1P005G7/analysis,
captured by calling the live API rather than written by hand.
Each field is described once and rendered on every endpoint that returns it, so the wording cannot drift between pages.
| Field | Type | What it is |
|---|---|---|
| race_id | string | Opaque race identifier. Stable, and safe to store. |
| course | string | Course name. |
| date | date | Race date, YYYY-MM-DD. |
| run_type | string | Flat, Hurdle, Chase or NH Flat. |
| 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. |
| class | string | Race class. |
| handicap | boolean | Whether the race was run off official ratings. |
| runners | integer | Runners who took part. |
| book_percentage | number | The field's implied probabilities added up. 100 would be a fair book; the excess is the margin. Ours average 117 in small fields and 133 in sixteen or more. |
| priced | integer | Rides carrying a starting price. A/E and the profit figures use these; runs is the wider count. |
| horse_id | string | Opaque horse identifier. |
| horse | string | Horse name. |
| sp | string | Starting price, fractional, as returned. |
| position | integer | Finishing position. Null means the runner did not complete or was not published below the placings. |
| opening | string | The price the runner opened at, where published. |
| drift | number | Starting price against opening, as a ratio. Positive drifted, negative shortened. |
| direction | string | drifted, shortened or unchanged. |
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.
Class is published in Britain and reliably absent elsewhere. Do not treat a null class as an ungraded race.
America publishes the placings and nothing below them, so position is null for most of the field. Britain, Ireland, France and South Africa carry a position for the great majority of runners. /v1/countries returns the current figure for each.
The source publishes no odds history on a settled race, only an opening price and any prices touched. About one runner in five carries one. price_path.source tells you which kind you have on every runner, so you never have to infer it.
| Code | Status | When |
|---|---|---|
| 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. |
| not_found | 404 | The identifier is well formed but nothing has it. Distinct from invalid_id, which means the identifier itself is wrong. |
| 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. |
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. Forget it
One race in context: how the market was shaped, who the favourite was, which prices moved, and what this course and distance usually produces.
It is the things a reader would otherwise assemble from four calls. The market shape in particular is a summary that is tedious to compute per race: the shortest and longest prices, the median, and the spread between them, which tells you whether this was a market with a short favourite and a long tail or one where everything was six to one.
This is context for a single race, not statistics over an archive, which is why it is on every paid plan. The archive-wide work lives on the Analyst endpoints.
One call gives you enough to write about a race without a model.
The spread figure separates a two-horse race from a genuine handicap far faster than reading fourteen prices.
The favourite strike rate at this course and distance is the honest baseline against which anything you predict should be measured.