Code examples
Copy, set env vars, run.
Every example reads the key from RACINGAPI_KEY on your server. Never embed keys in browser code or commit them.
JavaScript examples run on Node.js 18+: save them as .mjs files to enable imports and top-level await. Python examples use requests (python -m pip install requests); PHP examples require the cURL extension. Replace example IDs with IDs from a racecard in the same daily feed.
Today's UK and Irish racecards
curl --fail-with-body "https://racingapi.net/v1/racecards?region=uk" \
-H "Authorization: Bearer $RACINGAPI_KEY"Dated UK and Irish results
This example uses a sampled day in the combined feed. Replace the date with the day you need; results only include races with published finishers. Archive completeness is not guaranteed.
curl --fail-with-body "https://racingapi.net/v1/results?region=uk&date=2026-10-26" \
-H "Authorization: Bearer $RACINGAPI_KEY"One race by ID
Set RACE_ID to an ID from a racecard on the same day. For a dated or tomorrow racecard, carry that filter through to the detail request; these basic examples target today's feed.
curl --fail-with-body --get "https://racingapi.net/v1/races/$RACE_ID" \
--data-urlencode "region=uk" \
-H "Authorization: Bearer $RACINGAPI_KEY"Horse form summary statistics
Set HORSE_ID and RACE_ID from the same racecard. The raceId query is optional in the API, but these examples include it to select the correct declaration. Keep the same date or tomorrow=true if discovery used one.
curl --fail-with-body --get "https://racingapi.net/v1/horses/$HORSE_ID/form" \
--data-urlencode "region=uk" \
--data-urlencode "raceId=$RACE_ID" \
-H "Authorization: Bearer $RACINGAPI_KEY"Group every data.form row by section and display name, runs, wins, places and winPercentage as supplied strings. Empty strings mean not supplied, not zero; an empty form array means no summary statistics were supplied. The response identifies the chosen race, course and feed date and lists matching declarations. These are summary statistics, not individual past-race records.
Download a silk
Binary PNG, never parsed as JSON. Take HORSE_ID and RACE_ID from the same racecard; add the matching date or tomorrow=true for non-current cards. A 404 means the runner or source image was not found; nothing is substituted.
curl --fail --get "https://racingapi.net/v1/silks/$HORSE_ID" \
--data-urlencode "region=uk" \
--data-urlencode "raceId=$RACE_ID" \
-H "Authorization: Bearer $RACINGAPI_KEY" \
--output silk.pngRacecourse weather
Read a course ID from racecards or racecourses, then use the same feed date. Weather is available on every REST plan. It is a local forecast, not an on-track observation. Preserve meta.source credit and links when rendering it.
curl --fail-with-body "https://racingapi.net/v1/racecourses/COURSE_ID/weather?region=uk" \
-H "Authorization: Bearer $RACINGAPI_KEY"Read data.forecast[]: temperatureC is °C, windSpeedMps is m/s, and precipitationMm covers the next precipitationPeriodHours after time. Times are UTC; values may be null when not supplied. Cache until meta.expiresAt. See weather field semantics before matching forecasts to races.
Paginate a list
const headers = { Authorization: `Bearer ${process.env.RACINGAPI_KEY}` };
const all = [];
for (let page = 1; ; page++) {
const url = new URL("https://racingapi.net/v1/racecourses");
url.searchParams.set("region", "uk");
url.searchParams.set("page", String(page));
url.searchParams.set("limit", "100");
const res = await fetch(url, { headers });
if (!res.ok) throw new Error(`RacingAPI ${res.status}`);
const { meta, data } = await res.json();
all.push(...data);
if (page * meta.limit >= meta.total) break;
await new Promise((r) => setTimeout(r, 600)); // stay under the per-second limit
}Errors and retries
These JSON-only helpers make at most three attempts, with a 15-second timeout per request. They retry 429 and a temporary source-unavailable response, but stop rather than sleeping for more than 30 seconds between attempts. This matters when Retry-After points to next month's quota reset. Transport, timeout and JSON-parsing failures are surfaced to your application rather than retried automatically. An inactive endpoint (endpoint_unavailable) requires activation, not immediate retries. Never repeatedly retry 400, 401, 403, 404 or 422 without correcting their cause. These helpers do not replace account-wide request scheduling.
const key = process.env.RACINGAPI_KEY;
if (!key) throw new Error("Set RACINGAPI_KEY on your server");
const base = process.env.API_BASE_URL || "https://racingapi.net";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function racingGet(path, params = {}) {
const url = new URL(path, base);
for (const [name, value] of Object.entries(params)) {
url.searchParams.set(name, String(value));
}
for (let attempt = 0; attempt < 3; attempt++) {
const res = await fetch(url, {
headers: { "X-API-Key": key },
signal: AbortSignal.timeout(15000),
});
const body = await res.json(); // JSON endpoints only; not silk PNGs
if (res.ok) return body;
const retryAfter = Number(res.headers.get("Retry-After") || 0);
const transientSource = res.status === 503 &&
["provider_unavailable", "image_source_unavailable"].includes(body.code);
const retryable = res.status === 429 || transientSource;
const delay = Math.max(
Number.isFinite(retryAfter) ? retryAfter * 1000 : 0,
1000 * (2 ** attempt),
);
if (!retryable || attempt === 2 || delay > 30000) {
throw Object.assign(new Error(body.error || `HTTP ${res.status}`), {
status: res.status, code: body.code,
requestId: body.requestId, retryAfter,
});
}
await sleep(delay + Math.random() * 200);
}
}
const { meta, data } = await racingGet("/v1/racecards", {
region: "uk", limit: "10",
});
console.log(meta, data);