# GreyhoundAPI complete reference

> Greyhound racing data for Great Britain, Ireland and Australia, as a clean REST + WebSocket API.
>
> Generated from the live API definition on 7 October 2026.
> Every endpoint, parameter and sample below is produced from the same
> source the API itself runs on, so this document cannot disagree with it.
>
> Canonical copy: https://greyhoundapi.com/documentation/manual.md

---

## At a glance

| | |
| --- | --- |
| Base URL | `https://api.greyhoundapi.com/v1` |
| Authentication | `X-API-Key: <your key>` request header |
| Format | JSON. Every response has `meta` and `data`. |
| Endpoints | 62 (42 on Live, 62 on Live Plus) |
| Coverage | Great Britain, Ireland and Australia |
| Free key | 50 calls a day, 60/min, rolling 1-day window |
| Live | $99/month, 250,000 calls, 120/min |
| Live Plus | $199/month, 600,000 calls, 180/min |
| Support | support@greyhoundapi.com |

This API publishes **factual racing data only**: results, times, prices,
form. It carries no predictions, ratings, tips or betting advice on any
endpoint, and nothing in this document should be read as any of those.

---

## Guides

The reasoning behind the data: how it is collected, what is missing, and
the conventions that are easy to get wrong.

## Start with a free key

*Everything a free GreyhoundAPI key can do, the seven endpoints it reaches, and a first request that works straight away.*  
`https://greyhoundapi.com/documentation/free-key`

A free key needs nothing but an email and never expires. It reaches
**17 endpoints** covering races, runners and tracks, across every track in
both regions — not a cut-down sample.

What it does not reach is live-day data and the derived statistics. That is the whole of the
difference, and it is worth knowing before your first request rather than after it.

### Your first request

This works on the key you already have. Nothing to configure.

```
curl "https://api.greyhoundapi.com/v1/races?date_from=2026-07-11&region=GB&limit=5" \
  -H "X-API-Key: $GAPI_KEY"
```

### The 17 endpoints on your key

| Endpoint | What it gives you |
| --- | --- |
| [`/v1/racecards/today`](https://greyhoundapi.com/documentation/racecards-today) | Every greyhound racecard scheduled today in Great Britain and Australia, resolved per… |
| [`/v1/racecards/upcoming`](https://greyhoundapi.com/documentation/racecards-upcoming) | A rolling window of the next greyhound races to jump across GB, AU and IE, from the… |
| [`/v1/races`](https://greyhoundapi.com/documentation/list-races) | Search the full greyhound race archive by date range, region, track, grade, distance,… |
| [`/v1/races/{race_id}`](https://greyhoundapi.com/documentation/get-race) | Full detail for one greyhound race: track, grade, distance, going, scheduled and… |
| [`/v1/races/{race_id}/runners`](https://greyhoundapi.com/documentation/race-runners) | Just the runners for one race: trap draws, dogs, breeding, trainers, owners, weights… |
| [`/v1/results/today`](https://greyhoundapi.com/documentation/results-today) | Every greyhound race completed today across GB, AU and IE, most recent first, with… |
| [`/v1/results/yesterday`](https://greyhoundapi.com/documentation/results-yesterday) | Every greyhound race completed yesterday across GB, AU and IE, most recent first, with… |
| [`/v1/results/latest`](https://greyhoundapi.com/documentation/latest-results) | Results ordered by the moment they reached the pipeline — the polling companion to… |
| [`/v1/tracks`](https://greyhoundapi.com/documentation/list-tracks) | Every GB, AU and IE greyhound track that has raced in the last two weeks (courses… |
| [`/v1/tracks/{track_id}`](https://greyhoundapi.com/documentation/get-track) | One track's record: name, region, timezone, active status, every distance raced there… |
| [`/v1/tracks/{track_id}/races`](https://greyhoundapi.com/documentation/track-races) | Racecards and results at one track, filterable by track-local date range and lifecycle… |
| [`/v1/tracks/{track_id}/stats`](https://greyhoundapi.com/documentation/track-stats) | Descriptive statistics for a track: trap-by-trap win counts and percentages per… |
| [`/v1/status`](https://greyhoundapi.com/documentation/api-status) | Live health of the data pipeline: last sync per region, races currently awaiting… |
| [`/v1/coverage`](https://greyhoundapi.com/documentation/coverage) | Every track and day: races carded, run, resulted and abandoned, checked against… |
| [`/v1/usage`](https://greyhoundapi.com/documentation/usage) | Your own API key's consumption: requests today and this billing month, your plan's… |
| [`/v1/archive`](https://greyhoundapi.com/documentation/archive) | The size of the dataset, counted rather than claimed: how many dogs, trainers, owners… |
| [`/v1/reference`](https://greyhoundapi.com/documentation/reference) | Decode tables for the whole API: race grades, regions and timezones, going descriptions… |

### What a free key cannot do

Two limits, and both are about live racing rather than about the archive:

- **A window on recency.** Results settle before a free key can read them, so today's
finishing order is not available. The archive behind that window is.

- **No derived statistics.** The dog, trainer and owner statistics endpoints, and the
analysis endpoints, are Live Plus.

Ask for something outside that and the response is an honest one rather than an empty
result — error code `upgrade_required`, or `outside_sandbox_window`
if the data exists but is newer than your key can see. Both name the tier that would answer it.

### When you are ready

Live opens the remaining endpoints, live-day data and the results stream. Nothing about your
integration changes: same key header, same response shape, same paths. See
[pricing](https://greyhoundapi.com/pricing), or [the full reference](https://greyhoundapi.com/documentation) for everything
the API covers.

---

## Race Day plan overview

*Today and yesterday, with the prices on: what the Race Day plan covers, every endpoint it reaches, and what still needs Live.*  
`https://greyhoundapi.com/documentation/race-day-plan-overview`

Race Day · $49/month

Today and yesterday, with the money on.

Every card, every result and every price for the two days still trading. It is the plan for
screens, bots and bet-placement tools that only ever ask about racing happening now. It is not
an archive plan: anything older than yesterday is refused, with the window named, rather than
quietly returning less.

[Go Race Day, $49/mo](https://greyhoundapi.com/pricing)
[Compare the plans](https://greyhoundapi.com/pricing#compare)

### The window

Counted at each track in its own timezone, so an Australian card comes back whole. The floor
moves at local midnight: a race you read at 23:55 can be outside the window five minutes later.

**Before yesterday**Refused, with the date asked for and the plan that carries it
**Yesterday**Cards, results, prices, weights, weather, dividends
**Today**Cards from publication, results as they land, live prices
**Scheduled ahead**Cards only, since the racing has not happened

### What it adds to a free key

This is the whole argument for the plan. A free key already reads these two days. What it
cannot read is the money.

#### Race day On this plan

-

**Starting prices**, fractional and decimal

- **Betfair back and lay**, with matched amounts

- **Runner weights** and handicap marks

- **Race weather**, hour by hour

- **Forecast and tricast** dividends

- **The results stream** and webhooks

#### Still the Live plan

- Any racing before yesterday

- A dog's form and past runs

- Odds history and price movement

- Trainer, dog and owner statistics

- Sire and dam breeding records

- Bulk archive download

### Every endpoint on the plan

20 REST endpoints, plus the results stream and webhooks. Each links to its own
page, with parameters and a sample response.

| Endpoint | What it answers | Plan |
| --- | --- | --- |
| [`GET /v1/racecards/today`](https://greyhoundapi.com/documentation/racecards-today) | Today's racecards | Race day |
| [`GET /v1/racecards/upcoming`](https://greyhoundapi.com/documentation/racecards-upcoming) | Upcoming races | Race day |
| [`GET /v1/races`](https://greyhoundapi.com/documentation/list-races) | Search races | Race day |
| [`GET /v1/races/{race_id}`](https://greyhoundapi.com/documentation/get-race) | Get a race | Race day |
| [`GET /v1/races/{race_id}/runners`](https://greyhoundapi.com/documentation/race-runners) | Race runners | Race day |
| [`GET /v1/races/{race_id}/result`](https://greyhoundapi.com/documentation/race-result) | Get a race result | Race day |
| [`GET /v1/races/{race_id}/status`](https://greyhoundapi.com/documentation/race-status) | Race status & freshness | Race day |
| [`GET /v1/races/{race_id}/market`](https://greyhoundapi.com/documentation/race-market) | Race market summary | Race day |
| [`GET /v1/results/today`](https://greyhoundapi.com/documentation/results-today) | Today's results | Race day |
| [`GET /v1/results/yesterday`](https://greyhoundapi.com/documentation/results-yesterday) | Yesterday's results | Race day |
| [`GET /v1/results/latest`](https://greyhoundapi.com/documentation/latest-results) | Latest results | Race day |
| [`GET /v1/tracks`](https://greyhoundapi.com/documentation/list-tracks) | List tracks | Race day |
| [`GET /v1/tracks/{track_id}`](https://greyhoundapi.com/documentation/get-track) | Get a track | Race day |
| [`GET /v1/tracks/{track_id}/races`](https://greyhoundapi.com/documentation/track-races) | Races at a track | Race day |
| [`GET /v1/tracks/{track_id}/stats`](https://greyhoundapi.com/documentation/track-stats) | Track statistics | Race day |
| [`GET /v1/status`](https://greyhoundapi.com/documentation/api-status) | Pipeline status | Race day |
| [`GET /v1/coverage`](https://greyhoundapi.com/documentation/coverage) | Coverage ledger | Race day |
| [`GET /v1/usage`](https://greyhoundapi.com/documentation/usage) | Your usage | Race day |
| [`GET /v1/archive`](https://greyhoundapi.com/documentation/archive) | All statistics | Race day |
| [`GET /v1/reference`](https://greyhoundapi.com/documentation/reference) | Reference data | Race day |
| [`WSS results stream`](https://greyhoundapi.com/documentation/websocket) | Results and status changes as they settle | Race day |
| [`Webhooks`](https://greyhoundapi.com/documentation/webhooks) | A POST to your URL when a result lands | Race day |

### What a refusal looks like

Asking for older racing is answered rather than ignored: the date asked for, the window that
applies, and the plan that carries it.

******403 Forbiddenapplication/json**Copy

```
{
  "error": {
    "code": "outside_plan_window",
    "message": "Race Day covers today and yesterday (2026-09-22 onward). 2026-09-15 needs the Live plan, which carries the full archive.",
    "your_plan": "race_day",
    "required_plan": "live",
    "window": { "from": "2026-09-22", "to": "upcoming" },
    "upgrade_url": "https://greyhoundapi.com/account"
  }
}
```

If you were planning to download the archive, this is the wrong plan.**
Race Day holds two days at a time and nothing behind them, so a backfill, a model trained on
past form, or a study of one track across a season all need Live. Moving up keeps the same key:
the history and the extra endpoints start answering on the next request, with nothing to
re-integrate.

---

## Getting started

*Make your first GreyhoundAPI call in under five minutes: create a key, request today's racecards and read the response envelope.*  
`https://greyhoundapi.com/documentation/getting-started`

Every request is a plain HTTPS `GET` against `https://api.greyhoundapi.com/v1`
with your key in a header. There is no OAuth dance and no request signing.

### 1. Get a key

Sign in and create a key from your dashboard. Live keys start `gapi_live_`;
free [sandbox](https://greyhoundapi.com/documentation/sandbox) keys start `gapi_test_` and work
against the same endpoints inside the sandbox data window.

### 2. Call the API

```
curl "https://api.greyhoundapi.com/v1/racecards/today?region=GB" \
  -H "X-API-Key: $GAPI_KEY"
```

### 3. Read the envelope

Every response has the same shape — `meta` for request bookkeeping and
freshness, `data` for the payload:

```
{
  "meta": { "request_id": "req_8f2ac1", "data_as_of": "2026-07-08T13:05:12Z",
            "count": 25, "next_cursor": "eyJz..." },
  "data": [ … ]
}
```

`data_as_of` tells you how fresh the underlying pipeline was when the
response was built — read [Data freshness](https://greyhoundapi.com/documentation/data-freshness) to use it
properly. From here, [Get a race](https://greyhoundapi.com/documentation/get-race) is the endpoint most
integrations are built around.

Before you build against a particular track, check it is one we hold:
[What we cover](https://greyhoundapi.com/documentation/coverage) states the limits plainly, and the
[racing calendar](https://greyhoundapi.com/racing-calendar) shows every track and how often it races.

---

## Authentication

*How GreyhoundAPI keys work: the header format, live and sandbox prefixes, rotation with overlap, and what suspension looks like.*  
`https://greyhoundapi.com/documentation/authentication`

Authenticate every request with your key in the `X-API-Key` header.
`Authorization: Bearer <key>` is accepted as an alias.

```
curl "https://api.greyhoundapi.com/v1/status" -H "X-API-Key: $GAPI_KEY"
```

### Key types

| Prefix | Tier | Data |
| --- | --- | --- |
| `gapi_live_` | Live ($99/month) | Everything, including live-day data and the WebSocket stream |
| `gapi_test_` | [Sandbox](https://greyhoundapi.com/documentation/sandbox) (free) | Yesterday, today and ahead, with prices held back |

### Handling and rotation

Keys are shown once at creation and stored hashed — treat them like passwords and keep
them server-side. Rotating a key issues a replacement immediately and keeps the old key
working for 24 hours so deploys can overlap. Revoking is immediate.

### Suspension

If a subscription lapses, its keys return `403` with error code
`key_suspended` and open WebSocket connections are closed. Reactivating the
subscription restores the same keys — nothing needs re-issuing.

---

## Batch requests

*Fetch up to 50 known records in one call with the ids parameter, instead of one request per record.*  
`https://greyhoundapi.com/documentation/batch-requests`

If you already know which records you want, ask for them together. Every list endpoint that
supports it takes an `ids` parameter:

```
curl "https://api.greyhoundapi.com/v1/races?ids=2765720978-1237493697-2214883445" \
  -H "X-API-Key: $GAPI_KEY"
```

One request, one response, three races. The alternative — a loop calling
`/v1/races/{race_id}` once per race — costs you a round trip each time and burns
a request from your quota for every one.

We found this from the other side. One of our own collectors was fetching results a race at a
time; moving it to a batched upstream call turned a full day of work into about fifteen minutes,
for byte-identical data. The saving is almost entirely latency, and it is larger than people expect.

### What it costs

**Each id costs one call.** Fifty ids in one request is charged as fifty, exactly as if you
had fetched them one at a time.

So batching does not buy you a cheaper allowance — it buys you time. Fifty round trips
become one, which on a typical connection is the difference between several seconds and a few
hundred milliseconds, and your code gets simpler.

We are explicit about this because the alternative is worse for everyone. If a batch counted as
one request, a key limited to 180 a minute could pull 9,000 records a minute by wrapping them
fifty at a time, and the limit would stop describing anything. A rate limit should measure how
much data you take, not how many connections you opened.

The cost is charged before the request is served, so a batch larger than your remaining
allowance is refused with `429` rather than partially filled.

### The rules

- **Dash separated**, e.g. `?ids=A-B-C`. Commas work too if that suits your code better.

- **Up to 50 ids** per request. Past that you are paginating, not batching — see below.

- **Filters still compose.** `?ids=A-B-C&status=complete` narrows the batch;
it does not replace it.

- **Duplicates collapse.** Sending the same id three times returns it once.

- **Each id costs one call.** A batch of fifty is charged as fifty, exactly the same as
fetching them one at a time.

- **Public ids only**, the same ones every response returns. Raw internal integers are
refused here as they are everywhere.

### A bad id fails the whole request

If any id in the batch has no record, you get `400 invalid_id` naming it, and no
data at all:

```
{
  "error": {
    "code": "invalid_id",
    "message": "No record for id 9999999999.",
    "ids": ["9999999999"]
  }
}
```

This is deliberate, and it is the opposite of what several APIs do. Quietly returning the 49
that worked gives you a response that *looks* complete and is not. Nothing in the payload
tells you one is missing, so the gap gets written into whatever you build and surfaces weeks
later as a hole in a report. We would rather be loudly wrong now than quietly wrong later.

If you are working from ids that may not exist — a stale list, or something a user typed
— check them in smaller batches, or handle the `ids` array in the error and retry
without them.

### When not to use it

`ids` is for a **known set**. If you are walking everything — a whole
meeting, a date range, a track’s history — use the
[cursor](https://greyhoundapi.com/documentation/pagination) instead. It is built for that, it has no cap, and
it will not break when the set grows past 50.

The distinction in one line: **you already have the ids → `ids`. You are
discovering them → `cursor`.**

### Where it works

| Endpoint | Batches |
| --- | --- |
| [`/v1/races`](https://greyhoundapi.com/documentation/list-races) | Search races |
| [`/v1/results`](https://greyhoundapi.com/documentation/search-results) | Search results |
| [`/v1/dogs/search`](https://greyhoundapi.com/documentation/search-dogs) | Search dogs |
| [`/v1/tracks`](https://greyhoundapi.com/documentation/list-tracks) | List tracks |

---

## Pagination

*Two ways to page GreyhoundAPI list endpoints: simple 1-based page numbers, or opaque cursors for large and streaming exports.*  
`https://greyhoundapi.com/documentation/pagination`

**Fetching a known set of records?** You want
[batch requests](https://greyhoundapi.com/documentation/batch-requests), not the cursor. Pagination is for
walking a set you are discovering; `ids` is for records you can already name.

Every list endpoint can be paged two ways. They work on the same endpoints, so pick
whichever suits the job — and you can switch between them mid-stream.

### Page numbers

The simplest option: ask for a page with `page` (1-based). The response tells
you exactly where you are and how many pages there are.

```
curl "https://api.greyhoundapi.com/v1/results?date_from=2026-07-01&page=2&limit=100" \
  -H "X-API-Key: $GAPI_KEY"
```

```
{
  "meta": {
    "count": 100,
    "next_cursor": "eyJzIjoi...",
    "page": 2,
    "per_page": 100,
    "total": 4980,
    "total_pages": 50,
    "next_page": 3,
    "prev_page": 1
  },
  "data": [ ... ]
}
```

Page numbers are ideal for UI pagers and for browsing bounded (date-filtered) result
sets. They are less suited to very large exports: deep pages get progressively slower, and
a page can shift if new races land while you iterate. For those, use cursors.

You don't have to ask for page 1 explicitly: the first response of a paged list
(a call with no `cursor`) already carries `page`, `per_page`,
`total`, `total_pages` and `next_page` — so you learn the size
of the result set from the first request. A cursor continuation stays lean, returning just
`count` and `next_cursor`.

### Cursors

Cursors page by sort position rather than offset, so results stay stable even while new
races arrive underneath the query, and page 10,000 is as fast as page 1. Follow
`meta.next_cursor` until it is absent.

```
curl "https://api.greyhoundapi.com/v1/results?date_from=2026-07-01&limit=100" \
  -H "X-API-Key: $GAPI_KEY"
# -> meta.next_cursor: "eyJzIjoi..."

curl "https://api.greyhoundapi.com/v1/results?date_from=2026-07-01&limit=100&cursor=eyJzIjoi..." \
  -H "X-API-Key: $GAPI_KEY"
```

Cursors are opaque — pass `meta.next_cursor` back unchanged and do not build or
parse them yourself. When it is absent, you have reached the last page.

### Which should I use?

| Use case | Reach for |
| --- | --- |
| UI pagers, jumping to a specific page, small or date-bounded queries | `page` |
| Bulk exports (e.g. ML training sets), full-archive pulls, catch-up after a disconnect | `cursor` |

You are not locked in: a page-number response also carries a `next_cursor`, so
you can start on `page=1` and switch to cursors once past the first page. If you
pass both, `page` takes precedence.

### Page size

`limit` accepts 1–200 and defaults to 50. It applies to both styles.

---

## Rate limits

*Per-minute and monthly limits for live and sandbox GreyhoundAPI keys, the rate-limit response headers and how to back off cleanly.*  
`https://greyhoundapi.com/documentation/rate-limits`

| Tier | Per minute | Quota | WebSocket |
| --- | --- | --- | --- |
| Live | 120 | 250,000 / month | 2 connections |
| Live Plus | 180 | 600,000 / month | 2 connections |
| Sandbox | 60 | 50 / day | — |

Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`
and `X-RateLimit-Reset`. Exceeding the per-minute limit returns
`429 rate_limited`; exhausting the quota returns `429 quota_exceeded`
until the window resets. On a 429, wait until the `X-RateLimit-Reset` instant
rather than hammering retries.

Check consumption any time with [GET /v1/usage](https://greyhoundapi.com/documentation/usage) — it
does not count against your limit.

---

## Errors

*The GreyhoundAPI error shape, every error code the API returns and the HTTP statuses to expect for each.*  
`https://greyhoundapi.com/documentation/errors`

Errors are JSON with a stable shape and a documentation link:

```
{ "error": { "code": "rate_limited",
  "message": "120 requests/minute exceeded. Retry after 21s.",
  "doc_url": "https://greyhoundapi.com/documentation/errors" } }
```

| HTTP | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthorized` | Missing or unknown key. |
| 403 | `key_suspended` | Key exists but its subscription has lapsed. |
| 403 | `upgrade_required` | Your key’s tier does not reach this endpoint. The payload names the tier you hold and the one you need, the monthly price, and — where one exists — a `try_instead` endpoint that works on the key you already have. |
| 403 | `outside_sandbox_window` | Sandbox key requested data outside its window. |
| 404 | `not_found` | No such resource. |
| 405 | `method_not_allowed` | The API is read-only; every endpoint is GET. |
| 409 | `result_not_available` | The race exists but has no result yet. Carries its current `status` and a `retry` flag — `true` while it is still to run, `false` once it has run and stalled. A race confirmed as never run returns `200` instead, with `result_available: false`. |
| 400 | `invalid_id` | An id in a [`ids`](https://greyhoundapi.com/documentation/batch-requests) batch has no record, or there are more than 50. The whole request is refused and the offending ids are listed in `ids`. |
| 422 | `invalid_param` | A parameter failed validation; the message names it. |
| 429 | `rate_limited` | Per-minute limit exceeded. |
| 429 | `quota_exceeded` | Monthly (live) or daily (sandbox) quota exhausted. |
| 500 | `server_error` | Our side. Safe to retry with backoff. |
| 501 | `not_implemented` | Documented but not yet live — pre-launch only. |

---

## Timezones & dates

*How GreyhoundAPI handles time across Great Britain and Australia: UTC timestamps, track-local rendering and track-local date parameters.*  
`https://greyhoundapi.com/documentation/timezones`

Covering GB, AU and IE together means two racing days are always in play. The API keeps
this unambiguous with two rules.

### Rule 1 — timestamps are UTC, rendered locally too

```
"scheduled_start": {
  "utc":   "2026-07-08T20:46:00Z",
  "local": "2026-07-08T21:46:00+01:00"
},
"track": { "name": "Romford", "timezone": "Europe/London" }
```

Store and compare the `utc` value; show the `local` value.
Every track carries its IANA timezone.

### Rule 2 — date parameters are track-local

`?date=2026-07-09` means the 9th of July *at that track*. A Wentworth
Park evening card on the 9th (Sydney time) matches `date=2026-07-09` even while
it is still the 8th in London. Likewise [racecards/today](https://greyhoundapi.com/documentation/racecards-today)
resolves "today" per track — an AU card and a GB card can both be today's at the same moment.
This is deliberate: it matches how racing calendars are published in each country.

---

## Data integrity

*Check what we hold for any date before you pay for it — races carded, results recorded, and anything without a result, with the reason.*  
`https://greyhoundapi.com/documentation/data-integrity`

Every claim on this page is checkable. Pick any date and see exactly what we
hold for it — races carded, results recorded, and anything that has no result, with the
reason. No key required, and the same figures our own monitoring reads.

**—**races in the archive*Great Britain and Australia*
**—**have a recorded result*of races that were actually run*
**—**runner records*times, prices, sectionals*
**—**confirmed abandoned*carded, never run*

#### Look up any date

Counts rebuild nightly. If a day is short, this will say so.

From
To
Region
All regions
Great BritainAustralia
Ireland
**Check

Quick ranges

7 days
30 days
60 days
90 days
12 months
24 months

Loading…

### Three states, and why the difference matters

A race with no result is not automatically a gap in our data. Cards get abandoned, races
get voided, meetings get called off. What matters is being able to tell those apart, so you
can reconcile a day against any other source and know whether to investigate or move on.

#### Complete result recorded

The race ran and we hold its finishing order, run times, sectionals where the track
records them, starting prices and exchange prices.

This is what the [results endpoints](https://greyhoundapi.com/documentation/search-results) return.

#### Abandoned never run

The race was carded and did not take place, or ran and was voided. We confirm this
against source before marking it — never by assuming an old race without a
result must have been called off.

Requesting its result returns `200` with `result_available: false`,
so a polling client stops immediately instead of retrying forever.

#### Unresolved no verdict yet

The race ran, has no result, and we have not yet confirmed why. These are the only
genuine unknowns, and every one is visible in the checker above.

Results searches return `meta.unresolved` beside the total, so a short day
explains itself without a support ticket.

### How we know

Every racing day is reconciled against our source. Where a card produced results and one
of its races is absent from them, that race did not run and is marked accordingly. Where a
whole meeting produced nothing, we confirm the meeting itself rather than inferring from
silence — a source that fails to answer is never treated as a source saying no.

Anything that cannot be positively confirmed stays unresolved** and stays visible.
That is why the figure above is not 100%.

### Pull the figures yourself

The checker above is powered by an open endpoint. No key, no quota, no rate limit
— it returns aggregate day counts only, and it is the same table our own dashboards
read.

```
GET https://greyhoundapi.com/data-integrity.json?from=2026-07-10&to=2026-07-13&region=GB
```

Ranges are capped at 366 days. `complete + abandoned + unresolved` always
equals `races`; if it ever does not, our rebuild is wrong and we would like to
know.

---

## Data freshness

*The GreyhoundAPI race lifecycle from scheduled to complete, honest delay metrics, and how to build consumers around results_received_at.*  
`https://greyhoundapi.com/documentation/data-freshness`

Results reach the pipeline anywhere from minutes to hours after the off — occasionally
longer. Rather than pretend otherwise, the API tells you exactly where every race stands.

If a meeting never appears at all, freshness is the wrong place to look: check
[the calendar](https://greyhoundapi.com/racing-calendar) first, because a track we do not cover produces
no race to be late.

### The lifecycle

`scheduled → off → results_pending → complete`, with `void` and
`abandoned` as terminal exceptions. Every race carries a
`freshness` block:

```
"status": "complete",
"freshness": {
  "first_seen":       "2026-07-07T05:12:10Z",
  "results_received": "2026-07-08T21:03:40Z",
  "results_delay_s":  1060
}
```

### off, results_pending & complete

`off` is a window of minutes, not a lasting state. If you are polling for races
in progress, expect a handful at most at any moment — and if a result never arrives, the
race sits in `results_pending` with its `freshness` block showing how
long it has been waiting, rather than claiming to still be running.

Greyhound races run in around 30 seconds, so a race stays `off` only for the
brief window it is actually running. Once it has jumped and a short settling period has passed
with no final result, the API reports `results_pending` — the race has run and its
result is being processed — and it becomes `complete` the moment the full finishing
order is published. This lets you tell a race that is still to run (`scheduled`)
apart from one that has run and is awaiting its result (`results_pending`) without
inferring anything from the clock yourself. It behaves identically for Great Britain and
Australia.

`results_pending` reflects that a race has run; the *speed* at which the
final result then lands depends on the upstream feed and is reported honestly through the
`freshness` block above and [GET /v1/status](https://greyhoundapi.com/documentation/api-status).

### Provisional vs final results

The upstream feed sometimes publishes the winner first and fills the minor placings in
shortly after, so a race can read `complete` before every placing is settled. The
`result` block therefore carries a `result_status`: `provisional`
while placings are still arriving, and `final` once every dog that ran has a finishing
position. Poll until `result_status` is `final` if you need the settled
order; the same field is on [GET /v1/races/{id}/status](https://greyhoundapi.com/documentation/race-status).

### Build for it

Treat `results_received` as the event time of a result, not the off-time —
that is what [GET /v1/results/latest](https://greyhoundapi.com/documentation/latest-results) orders by and
what the [WebSocket stream](https://greyhoundapi.com/documentation/websocket) emits on. A consumer that
keys on off-time will believe late results are missing; one that keys on receipt time is
always consistent with the pipeline. Live pipeline health is public at
[GET /v1/status](https://greyhoundapi.com/documentation/api-status).

---

## Historical data

*What history GreyhoundAPI holds, which detail lives inside results versus dedicated endpoints, and how to bulk-import the archive for analysis or model training.*  
`https://greyhoundapi.com/documentation/historical-data`

A live key sees the full archive — every race, runner and result on record, with no rolling
window. (Free [sandbox](https://greyhoundapi.com/documentation/sandbox) keys see
yesterday, today and ahead only.) There are two ways to work with it: pull the
archive in bulk, or look one dog up at a time.

Full means every race we hold, which is not every race that runs — see
[What we cover](https://greyhoundapi.com/documentation/coverage) for the regions, and the
[racing calendar](https://greyhoundapi.com/racing-calendar) for which tracks appear on which days.

### Most detail already lives in the result

You rarely need to stitch endpoints together. Each finisher in a result already carries its
timing, price and running detail, so a single pass over `/results` gives you almost
everything at once:

| Field | What it is |
| --- | --- |
| `position`, `trap` | Finishing position and trap/box |
| `sectional_s` | Sectional — the split to the first bend |
| `run_time_s`, `adjusted_time_s` | Overall time, and the going-adjusted time |
| `btn` | Beaten distance (raw, and in lengths) |
| `sp` | Starting price (fractional and decimal) |
| `betfair` | Betfair market summary |

Greyhound racing records a *single* sectional — the split to the first bend — not a
series of per-section splits as in horse racing. So `sectional_s` is the split figure;
there is nothing more granular, which is the nature of the sport rather than a gap in the data.

### Bulk import — walk the results archive

The efficient way to build an analysis set or a training corpus is to page `/results`
across a date range and follow the cursor. Narrow it with `region`, `track`,
`dog_id`, `trainer_id` or `owner_id` as needed.

```
curl "https://api.greyhoundapi.com/v1/results?date_from=2024-01-01&date_to=2024-12-31&limit=200" \
  -H "X-API-Key: $GAPI_KEY"

# then follow meta.next_cursor until it is absent:
curl "https://api.greyhoundapi.com/v1/results?date_from=2024-01-01&date_to=2024-12-31&limit=200&cursor=..." \
  -H "X-API-Key: $GAPI_KEY"
```

Use `limit=200` and the cursor — not page numbers — for a full pull; see
[Pagination](https://greyhoundapi.com/documentation/pagination). Keep an eye on your
[rate limits](https://greyhoundapi.com/documentation/rate-limits) and back off on a `429`. For the
racecard side of the archive (entries rather than results), `/races` pages the same way.

### One dog at a time

When you want a single animal's history rather than the whole archive, these return it directly,
with no client-side joining:

| Endpoint | Returns |
| --- | --- |
| `GET /dogs/{dog_id}/form` | Career form — each past run with position, times and SP |
| `GET /dogs/{dog_id}/prices` | Starting price and Betfair for each of the dog's runs |
| `GET /dogs/{dog_id}/head-to-head/{rival_id}` | Every race the two dogs contested, who finished ahead each time, and a career summary for both |
| `GET /races/{race_id}/market` | The Betfair market for one race |

The same career form is also reachable by filtering `/results` with `dog_id`,
so for a large model you can usually stay on the bulk endpoint and skip the per-dog calls entirely.

---

## Sandbox tier

*The free GreyhoundAPI key: today's cards and results, the days ahead and the last few days, across every GB, AU and IE track. Prices, weights and weather are on the paid plans.*  
`https://greyhoundapi.com/documentation/sandbox`

The sandbox is a free key for seeing the API's shapes and testing your integration. No card,
no trial clock. It is deliberately scoped — a small, read-only slice of the product, not a free
version of it.

### What a sandbox key can do

The races and tracks endpoints, and only these:

- **Races** — list (`GET /v1/races`), view a race
(`GET /v1/races/{race_id}`) and its runners (`/runners`).

- **Tracks** — list (`GET /v1/tracks`), view a track
(`GET /v1/tracks/{track_id}`), its races (`/races`) and its
statistics (`/stats`).

List endpoints accept `region=GB` or `region=AU`. Try the listings on the
[sandbox page](https://greyhoundapi.com/sandbox); race detail and track stats run from their docs pages.

### The limits, and why

- **Every track is included**: the full GB, AU and IE fixture list, not a sample. The
sandbox used to cover three tracks; that restriction was removed on 2026-08-20 because it stopped
people finding the course they came to look up, while protecting nothing the window does not.

- **Yesterday, today and ahead**: cards and results,
counted in whole UTC days. Older racing is on Live.

- **60 requests/minute**, capped at
50 a day.

### Everything else needs a live key

Any endpoint other than the two above — results, sectionals, starting prices, Betfair markets,
dogs, trainers, owners, breeding, racecards and the WebSocket stream — returns
`403 upgrade_required` on a sandbox key. Races outside the sandbox window are excluded
too. Live-day data, the deep archive, push webhooks and the stream are what the
[live plan](https://greyhoundapi.com/pricing) is for — the limit is *when*, not *where*.

---

## MCP server for Claude and ChatGPT

*Connect GreyhoundAPI to Claude, ChatGPT or any Model Context Protocol client and ask about GB and Australian greyhound racing in plain English. How to set it up, which tools the assistant gets, and how it decides which to call.*  
`https://greyhoundapi.com/documentation/mcp-server-claude-chatgpt`

The Model Context Protocol is a standard way for an AI assistant to call an external
service. Point Claude or ChatGPT at our MCP server and racing data becomes something it
can look up mid-conversation, rather than something you have to fetch and paste in.

There is nothing to install and nothing to run. The server is hosted at
`https://greyhoundapi.com/mcp` and authenticates with the same API key
you already use. [The overview page](https://greyhoundapi.com/mcp) covers the pitch; this guide is the
detail.

### Why bother

- **No integration to write.** The 23 tools describe themselves, so the
assistant works out which one answers your question and how to fill in its arguments.

- **Names resolve themselves.** Ask about a dog by name and the assistant calls
`find_dog` first to get an id, then the tool that needs it. You never see the
two-step.

- **It is your key.** Same tier scope, same rate limits, same quota. Nothing is
bypassed and nothing is billed differently.

- **Good for exploring.** Working out whether the data suits you takes minutes in a
chat window rather than an afternoon of writing throwaway scripts.

### Connect it in Claude

Settings → Connectors → **Add custom connector**.

- URL: `https://greyhoundapi.com/mcp`

- Authentication: **None**. This is the step that catches people — Claude also
offers two OAuth options and either will fail to connect. This server uses an API key
header, not OAuth, and Claude normally detects that and marks *None* as
*Detected*.

- Under *Additional request headers*, add `x-api-key` with your key as
the value.

- Transport: leave it. *Streamable HTTP* is set from the URL and is correct.

Other clients work the same way: that address, and your key in an `x-api-key`
header. `Authorization: Bearer <key>` is accepted too, for clients that only
offer bearer tokens.

### What the assistant actually does

Two calls, both JSON-RPC 2.0 over HTTP `POST`. On connecting it asks what
exists:

```
curl -X POST https://greyhoundapi.com/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

Then, when your question needs data, it calls one:

```
{
  "jsonrpc": "2.0", "id": 2, "method": "tools/call",
  "params": {
    "name": "find_races",
    "arguments": { "date_from": "2026-08-24", "date_to": "2026-08-24", "region": "GB" }
  }
}
```

The server forwards that to the public REST API with your key, so every gate that applies
to a normal request applies here too. The answer comes back as text the assistant reads,
prefixed with anything worth knowing — how many results were truncated, how fresh the
data is, or why a list came back empty.

### The tools

23 of the 62 endpoints are exposed, chosen
because they match how people ask questions. A model given every endpoint picks badly, and
most clients show the list in a picker. Anything not here is still reachable through the
REST API.

| Tool | Endpoint | Plan | What it answers |
| --- | --- | --- | --- |
| `todays_racecards` | [`/v1/racecards/today`](https://greyhoundapi.com/documentation/racecards-today) | Free | Every race card scheduled today across Great Britain and Australia, with runners, trap draws and scheduled off times. |
| `upcoming_races` | [`/v1/racecards/upcoming`](https://greyhoundapi.com/documentation/racecards-upcoming) | Free | Races due off shortly, nearest first. Use this for "what is running next". |
| `latest_results` | [`/v1/results/latest`](https://greyhoundapi.com/documentation/latest-results) | Free | The most recently settled races, newest first, with finishing order and times. |
| `find_races` | [`/v1/races`](https://greyhoundapi.com/documentation/list-races) | Free | Search the race archive by date range, region, track, grade, distance or status. The general-purpose way to locate races. |
| `get_race` | [`/v1/races/{race_id}`](https://greyhoundapi.com/documentation/get-race) | Free | One race in full: conditions, going, distance, grade and its runners. |
| `get_race_result` | [`/v1/races/{race_id}/result`](https://greyhoundapi.com/documentation/race-result) | Live | The finishing order for one race, with times, sectionals and starting prices where published. |
| `dog_layoff` | [`/v1/dogs/{dog_id}/layoff`](https://greyhoundapi.com/documentation/dog-layoff) | Live Plus | How a greyhound has run after different lengths of time off, beside how greyhounds generally run after the same break. Rates are suppressed where the dog has too few runs; counts are always shown. |
| `dog_trend` | [`/v1/dogs/{dog_id}/trend`](https://greyhoundapi.com/documentation/dog-trend) | Live Plus | Whether a greyhound is running quicker or slower lately, each run measured against par for that track and distance, with the change shown beside the dog's own run-to-run variation so noise is not mistaken for a trend. |
| `meeting_analysis` | [`/v1/meetings/{meeting_id}/analysis`](https://greyhoundapi.com/documentation/meeting-analysis) | Live Plus | A whole card as one unit: how fast the track actually ran against par for each distance, and whether the published going agreed. Trap wins are counts only — a single card cannot show bias. |
| `race_precedents` | [`/v1/races/{race_id}/precedents`](https://greyhoundapi.com/documentation/race-precedents) | Live Plus | Races from the archive that ran under matching conditions at this track and distance, with what happened in them. Each row says what it matched on and where it differs. History, never a forecast. |
| `race_field` | [`/v1/races/{race_id}/field`](https://greyhoundapi.com/documentation/race-field) | Live Plus | Every runner in one race with its record at this track and trip, its grade record, days since it last ran and how it has run off that break, beside the strike rate of the trap it has drawn. Replaces a call per runner per measure. Measurements only, never a forecast; runners short of history are named, not omitted. |
| `race_analysis` | [`/v1/races/{race_id}/analysis`](https://greyhoundapi.com/documentation/race-analysis) | Live Plus | Conditions and early pace for one race in a single call, joined per runner — trap record, winning times, field experience and each runner's early split with its sample size. Measurements only, never a forecast. |
| `race_pace` | [`/v1/races/{race_id}/pace`](https://greyhoundapi.com/documentation/race-pace) | Live Plus | Each runner's measured early split from past runs at this track and distance, with the run count behind every figure. Runners with too little data are named, not omitted. Measurements only, never a forecast. |
| `race_conditions` | [`/v1/races/{race_id}/conditions`](https://greyhoundapi.com/documentation/race-conditions) | Live Plus | The context one race runs in: trap strike rates at this track and distance, typical winning time, and how experienced the field is. Every figure carries its sample size. Historical context, never a forecast. |
| `get_race_market` | [`/v1/races/{race_id}/market`](https://greyhoundapi.com/documentation/race-market) | Live | Market data for one race: each runner's starting price and the best Betfair back and lay prices taken seconds before the off. Factual prices only, never a prediction. |
| `find_dog` | [`/v1/dogs/search`](https://greyhoundapi.com/documentation/search-dogs) | Live | Find a greyhound by name. Use this first when someone names a dog, to get its dog_id. |
| `get_dog_form` | [`/v1/dogs/{dog_id}/form`](https://greyhoundapi.com/documentation/dog-form) | Live | A greyhound's recent runs — the form line: date, track, distance, grade, finishing position, time and price. |
| `compare_dogs` | [`/v1/dogs/{dog_id}/head-to-head/{rival_id}`](https://greyhoundapi.com/documentation/dog-head-to-head) | Live | Every time two greyhounds have met, with how each finished. Needs both dog ids. |
| `dog_statistics` | [`/v1/dogs/{dog_id}/stats`](https://greyhoundapi.com/documentation/dog-stats) | Live Plus | A greyhound's record split by distance, track, trap, grade and going — what tends to happen rather than what happened once. |
| `find_trainer` | [`/v1/trainers/search`](https://greyhoundapi.com/documentation/search-trainers) | Live | Find a trainer by name to get their trainer_id. |
| `trainer_statistics` | [`/v1/trainers/{trainer_id}/stats`](https://greyhoundapi.com/documentation/trainer-stats) | Live Plus | A trainer's strike rate split by track, distance, trap and grade over a rolling window. |
| `list_tracks` | [`/v1/tracks`](https://greyhoundapi.com/documentation/list-tracks) | Free | Every track covered, with region and the distances raced there. |
| `track_statistics` | [`/v1/tracks/{track_id}/stats`](https://greyhoundapi.com/documentation/track-stats) | Free | Trap bias and winning-time distribution for a track, by distance. |

7 of them work on a free [sandbox](https://greyhoundapi.com/documentation/sandbox) key,
within its data window. The rest return a plain-English message telling the assistant which
plan is needed, rather than a raw `403` for it to misreport.

### Limits

An assistant answering one question can fire several tool calls in a few seconds, which
trips a per-minute limit a human never would. When that happens the server says so in words
— that it is a pace limit and not an outage — so the model waits rather than
telling you the API is broken. Your [rate limits](https://greyhoundapi.com/documentation/rate-limits) are
otherwise unchanged.

### What this is not

It returns factual race data: results, times, sectionals, form and published prices. There
are no predictions, ratings or selections in it, and the server tells the assistant so on
connecting. A model can still *reason* out loud about what it fetched — that is
the model doing it, not the data, and it is not betting advice.

---

## Live odds

*Best Betfair back and lay prices, when they arrive, how often they update, and what the numbers do and do not mean.*  
`https://greyhoundapi.com/documentation/live-odds`

Every runner can carry a `betfair` block: the best available back and lay price
from the Betfair exchange, captured in the minutes before the race goes off.

```
"betfair": {
  "selection_id": 84512233,
  "back_price":   3.55,
  "lay_price":    3.60,
  "last_update":  "2026-09-02T08:44:56Z"
}
```

`back_price` is the best price you could have backed at; `lay_price`
is the best price you could have laid at. Both are decimal odds, and both are taken from the
top of the order book at the moment of capture.

### When prices arrive

Capture starts about **three minutes before the scheduled off** and refreshes
every few seconds until shortly before the traps open. Before that window a race has no
prices at all — the exchange market exists, but there is rarely enough money in it for a
price to mean anything.

The practical consequence: `betfair` is `null` on a race that is more
than a few minutes away, and populated on one that has just run. If you are polling a card in
the morning, expect nulls; poll again near the off.

### How current they are

`last_update` is the moment that price was read from the exchange, not the moment
you called the API. Trust it over your own clock — it is the only honest measure of how
stale a number is:

```
# how old is this price, in seconds?
curl -s -H "X-API-Key: $KEY" \
  https://api.greyhoundapi.com/v1/races/1541377233/market \
  | jq '.data.runners[] | {dog: .dog_name, back: .betfair.back_price,
      age_s: (now - (.betfair.last_update | fromdate))}'
```

Prices reach this API within a second or two of being read, so a few seconds of age is
normal and expected. A price several minutes old means the race has already run — that is
the final capture before the off, which is usually the number people want.

### The last price before the off

Once a race has run, `back_price` and `lay_price` hold the last values
captured, typically a handful of seconds before the traps opened. That makes them a useful
companion to the official starting price in `sp`: one is the bookmaker return, the
other is what the exchange was actually paying at the same moment. They frequently disagree,
and the difference is the interesting part.

### What these numbers are not

- **Not a full order book.** You get the best price on each side, not the
depth behind it, and not the size available at that price.

- **Not a price history.** Each capture overwrites the last, so you get the
most recent price, not how it moved. If you need drift, poll and store your own series.

- **Not a prediction.** A short price reflects what other people are willing
to bet, nothing more. This API reports market data; it does not rate runners, forecast
results or suggest bets.

- **Not guaranteed present.** Coverage depends on a market existing and having
prices during the capture window. Withdrawn runners carry no price at all.

### Where to find them

The `betfair` block appears wherever a runner does — race cards, results,
and career form — so historical prices come back with the run they belong to. For a single
race, `GET /v1/races/{race_id}/market` returns every runner\'s prices together with
the exchange market id.

Always test for presence rather than assuming:

```
const bf = runner.betfair;
if (bf?.back_price) {
  // a real price, captured at bf.last_update
} else {
  // no market, outside the capture window, or a withdrawn runner
}
```

---

## What we cover

*Which tracks are in the archive, how often each races, and where the edges are.*  
`https://greyhoundapi.com/documentation/coverage`

Great Britain and Australia. Every meeting we hold is on the
[racing calendar](https://greyhoundapi.com/racing-calendar) — by track, by day, for the last thirty days.
That page is the coverage itself rather than a description of it, so you can check before
you build.

### Great Britain

Every British track racing under rules — the whole circuit, not a selection.
A British track races most days, so the calendar is close to solid.
Results usually arrive within a few minutes of the off; the
[data freshness](https://greyhoundapi.com/documentation/data-freshness) guide covers the timing and
what `results_pending` means.

### Australia

Tracks across Australia — Sandown Park, The Meadows, Shepparton, Ballarat, Bendigo,
Geelong, Wentworth Park, Cannington and the rest. They race roughly weekly rather than daily,
which is why the Australian half of the calendar looks sparser than the British half.

The [racing calendar](https://greyhoundapi.com/racing-calendar) shows every track we hold and how often
it runs, so you can check a specific one before you build on it.

### Fields that are not always present

- **Sectional times.** About two thirds of British runs and about half of Australian
ones. Where the timing gear did not record a split, the field is `null`
rather than zero. The split is timed to a **fixed point on the track**, not to a fixed
distance from the traps, so it is only comparable within the same track and the same
distance — a 500m split and a 460m split at the same course measure journeys forty
metres apart and will differ by seconds.

- **Going.** British racing only, on about 95% of races. It is a signed
hundredths-of-a-second track adjustment, not a word: negative is a slower track.
Australian racing does not publish it.

- **Starting price.** Present for both regions. Australian prices are fixed-odds
rather than a returned SP.

- **Betfair lay price.** Captured from 2 September 2026 onward, on about 94% of
runners. Earlier runs carry `null` because the lay side was not recorded
at the time — it is a missing recording, not a market without a lay. The Betfair
**back** price and the starting price both run the full length of the archive.

### Checking for yourself

Every figure above is visible in the data. `GET /v1/tracks` lists what is
covered, and the [calendar](https://greyhoundapi.com/racing-calendar) shows how often each one races. If
something you need is missing, [open a ticket](https://greyhoundapi.com/support) — knowing what
people are asking for is how the next region gets added.

---

## The whole API in one file

*Every endpoint, parameter, sample and guide as a single markdown document, for offline reading or for pasting into an assistant.*  
`https://greyhoundapi.com/documentation/manual`

Everything on this site, assembled into one markdown file:

[Download the complete reference →](https://greyhoundapi.com/documentation/manual.md)

Around 160KB, which is roughly forty thousand tokens — small enough to paste
whole into Claude, ChatGPT or anything else with a reasonable context window, and
still leave room for it to answer with.

### What is in it

- Every endpoint, with its method, path, tier, parameters and a full sample response.

- Every guide on this site, in full — the conventions, the edge cases and the
things that are easy to get wrong.

- Base URL, authentication, rate limits, prices and coverage.

### Why it cannot be out of date

It is not written, it is **generated**. The endpoint list comes from the same
definition the API routes on, the guides from the same source these pages render
from, and the limits from the same configuration the billing uses. Add an endpoint
and it appears here on the next request. There is no separate copy to forget to
update, which is the usual reason a manual like this is wrong within a month.

The date at the top is the date the underlying definition last changed, not the
date you downloaded it — so two downloads with no release between them are
byte-for-byte identical, and a diff shows you only what actually moved.

### For assistants

There is also [llms.txt](https://greyhoundapi.com/llms.txt), a short index that points at the
manual and lists every guide and endpoint with a one-line description. It follows
the emerging convention for making a site legible to language models: read the
index first, fetch the detail if needed.

If you would rather your assistant query the live API than read about it, the
[MCP server](https://greyhoundapi.com/documentation/mcp-server-claude-chatgpt) does that directly,
and the [OpenAPI description](https://greyhoundapi.com/openapi.json) is available for tooling
that expects it.

### Licence and fair use

The document is free to read, copy and feed to whatever you like. The data behind
it still needs a key, and the terms that apply to the API apply to anything you
build from this. Nothing in it is a prediction, a rating or a tip — this API
publishes facts about races that have happened and cards that are scheduled.

---

## WebSocket stream

*Stream greyhound results and race status changes over wss://: channels, the subscribe protocol, the replay window and connection limits.*  
`https://greyhoundapi.com/documentation/websocket`

Live keys can hold up to 2 connections to
`wss://greyhoundapi.com/v1/stream`. Sandbox keys have no stream.
Present your key on the upgrade in whichever way suits your client: the
`X-API-Key` header (or `Authorization: Bearer`) for server-side
clients, or — since browsers can't set handshake headers — as a WebSocket subprotocol:

```
// browser: pass a marker protocol and your key
const ws = new WebSocket('wss://greyhoundapi.com/v1/stream',
  ['gapi-stream', 'gapi_live_yourkey']);
```

Subscribe by channel — topic `{results|status|cards}` and region
`{GB|AU|*}`:

```
{ "action": "subscribe",
  "channels": ["results:GB", "status:*", "cards:AU"] }
```

### Events

`result.received` when a result lands in the pipeline;
`race.status_changed` on off / results_pending / void / abandoned transitions;
`card.updated` for runner changes on upcoming races.

```
{ "event": "result.received", "channel": "results:GB",
  "race_id": 1229082, "track": "Romford",
  "results_delay_s": 1060, "ts": "2026-07-08T21:03:41Z" }
```

### Reconnecting

Heartbeat ping every 30s. On reconnect, send
`{"action":"replay","channel":"results:GB","since":"<iso>"}` to replay the
last 15 minutes; for anything older, catch up over REST with
[GET /v1/results/latest?since=](https://greyhoundapi.com/documentation/latest-results).

### Close codes

The gateway closes with `4401` (unknown, revoked or expired key),
`4403` (sandbox key, or a live key suspended mid-session — you'll get a
`{"event":"closing","reason":…}` frame first) or `4408` (you already
hold the maximum 2 connections). A suspended or expired key is
dropped within a minute, not just refused on the next connect.

---

## Webhooks

*Push race status changes and result publication to your own HTTPS endpoint: events, the signed payload, retries and how to verify a delivery.*  
`https://greyhoundapi.com/documentation/webhooks`

Webhooks push events to an HTTPS endpoint you control, so you don't have to poll. They carry the
same events as the [WebSocket stream](https://greyhoundapi.com/documentation/websocket) — pick whichever suits you:
a long-lived socket, or plain HTTP callbacks your server already knows how to accept. Live keys only;
endpoints are registered on request today (send us your URL and the events/regions you want), with
self-serve management coming to your dashboard.

### Events

- `result.received` — a race has completed and its result landed.

- `race.status_changed` — a race moved to a new lifecycle status (carries `status` and `previous_status`).

- `card.updated` — a new upcoming card, or a runner change on one.

Choose region `GB`, `AU` or both per endpoint.

### The delivery

Each event is a single HTTPS `POST` with a JSON body and these headers:

```
Content-Type: application/json
X-GAPI-Event: race.status_changed
X-GAPI-Delivery: 84213
X-GAPI-Signature: sha256=9f2a…
```

```
{ "event": "race.status_changed", "channel": "status:AU",
  "race_id": 3661572768, "track": "Warragul",
  "status": "off", "previous_status": "scheduled",
  "scheduled_start": "2026-08-04T09:25:00Z", "ts": "2026-08-04T09:25:03Z" }
```

### Verify every delivery

We sign the raw request body with your endpoint's secret using HMAC-SHA256. Recompute it and
compare in constant time against `X-GAPI-Signature` before trusting a payload:

```
$secret   = "whsec_…";
$expected = "sha256=" . hash_hmac("sha256", file_get_contents("php://input"), $secret);
if (!hash_equals($expected, $_SERVER["HTTP_X_GAPI_SIGNATURE"] ?? "")) { http_response_code(401); exit; }
```

### Retries & delivery guarantees

Respond `2xx` to acknowledge. A `5xx`, timeout or network error is retried
with backoff (roughly 1m, 5m, 15m, 1h, 6h) for up to six attempts; a `4xx` is treated as a
permanent rejection and not retried, so keep your endpoint returning `2xx` even when you
choose to ignore an event. Delivery is at-least-once — the same event can arrive again on a retry, so
dedupe on `X-GAPI-Delivery`, which is stable per delivery.

### Result completeness

A `result.received` fires when a race first completes; because placings can arrive after
the winner, read `result_status` on the race (see [Data
freshness](https://greyhoundapi.com/documentation/data-freshness)) to tell a provisional result from a final one.

---

## Browser extension

*A Chrome extension that watches API status and server ping from your toolbar, with desktop alerts when the results pipeline falls behind.*  
`https://greyhoundapi.com/documentation/browser-extension`

If you rely on GreyhoundAPI in production, our free Chrome extension keeps an eye
on it for you. It lives in your toolbar, pings the server and reads the live status on an
interval you choose, and raises a desktop notification the moment the results pipeline falls
behind — so you hear about a problem before your users do.

Everything it shows comes from two endpoints you already have:
[GET /v1/status](https://greyhoundapi.com/documentation/api-status) for pipeline freshness and per-region
result delays, and `GET /v1/ping` for server reachability and latency. Your key is
used only for the status call, and it never leaves your browser's own local storage.

### What it surfaces

- A colour-coded toolbar badge — green when healthy, amber with a count when something is slow, red when the API is unreachable.

- Server ping latency and the age of the last successful sync.

- Per-region races awaiting results, with the 24-hour median and p95 result delay.

- A desktop alert when a new issue appears — once per issue, not on every check.

### Tune it to your tolerance

Every threshold is adjustable and saved locally: how often it polls, how stale a sync may get
before it warns, the awaiting-results ceiling and the maximum acceptable ping. Set them to match
how sensitive you need the alerting to be.

**[Download the extension and the install guide →](https://greyhoundapi.com/browser-extension)**
It loads in about a minute and works in Chrome, Edge, Brave and other Chromium browsers.

---

## Changelog

*What changed and when: new endpoints, fields and platform features. Additive changes only within v1.*  
`https://greyhoundapi.com/documentation/changelog`

Dates are UTC. Within v1 the contract only gains fields — nothing documented here is
removed or renamed without a version bump. Machine-readable spec:
[OpenAPI 3.1](https://greyhoundapi.com/openapi.json).

### 2026-09-23 — Race Day: today and yesterday, with the prices on

- A new plan at $49 a month. Race Day covers yesterday, today and the cards ahead at every track, with each day counted in that track's own timezone so an Australian card arrives whole. It carries the fields the free key holds back: starting prices, Betfair back and lay, runner weights, race weather and forecast and tricast dividends, plus the results stream and webhooks.
- It reaches 20 endpoints: the cards and results for its days, a race's runners, market, result and status, and the platform and reference endpoints every key has. 120,000 requests a month at 90 a minute. The full list is on the Race Day plan overview in the documentation.
- Why it exists. Most people building on greyhound data want the prices on today's racing and nothing behind it: a racecard screen, a results bot, a tool that places bets. Until now the only way to get prices was Live at $99, which bundles the whole archive back to 2018. Race Day sells the day on its own.
- The trade-off is depth. Race Day holds two days at a time. Racing before yesterday, a dog's form and past runs, odds history and the statistics endpoints stay on Live and Live Plus. Asking for an older date is refused with outside_plan_window, naming the window and the plan that carries it, rather than answered with an empty page that would read as "no racing that day". Moving up keeps the same key, with nothing to re-integrate.

### 2026-09-23 — Free key: yesterday, today and ahead, 50 lookups a day

- A free key now reads the same days as Race Day: yesterday, today and the cards ahead, across every GB, IE and AU track. Today's and upcoming racecards and today's, yesterday's and the latest results are on it, beside the race and track listings it already had: 17 endpoints in all.
- Cards and results come through in full: traps, dogs, trainers, owners, grades, distances, going, scheduled and off times, run times, sectionals, comments and finishing order. Starting prices, exchange prices, runner weights and race weather come back empty, each with a note in its place naming the plan that carries it, so a response is never short of a field without saying why.
- What changed, and why. The free key reached back 7 days at 500 requests a day. That was enough to run a small product on without ever paying, which is not what a free key is for. It now reaches back to yesterday at 50 lookups a day: enough to see every field, build against real racing and judge the data, not enough to run on it.
- The trade-off for older data. A free key no longer reads the week behind it: anything before yesterday is Live, which carries the full archive back to 2018, and the prices on the free key's own days are Race Day. Existing free keys keep working unchanged; they see the new window and allowance from today, and the daily count resets at 00:00 UTC.

### 2026-09-22 — Coverage ledger

- New on every key, the free one included: GET /v1/coverage?date=. For each track and day, how many races were carded, run, resulted and abandoned, and how many Betfair's exchange listed that we never received. It answers "missing or abandoned?" before anyone has to ask.
- The independent list is Betfair's own catalogue of greyhound WIN markets, collected every 15 minutes. British days from December 2022 to June 2025 are checked against the recorded history of that list; every day from today is checked live.
- A day with no list says so: listed and missing come back null, never zero, because nothing was checked. Venues we do not carry are shown but never count as missing.
- Not held to the free key's date window: it carries counts, not racing data.

### 2026-09-22 — Sire and dam statistics

- New on Live and Live Plus: GET /v1/sires and GET /v1/dams. Every parent we hold figures for, with the whole record of its offspring: runs, strike rate, wins against what the market expected, how often they lead to the first bend and how their debuts go. Searchable by part of a name and sortable by any of those measures.
- New on Live Plus: GET /v1/sires/{name}/stats and GET /v1/dams/{name}/stats. One parent's figures split by region, distance, trap, age and track, with the ten biggest winners.
- Every sire and dam has its own id, sire_id and dam_id, on every row of the lists. The statistics take it in the path: /v1/sires/{sire_id}/stats. The ids stay the same from one weekly rebuild to the next.
- GET /v1/sires/{sire_id}/progeny and GET /v1/dams/{dam_id}/progeny now take the id instead of the name. A name with a space in it was refused before it reached the API, so for most parents these were not answering at all. They also now count every spelling on record as one parent.
- Coverage is Great Britain from 2018 and Ireland from 2026. Australian dogs carry no breeding at all, so none of their runs are counted. Irish racing before 2026 is left out because breeding is recorded for only part of those fields. Every response lists what was counted in meta.coverage, and a year joins by itself once 95% of its runs have breeding on record.
- A figure that cannot be measured comes back null, never 0, with the count behind it: no priced races means no market comparison, not a bad one.
- The figures are rebuilt weekly.

### 2026-09-21 — Race write-ups

- New in Race commentary on live_plus: a written preview and report for each race, with a line on every runner, written from the records.
- GET /v1/races/{race_id}/writeup returns one race. GET /v1/writeups/upcoming lists the previews for races in the next 1 to 48 hours; GET /v1/writeups/results lists complete write-ups for races run in the last 1 to 168 hours, or on one date.
- Previews are written before the off, from the field and each dog's form, so they never know the result. The report is added once the race has run, and the preview is kept as it was.
- Runner comments, race write-ups and the comment vocabulary now share one section, Race commentary.

### 2026-09-21 — Runner comments, in their own section

- New section on live_plus: GET /v1/races/{race_id}/comments. How each runner ran, as events with the bend or point of the race they happened at, and a one-line summary in plain English.
- Great Britain and Ireland describe the race as it was run. Australian racing carries the view before the off instead: a verdict on each runner's chance and the traits behind it.
- GET /v1/comments/vocabulary lists every event, place, verdict and trait the section can return, with its wording, so nothing has to be guessed.
- A remark we cannot describe with confidence is left out rather than guessed at, so a runner can come back with comments: null.

### 2026-09-18 — The weather every race ran in

- Every race now carries a `weather` object: temperature, humidity, wind speed and direction, gusts, rainfall and a plain description of the conditions, for the hour that race went off at its own track. It is on the racecards, on a race, and inside `result` once the race has run.
- New endpoint: GET /v1/races/{race_id}/weather, for the conditions on their own.
- `before_race` is the part worth having. The hour of the off tells you what it was like at the traps; it cannot tell you that six millimetres fell that afternoon, which is what makes a track slow. So each race carries rainfall over the previous 6 and 24 hours and average wind over the previous 6.
- It is hourly reanalysis for the grid point at the track, not a sensor at the bend, and `source` names which model an hour came from: a race in the last day or so reads `forecast` and becomes `archive` once the archive catches up, so the figure can change between the two.
- A race whose start time was never recorded returns `weather: null` with a sentence saying why, rather than a guess. That is now a few hundred Irish races out of 123,000: Great Britain, Australia and Ireland are otherwise complete, back to 2018, 2022 and 2019.
- No parameter to switch it on, and no rating, going estimate or adjustment applied to any time we publish. See the Weather guide.

### 2026-09-18 — Irish racing back to 2019

- Irish coverage now starts in 2019 rather than 2022. Races, runners, finishing positions, winning and estimated times, weights, going, prize money, starting prices and the run comment, under the same Irish ids already in use, so nothing you have stored changes.
- They carry a real off time, which took a second pass to get. An Irish RESULT is published with a date and no clock, so the first load of the archive had none: every race sat at midnight on its own date. The racecard for the same meeting does carry the time, and the site still serves those cards years later, so every meeting was fetched again and read for it.
- 121,576 races were given their time this way, which is all but 312 of the archive. What is left is meetings the site has no card for, and races that were carded but never listed on it.
- The time is the carded one, the site's own "Approx." It is when the race was due rather than the moment the traps opened, so it fills scheduled_start and off, and it is accurate to the minute the track published.
- Weather followed from it. An hour can now be matched to an Irish race, so the same weather object GB and AU carry is on Irish racing back to 2019, including rainfall over the previous 6 and 24 hours.

### 2026-09-17 — A lighter racecard

- Today's racecards takes a `summary` parameter. With `summary=true` you get the race name, scheduled time, distance, grade and track — and no runners. It defaults to false, so nothing changes for anyone already calling it.
- A full GB and AU day is around a thousand runners. If you are listing what is on rather than reading a field, that is a lot of payload for nothing.
- It drops the finishing positions too, not just the runners — those were the heavier half. A summary row is about a fifth the size of a full one.

### 2026-09-16 — Australia, not just Victoria

- Coverage is stated as Great Britain and Australia throughout. The pricing page, the calendar, the track reference and the coverage guide all said Victoria only, and the guide named the other states as not covered.
- The racing calendar lists every track we hold and how often it runs, which is the place to check a specific one before building on it.

### 2026-09-15 — The whole field in one call

- New endpoint: GET /v1/races/{race_id}/field, on live_plus. Every runner in a race with its record at that track and trip, its record at that grade, how long since it last ran and how it has gone off breaks of that length, its measured early split, and the strike rate of the trap it has drawn.
- It exists because describing a single race used to take a call per runner per measure. That is a lot of requests to answer one question, and the question is the same one every time.
- The runner and the draw are reported as separate figures, so a record can be read against the box it came out of rather than blended into one number.
- Runners short of history are named in `thin` and kept in `runners`. A field of six reads as six. Dropping a thin runner would change the shape of the race without saying so.
- Measurements only, never a forecast — no rating, no rank, no projected winner, and every figure carries the run count behind it. A value of null means not measured, never zero: a dog that has never run at a grade has not failed there.
- Form state is rebuilt nightly and `form_state_computed_utc` says when. Days since last run is the exception and is computed live against the race you asked about, because a stale gap is a wrong gap.

### 2026-09-10 — See exactly how much data there is, before you pay for it

- New endpoint: **[`GET /v1/archive`](https://greyhoundapi.com/documentation/archive)**. It answers the first question anyone asks &mdash; how much is actually in here. Races and runners by track and distance, the earliest and latest date, and how many dogs, trainers, owners, meetings, pace profiles and trap tables are known.
- Read **`races`** and **`coverage`** together. `races` is how much there is; `coverage` is how much of it carries the field you need. Exchange prices exist on roughly two thirds of races and are concentrated in recent years, so a strategy priced on the exchange has less to work with than the headline count suggests. Coverage varies per distance within a single track, and the endpoint reports it that way.
- Recomputed nightly from completed races, so it costs one query a night rather than a count per caller. A `count` of `null` means that figure was not computed on the last run, which is not the same as zero.
- Why we built it: we were answering "how much data do you have" with a sentence on the pricing page. Now it is a number you can check, in the same spirit as publishing the [trap-bias figures](https://greyhoundapi.com/greyhound-trap-bias) rather than asserting them.

### 2026-09-10 — The whole platform section is now on the free key

- A free key now reaches **11 endpoints instead of 7**. The four added are [Pipeline status](https://greyhoundapi.com/documentation/api-status), [Your usage](https://greyhoundapi.com/documentation/usage), [All statistics](https://greyhoundapi.com/documentation/archive) and [Reference data](https://greyhoundapi.com/documentation/reference).
- These were behind the same wall as the racing data, which never made sense. `/v1/status` and `/v1/usage` describe your own key and our pipeline; `/v1/reference` decodes grades, going codes and runner statuses; `/v1/archive` says how much data exists.
- Withholding them meant a free key could not answer "is the feed live", "how many calls have I used", "what does grade A3 mean" or "how much data is there" &mdash; the questions you ask while deciding whether to pay. Answering those with a 403 was a poor argument for the product.
- Nothing changes for existing keys except that these four now work on sandbox tier as well. The [sandbox](https://greyhoundapi.com/sandbox) page lists the current set.

### 2026-09-07 — Fetch up to 50 records in one call

- List endpoints now take an **`ids`** parameter: `GET /v1/races?ids=A-B-C` returns all three in one response. Available on [races](https://greyhoundapi.com/documentation/list-races), [results](https://greyhoundapi.com/documentation/search-results), [dogs](https://greyhoundapi.com/documentation/search-dogs) and [tracks](https://greyhoundapi.com/documentation/list-tracks).
- If you are calling a single-record endpoint in a loop, this replaces it. Up to 50 ids, dash or comma separated. Every other filter still composes, so `?ids=A-B-C&status=complete` narrows the batch.
- **Each id costs one call** &mdash; a batch of fifty is charged as fifty, the same as fetching them one at a time. Batching saves you round trips and latency, not allowance. Charging it as a single request would let a key pull fifty times its limit, which would make the limit meaningless.
- An id with no record refuses the **whole request** with `400 invalid_id`, naming it. That is deliberate: returning the 49 that worked gives you a response that looks complete and is not, and the gap turns up later in something you have already shipped.
- Use the [cursor](https://greyhoundapi.com/documentation/pagination) instead when you are walking a set rather than naming one. Full details in [batch requests](https://greyhoundapi.com/documentation/batch-requests).

### 2026-09-07 — Check our data before you pay for it

- New **[Data](https://greyhoundapi.com/data-integrity)** page, and a **[Data integrity](https://greyhoundapi.com/documentation/data-integrity)** guide in the docs. Both let you look up any date and see exactly what we hold for it &mdash; races carded, results recorded, and anything without a result, with the reason.
- Both are powered by a new **open endpoint**: `GET /data-integrity.json`. No key, no quota, no rate limit. It returns aggregate day counts, takes `from`, `to` and `region`, and accepts `summary=1` for archive totals or `group=month` for a longer view. Wire it into your own monitoring if you want to watch our coverage without asking us.
- `complete + abandoned + unresolved` always equals `races`. Completeness is measured against races that actually **ran**, so an abandoned meeting cannot flatter the figure.
- Why we built it: a feed that is short on a given day is indistinguishable from a day when racing was called off, unless someone tells you which. Rather than asking you to take our word for it, the counts are public and checkable.

### 2026-09-07 — Races that will never have a result now say so

- Some races are carded and never run &mdash; abandoned meetings, void races, cards lost to weather. Until now the API could only say "no result yet", which is indistinguishable from "no result ever" and left clients polling indefinitely.
- Where we have **confirmed against source** that a race has no result and never will, its status is now **`abandoned`**, and [GET /v1/races/{race_id}/result](https://greyhoundapi.com/documentation/race-result) returns **`200`** with the race, no positions, and `meta.result_available: false`. A definitive answer rather than an error.
- Races that simply have not settled yet still return `409` with `retry` &mdash; `true` while the race is still to run, `false` once it has run and stalled.
- Confirmation is never inferred from age. A race is only marked abandoned where the source lists the card without it, or confirms the whole meeting produced no result on any race.

### 2026-09-07 — A race with no result is no longer a 404

- [GET /v1/races/{race_id}/result](https://greyhoundapi.com/documentation/race-result) now returns **`409`** instead of `404` when a race exists but has no result. `404` said the race was missing when it was simply not settled, and that is a different problem needing a different response.
- `404` now means one thing only: no race with that ID. The error code `result_not_available` is unchanged, so anything switching on `error.code` needs no edit.
- The payload adds **`retry`**. `true` means the race has not run yet and polling will succeed eventually; `false` means it ran and was abandoned, voided or never settled, so polling is pointless.
- These responses no longer count toward the error rate on our side either. Asking for a result that is not ready is a reasonable thing for a client to do, not a fault.

### 2026-09-07 — Results searches now tell you what is missing

- [/v1/results](https://greyhoundapi.com/documentation/search-results) adds **`meta.unresolved`** — the number of races that ran inside your query but have no result. Abandoned meetings, void races, cards that were never settled.
- A day returning `total: 130` with `unresolved: 10` means the card was 140 and ten races have no finishing positions anywhere. Previously those races were simply absent, which is indistinguishable from data we had lost.
- Nothing in `data` changes and no existing field moves, so integrations are unaffected. Races scheduled in the last six hours are excluded from the count, so tonight's cards do not inflate it.

### 2026-09-04 — The whole API as one downloadable file

- New at [/documentation/manual.md](https://greyhoundapi.com/documentation/manual.md): every endpoint, parameter, sample response and guide assembled into a single markdown document. About 160KB, roughly forty thousand tokens — small enough to paste whole into an assistant and still leave room for it to answer.
- It is **generated, not written**. The endpoints come from the same definition the API routes on, the guides from the same source the documentation renders from, and the limits from the same configuration billing uses. There is no separate copy to forget to update.
- The date on the document is the date the definition last changed, not the date you fetched it — so two downloads with no release between them are byte-for-byte identical and a diff shows only what actually moved.
- Also new: [/llms.txt](https://greyhoundapi.com/llms.txt), a short index pointing at the manual and listing every guide and endpoint, following the emerging convention for making a site legible to language models.

### 2026-09-03 — Race card with form, in one call

- New endpoint `GET /v1/races/{race_id}/form` on **Live Plus**: the full race card with every runner's recent form attached. Built because `get-race` and `dog-form` are the two most-called endpoints on the platform and they are one question — this race, and how these dogs have been running. Answering it used to cost a call for the race and one per runner.
- Every form line returned happened **before** the race went off, and the race itself is excluded. For a historical race, "recent runs by this dog" would otherwise include what it did afterwards — anyone testing a method against that would be measuring their own lookahead. The cutoff is echoed in `meta.form_cutoff`.
- `runs` controls how many lines per runner: 6 by default, 12 at most. A runner with no prior completed runs returns an empty `form` array rather than an error — that is a first-timer.
- One query, not one per runner. Looping the card server-side would only move the fan-out from your code into ours.

### 2026-09-03 — The racing calendar, and coverage you can check

- New page at [/racing-calendar](https://greyhoundapi.com/racing-calendar) — every meeting we hold, by track and by day, for the last thirty days. It is the coverage itself rather than a description of it, so you can see which tracks are carried and how often they race before you build anything.
- New endpoint `GET /v1/meetings/calendar`, on **every paid key** rather than Live Plus. One row per track per day with the number of meetings and races on each. Answering the same question through `/v1/meetings` meant one call per day; this is one call for the range.
- Dates are each track's own local date, so an Australian evening card lands on the day it ran in Victoria rather than the UTC day it started. Days with no racing are simply absent — a gap is the answer, not a missing row.
- New guide [What we cover](https://greyhoundapi.com/documentation/coverage): the regions, the roughly fourteen Victorian tracks, the states that are **not** covered, and the fields that are not present on every race — sectionals on about two thirds of British runs, going on British racing only.
- The sample response on [List tracks](https://greyhoundapi.com/documentation/list-tracks) showed Wentworth Park, which is in New South Wales and not covered. It now shows a Victorian track.

### 2026-09-02 — Meeting analysis and race precedents

- New endpoint `GET /v1/meetings/{meeting_id}/analysis`, on Live Plus — a whole card as one unit. Every winning time measured against par for its own distance, so a card mixing 400m and 575m is still comparable, with `spread_s` showing whether the surface held through the evening.
- `agreement_s` compares that measurement against the published going, which is a signed hundredths-of-a-second track adjustment. It says whether the official figure matched the racing, and when it disagrees that is a finding rather than an error. GB only — almost no Australian races carry a going value.
- Trap wins on a card are returned as **counts, with no percentage**. One trap taking three of twelve races is 25% against 16.7% expected, and that is indistinguishable from chance. The trap statistics on `/races/{race_id}/conditions` rest on thousands of races.
- New endpoint `GET /v1/races/{race_id}/precedents`, on Live Plus. Races from the archive that ran under matching conditions at this track and distance, with what actually happened in them.
- **There is no similarity score.** A single number hides its own weighting — 0.94 could mean identical except going, or the same track with the wrong grade and field size, and you can neither tell them apart nor re-weight it. Every row carries `matched_on` and `differs_on` instead, so you filter for your own question.
- Matching relaxes when it must, and says so. Of 1,182 distinct track, distance, grade and field-size combinations across two years of GB racing, 407 hold fewer than ten races. Field size drops first, then grade, and `matching.relaxed` names what was given up. Track and distance never relax.
- No aggregate is returned. The frequency of an outcome across these races is history, not a probability — you can compute it from what comes back, but presenting it as a finding would be a claim we do not make.
- Also available as the `race_precedents` tool on the [MCP server](https://greyhoundapi.com/documentation/mcp-server-claude-chatgpt).

### 2026-09-02 — Performance trend, layoff record, and everything about a race in one request

- `GET /v1/dogs/{dog_id}/trend` — whether a greyhound is running quicker or slower lately. Every run is measured **against par**, its time minus the average winning time for that track and distance, so a dry fast night does not read as improvement. Narrow it with `track_id` and `distance_m` to ask whether the dog is improving over one specific trip.
- The change is reported beside `run_to_run_stdev_s`, the dog's own variation. A six-dog field spreads about a quarter of a second, so a 0.06s change across eight runs is noise — and when the change is smaller than the variation, the response says there is no clear trend rather than leaving you to work it out.
- `GET /v1/races/{race_id}/analysis` — conditions and early pace in one request, joined per runner. The same figures as the two endpoints it draws on, assembled so an application stops making three calls and doing the join itself.
- `GET /v1/dogs/{dog_id}/layoff` — how a greyhound has run after a week off, a fortnight, a month, two months. Most dogs race weekly and have very few runs after a long break, so the dog's own figures appear as **counts** with the rate suppressed below five runs, beside a population figure drawn from tens of thousands of runs. That comparison is what makes a small personal number readable.
- `avg_vs_par_s` on the layoff buckets answers what a strike rate cannot: whether greyhounds are actually slower after a break, measured against the average winning time for each course.
- All three are Live Plus. None rates a runner, ranks a field by chance, or forecasts a result.

### 2026-09-02 — Early pace — who gets away quickly, measured rather than judged

- New endpoint `GET /v1/races/{race_id}/pace`, on Live Plus. Each runner's average early split from past runs at this track over this exact distance, with its best, the number of runs behind it, and how it compares to the rest of the field.
- The scope is narrow on purpose. **Same track**: GB splits average 4.84s and AU 7.17s, so they are not the same measurement. **Same distance**: at Romford the split reads 3.86s over 400m, 13.84s over 575m and 24.48s over 750m — it is not a fixed point on the track. **Handicaps excluded**: runners start from different marks, so the run to the split differs per dog.
- Grade is deliberately not in the scope. At Romford over 400m, grade A averages 3.86 and Open 3.78 — eight hundredths apart, against a within-field spread of about a quarter second. Splitting by grade would shrink every sample to buy noise.
- A runner needs three timed runs before a figure is published. Below that it is listed under `without_data` with its count and the reason, because a two-run average looks exactly as authoritative as a twenty-run one and is worth far less. Runners we cannot measure are named, never quietly dropped from the field.
- `vs_field_s` compares a runner against the others we can measure — negative is quicker away. `spread_s` is the gap between quickest and slowest: narrow and they reach the first bend together, wide and someone gets clear.
- Sprints carry no split at all, so those races return an empty list and say why rather than appearing broken.
- Also available as the `race_pace` tool on the [MCP server](https://greyhoundapi.com/documentation/mcp-server-claude-chatgpt).

### 2026-09-02 — Race conditions — the context a race runs in, in one call

- New endpoint `GET /v1/races/{race_id}/conditions`, on Live Plus. How each trap has performed at this track over this exact distance, what a winning time looks like there, and how experienced the field is.
- Everything in it could already be assembled from other endpoints — a track statistics call plus one per runner, then picking the right distance bucket and weighting it yourself. This is that work done once.
- **Every figure carries its sample size.** A trap bias drawn from 40 races is noise and one drawn from 11,000 is signal, and you cannot tell them apart from the percentage alone. Trap percentages are shown against an even break, so a number is readable without counting boxes, and where the sample is thin the response says so.
- Winning times cover a rolling 24 months with average, fastest and standard deviation. Field figures describe the runners' careers to date — average win rate, average runs, and the experience spread across the race.
- Nothing here rates a runner or forecasts the race, and the response says as much rather than leaving it implied.
- Also available as the `race_conditions` tool on the [MCP server](https://greyhoundapi.com/documentation/mcp-server-claude-chatgpt).

### 2026-09-02 — Live odds — best Betfair back and lay price on every runner

- Every runner can now carry a `betfair` block with `back_price`, `lay_price`, `selection_id` and `last_update`. Both prices are decimal odds taken from the top of the Betfair exchange book, and they appear wherever a runner does — race cards, results and career form — so a historical price comes back attached to the run it belongs to.
- For a single race, `GET /v1/races/{race_id}/market` returns every runner's prices together with the exchange market id.
- Prices are captured in roughly the **last three minutes before the off** and refresh every few seconds until the traps open. A race further out than that returns `betfair: null` — the market exists, but there is rarely enough money in it that far ahead for a price to mean anything. Test for presence rather than assuming it is there.
- `last_update` is the moment the price was read from the exchange, not the moment you called us. It is the only honest measure of how stale a number is, and it is worth trusting over your own clock. A few seconds is normal; several minutes means the race has already run and you are looking at the final capture before the off.
- That final price is the interesting one. It sits alongside the official `sp` on results and form, so you can compare what the bookmakers returned against what the exchange was actually paying at the same moment. They disagree more often than you would expect.
- What this is not: it is the best price on each side, not the depth or size behind it; each capture overwrites the last, so you get the current price rather than how it moved; and a short price reflects what other people are willing to bet, nothing more. As with everything here, it is market data — never a rating, a forecast or a suggestion.
- Full detail, including how to poll for a live price without hammering the endpoint, is in the new [Live odds](https://greyhoundapi.com/documentation/live-odds) guide.

### 2026-09-01 — Four fields retired from the runner payload

- The following are no longer returned and are no longer supported: `comment` and `btn` on runners, and `total_volume` and `trade_count` inside the `betfair` block.
- The `comment_codes` decode table has been removed from `GET /v1/reference` for the same reason. That endpoint now returns `regions`, `race_statuses` and `runner_statuses`.
- Every documented sample, the OpenAPI description and the Postman collection have been regenerated so what is published matches what is served.
- If you read any of these fields, they will now be absent rather than null. Code that treats a missing key as an empty value needs no change; code that assumes the key exists does.

### 2026-08-30 — Ask the archive in plain English

- New at [/ai-greyhound-racing-assistant](https://greyhoundapi.com/ai-greyhound-racing-assistant): ask a question about British or Australian racing in ordinary language and get the answer looked up through this API, live, with the tool calls shown as they happen.
- It answers from the data and nothing else. Every figure it gives you came from an endpoint you can call yourself, and it will tell you when a race has not been run rather than guessing at a result. It does not predict, rate runners or suggest bets, and it will decline if you ask it to.
- Ten questions a week on the free plan, 100 on Live, 250 on Live Plus. The API calls behind each answer are on us — your own key and its limits are untouched.
- Conversations are threaded and keep their context, so a follow-up like "and over 500m?" works without repeating yourself. Each answer proposes three follow-up questions, which is usually the harder half of asking a good one.

### 2026-08-28 — MCP server — use the API from Claude or ChatGPT

- An MCP endpoint at `https://greyhoundapi.com/mcp` exposes 15 tools generated straight from the endpoint registry, so what the assistant can call and what the documentation describes cannot drift apart.
- Point Claude Desktop or the OpenAI Responses API at it, authenticate with your existing key, and ask questions in plain language. Your tier applies exactly as it does over REST — a sandbox key reaches sandbox data, a Live Plus key reaches the split statistics.
- Setup, the full tool list and the one configuration field people usually get wrong are in the [MCP server](https://greyhoundapi.com/documentation/mcp-server-claude-chatgpt) guide.

### 2026-08-26 — Errors that tell you what to do about them

- An empty result set now explains itself. Ask a sandbox key for a date outside its window and the response carries `meta.empty_because` naming the date you asked for, the window your key reaches and a date that would have worked — rather than an empty array that looks identical to a genuinely quiet day.
- The window guard says which side of the window you fell off. "Too recent" and "too old" are different problems with different fixes, and the message now distinguishes them and names the dates involved.
- These hints cover `date`, `date_from` and `date_to`. Range queries previously returned empty with no explanation at all, which was the most common way to conclude the data was missing when it was simply out of reach.

### 2026-08-24 — Timezone handling and mobile documentation

- Race times were already returned as both `scheduled_start.utc` and `scheduled_start.local`, but the local value is the track's own calendar date — which is what a date filter matches against. The [Timezones](https://greyhoundapi.com/documentation/timezones) guide now says so explicitly, because an Australian evening card belongs to the following UTC day and that catches people out.
- The documentation reads properly on a phone. Wide reference tables scroll within themselves instead of widening the page, and the endpoint list collapses rather than filling the screen before you reach the content.

### 2026-08-20 — The free sandbox now covers every track

- The sandbox key was scoped to three tracks — Romford and Nottingham in GB, Sandown Park in AU. That restriction is gone: a free key now reaches **every GB and AU track in the API**, the same list a live key sees.
- Nothing else about the free tier changed. It still covers the race and track endpoints only, still serves races between 24 hours and 7 days old, and is still capped at 60 requests a minute and 500 a day. The limit on a free key is **when**, not **where**.
- Why: the track restriction was costing more than it protected. Someone evaluating the API almost always wants to look up a course they already know, and if that course was not one of the three they had no way to tell whether the data existed at all — a missing track and an empty response look identical from outside. The data window is what separates the free tier from the paid one, and it is untouched.
- If you hold a sandbox key, no action is needed and nothing you have already built will break — this only widens what your existing key resolves. Requests for tracks that previously returned `403 upgrade_required` now return data.
- The sandbox notice attached to every free-key response no longer claims a track limit, and the sandbox scope is now declared in one place that the gate, the notice and the published documentation all read — so what the pricing page says the free key covers is what the API enforces.

### 2026-08-19 — Owner statistics — including which yards an ownership actually wins with

- New endpoint `GET /v1/owners/{owner_id}/stats`. Owners had two endpoints against the trainers' four; this closes most of that gap. Strike rate for one ownership split by track, distance, trap, grade and going, over a rolling window.
- The split unique to this endpoint is `by_trainer` — runs, wins and strike rate for every yard the ownership has runners with. It answers a question no other endpoint can: not how the dogs are doing, but which trainers the ownership is getting results from. A syndicate with three yards can see which one is earning its keep.
- Accepts `months` (default 12, maximum 60) and `track_id` to restrict every split to a single track.

### 2026-08-19 — Trainer statistics — strike rate by track, distance, trap and grade

- New endpoint `GET /v1/trainers/{trainer_id}/stats`. `GET /v1/trainers/{trainer_id}/results` returns what happened; this returns what tends to happen. Splits cover track, distance, trap, grade and going, each with runs, wins, places, strike rate and place rate.
- The question it exists to answer is "how does this yard do at this track". Previously that meant paging through every result and counting by hand. It is now one call, or one call with `track_id` to narrow every split to a single course.
- Bounded to a rolling window — `months`, default 12, maximum 60. This is deliberate: a busy yard has thousands of runners in a year, and an unbounded career scan on every request would be slow for every caller, not just the one asking.

### 2026-08-19 — Dog statistics — career splits by distance, trap, track, grade and going

- New endpoint `GET /v1/dogs/{dog_id}/stats`. Five splits over a dog's whole career, each with runs, wins, places, win percentage and place percentage, plus an `overall` block carrying first and last run dates.
- `by_distance` also returns `best_time_s` and `avg_time_s`. Those two fields appear **nowhere else**, and that is on purpose: a time averaged across mixed trips is not a rough figure, it is a meaningless one. A dog running 268m in 16s and 500m in 29s has an "average" that describes neither. Rather than ship the fields everywhere with a caveat, they are omitted wherever distance varies.
- `by_going` is banded — fast, slightly fast, standard, slightly slow, slow — rather than grouped on the raw going allowance. Grouping on the raw value split a 59-run career across ten buckets of three or four runs each, none large enough to read anything into. The underlying values are still returned in `going_values`, so nothing is hidden.
- Completed runs only: reserves and withdrawals are excluded throughout. A split with no runs is omitted rather than returned as zero, so an absent trap means the dog has never boxed there — not that it has run and never won.

### 2026-08-19 — The REST surface is now 38 endpoints — three of them on Live Plus

- Three additions take the API from 35 endpoints to 38: dog, trainer and owner statistics. No existing endpoint changed shape, and no field was removed — everything already built against v1 keeps working.
- All three are in the OpenAPI schema, the Postman collection and the "Run it live" console automatically, because the documentation and the router are generated from the same registry.
- The three statistics endpoints are part of **Live Plus**. Live keys reach the other 35 and receive `403 upgrade_required` on these, with a message naming the endpoint and pointing at the plan comparison — no silent failure and no empty payload.
- Why these three: they return pre-aggregated splits computed across the whole archive, which is materially more expensive per call than returning rows. Putting them with the higher plan keeps the base plan fast and gives the upgrade something concrete behind it.
- The endpoint count shown across the site is now counted from that registry rather than written into the copy, and it is tier-aware — the pricing table reads 35 for Live and 38 for Live Plus from the same source the gate uses, so the two cannot drift apart. It had been hardcoded in six places, which is how a number quietly goes stale.

### 2026-08-10 — Documentation redesign — colour-coded requests and responses, and a rebuilt "Run it live"

- Every request example and JSON response across the documentation is now syntax-highlighted — keys, strings, numbers and booleans each in their own colour — making payloads far easier to scan. It applies to all 35 endpoint pages, the guides and the quickstart.
- Redesigned code panels: the five request examples (cURL, PHP, JavaScript, React and Python) sit in one card whose language tabs form the panel header with a single copy button, and the sample Response now sits under a header showing a green 200 OK status and its content type.
- The "Run it live" console was rebuilt: your key, the parameters and the Run button are grouped into one card, and the live result mirrors the documented Response — a header whose status chip is colour-coded by outcome (green for 2xx, amber for 4xx, red for 5xx), the request line with timing, rate-limit remaining and request id, and the response body syntax-highlighted. It re-colours correctly on every run.

### 2026-08-10 — Page numbers in list meta: next_page, prev_page & totals on the first page

- meta.next_page and meta.prev_page: every page-based list response now includes the next and previous page numbers (null at the ends) alongside the existing page, per_page, total and total_pages — a pager has what it needs without computing offsets.
- The first page of a list now carries the page picture without opting into ?page=: a default call (no cursor) to the race, racecard and result lists returns page, per_page, total, total_pages and next_page, so you learn the total size of a result set from the first request. Cursor continuations stay lean, returning only count and next_cursor.

### 2026-08-10 — Webhooks, richer race status & a fresher tracks list

- Webhooks for push delivery: subscribe an HTTPS endpoint to result.received, race.status_changed and card.updated for GB, AU or both, and stop polling. Deliveries are signed (HMAC-SHA256 in an X-GAPI-Signature header), retried with backoff, and at-least-once with an X-GAPI-Delivery id for de-duplication. Manage them yourself in the dashboard under Webhooks — add an endpoint, choose events and regions, get a signing secret shown once, and send a test delivery. A new Webhooks guide covers the payloads and verification.
- New race lifecycle status results_pending: once a race has jumped and a short settling period has passed with no final result, it reports results_pending instead of remaining on off, so you can tell a race still to run apart from one that has run and is awaiting its result. It applies to GB and AU, and appears across the race endpoints and GET /v1/races/{id}/status.
- New result_status field on the result block and GET /v1/races/{id}/status: provisional while placings are still arriving, final once every dog that ran has a finishing position — so a winner-only result is distinguishable from a settled one. Documented in Data freshness.
- GET /v1/tracks now lists only tracks raced on in the last two weeks across GB and AU, rather than the all-time roster, so the list reflects tracks currently in rotation. Individual track lookups (get-track, track-races, track-stats) are unchanged and still resolve any track by id.
- Fixed the /status day counters — races and results today, and the results-delay percentiles — to count by race day and exclude synthetic sample data, so they reflect real racing accurately.

### 2026-08-01 — Live Plus Launched

- New payment tier added for Live Plus which gives increased request rate.

### 2026-07-29 — Page-number pagination on list endpoints

- List endpoints now accept a 1-based page parameter alongside cursors — for example ?page=2&limit=100 — and add meta.page, meta.per_page, meta.total and meta.total_pages to the response.
- Cursors are unchanged and remain the recommended approach for large or streaming exports; a page-number response also returns a next_cursor, so you can start with page numbers and switch to cursors for a full pull.
- The Pagination guide now documents both approaches side by side.

### 2026-07-27 — Status Page updated and Chat support added

- Status page expanded to offer deeper insight of the current upstream inner workings.
- Chat support added on-site.

### 2026-07-09 — Status page, dashboard usage & the live stream endpoint

- Public status page at /status: an overall banner plus live per-component health for the results pipeline, the WebSocket stream and the REST API — each with a rolling 90-day uptime chart.
- Pipeline freshness on the status page: results today, results in the last 24 hours, the latest result received, and the median and p95 results delay.
- Account dashboard gains an API usage view — a 90-day daily request heatmap, a 30-day request bar chart, and a breakdown of the endpoints you hit.
- Billing on the dashboard: plan status, your renewal date and invoice history, with a link straight to the Stripe billing portal.
- WebSocket stream reachable at wss://greyhoundapi.com:8443/v1/stream (proxied by Cloudflare); a live key still allows up to two concurrent connections.

### 2026-07-08 — v1 — full surface, self-serve accounts, docs console

- All 35 REST endpoints live: racecards, races, results, meetings, dogs, trainers, owners, tracks, breeding, Betfair market summaries and platform endpoints.
- Self-serve accounts: sign in with Google, free sandbox key in seconds, live plan at $99/month with Stripe checkout and billing portal.
- Key lifecycle from the dashboard — create, label, rotate with a 24-hour overlap, revoke. Keys are shown once and stored hashed.
- "Run it live" console on every endpoint page: real requests from your browser with your own key.
- OpenAPI 3.1 export at /openapi.json, generated from the same registry that drives the documentation and the API router.
- Postman collection at /postman-collection.json (Postman v2.1 — also imports into Insomnia and Bruno): all 35 endpoints grouped by section, X-API-Key auth pre-wired to an {{apiKey}} variable, and a saved example response on every request.
- Errors guide documents the full code set, including method_not_allowed and the pre-launch-only not_implemented.
- WebSocket stream for live results, status and card channels, with a 15-minute replay window and up to two connections per live key.

### 2026-07-04 — Access tiers, rate limits & wider track coverage

- Two access tiers: a free sandbox and a single live plan. The sandbox is scoped to the race & track endpoints over a rolling 7-day window at 500 requests a day.
- Per-key rate limiting: a per-minute ceiling and a daily quota, with the live plan raised well above the sandbox.
- GB + AU track coverage: race and track listings across the major British and Australian venues, each with its own distances.

### 2026-07-01 — Documentation hub

- Thirty-five endpoint pages with parameters, five-language request examples and canonical response payloads.
- Guides: getting started, authentication, pagination, rate limits, errors, timezones, data freshness, sandbox, WebSocket stream.
- Interactive head-to-head demo on the sandbox page built from real documented payloads.

### 2026-06-25 — Platform foundation

- Registry-driven v1 API: a single registry drives the request router, the documentation and the OpenAPI spec, so they never drift apart.
- Core data model for Great Britain and Australia greyhound racing: tracks, meetings, races, runners and results, all under one consistent JSON envelope with request ids.
- API keys stored as SHA-256 hashes; every request authenticates with the X-API-Key header, and unknown or revoked keys are rejected consistently.

---

## Weather at the track

*Where the weather on a race comes from, which hour it belongs to, what before_race measures, and which races carry none.*  
`https://greyhoundapi.com/documentation/weather`

Every race carries a `weather` object: the conditions at its own track for the
hour it went off. It is on the race, on the racecards and on
[`/v1/races/{race_id}/weather`](https://greyhoundapi.com/documentation/race-weather), and it is
not behind a parameter — a race already names its track and its off time, so asking
for the weather separately would be asking twice for something we hold.

### Which hour a race gets

The hour containing the official off. A card of twelve races at one track therefore shares
one or two hours between them, which is correct: the weather did not change between race 3 and
race 4, and pretending to a per-race figure would invent precision that does not exist.

A race with no official off yet is matched on its scheduled start instead, and
`matched_on` says which was used.

### before_race is the going, and it is the useful part

The race's own hour tells you what it was like at the off. It cannot tell you that 6mm fell
that afternoon, which is what actually makes a track slow. `before_race` carries
rain over the previous 6 and 24 hours and average wind over the previous 6, computed from the
same hourly store.

```
curl "https://api.greyhoundapi.com/v1/races/1229082445/weather" \
  -H "X-API-Key: $GAPI_KEY"
```

A dry race after a wet afternoon reads `precip_mm: 0` with
`rain_6h_mm: 5.6`. Those two together are the thing worth having.

### Where the numbers come from

Hourly reanalysis for the grid point at the track, not a sensor at the bend. The grid is
several kilometres across, so it describes the weather over the track rather than on it. For
temperature, wind and rain at the scale that affects a race that is the right instrument; if
you need the exact gust that hit trap 1, no public dataset has it.

`source` names the model. A race in the last day or so reads
`forecast`, because the reanalysis archive lags real time; once it catches up the
row is overwritten and the same call reads `archive`. The figure can change between
those two, which is why the field is there rather than implied.

### Which races carry none

A race whose start time was never recorded returns `weather: null` with a
sentence in `weather_absent` saying so. That is a few hundred Irish races out of
123,000, where the meeting has no racecard to read a time from.

All three regions are otherwise complete: every race with a recorded off time has its hour,
back to 2018 for Great Britain, 2022 for Australia and 2019 for Ireland. Irish times come from
the racecard rather than the result, so they are the carded time for the race.

### What it will not do

There is no rating, no going estimate and no adjustment applied to any time we publish. The
weather is stated beside the race and left there. What it means for a dog at a trip on a wet
night is your model's business, not ours.

---

## Endpoint reference

Every endpoint takes `X-API-Key` as a header. Paths below are relative to
`https://api.greyhoundapi.com`.

### Racecards (2)

#### `GET /v1/racecards/today`

**Today's racecards.** Every greyhound racecard scheduled today in Great Britain and Australia, resolved per track-local calendar day, with runners, traps, grades and start times.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `region` | query | string | no | `GB` |
| `track_id` | query | integer | no | `3905994177` |
| `grade` | query | string | no | `A2` |
| `distance_m` | query | integer | no | `575` |
| `summary` | query | boolean | no | `true` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |
| `page` | query | integer | no | `1` |

"Today" is evaluated in each track's own timezone, so an Australian evening card and a British afternoon card can both be "today" at the same moment. See [Timezones & dates](https://greyhoundapi.com/documentation/timezones).

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ",
        "page": 1,
        "per_page": 50,
        "total": 1287,
        "total_pages": 26,
        "next_page": 2,
        "prev_page": null
    },
    "data": [
        {
            "race_id": 1229082445,
            "meeting_id": 1449422077,
            "region": "GB",
            "track": {
                "track_id": 1400000014,
                "name": "Romford",
                "region": "GB",
                "timezone": "Europe/London"
            },
            "race_number": 12,
            "grade": "S1",
            "class_letter": "S",
            "class_number": 1,
            "distance_m": 575,
            "race_type": "Flat",
            "is_handicap": false,
            "going": null,
            "field_size": 6,
            "scheduled_start": {
                "utc": "2026-07-08T20:46:00Z",
                "local": "2026-07-08T21:46:00+01:00"
            },
            "status": "scheduled",
            "off": null,
            "freshness": {
                "first_seen": "2026-07-07T05:12:10Z",
                "results_received": null,
                "results_delay_s": null
            },
            "weather": {
                "hour_utc": "2026-07-08T20:00:00Z",
                "temp_c": 15.2,
                "humidity_pct": 81,
                "wind_kph": 19.5,
                "gust_kph": 42.8,
                "wind_from": "W",
                "wind_deg": 271,
                "precip_mm": 0.3,
                "conditions": "light drizzle",
                "before_race": {
                    "rain_6h_mm": 1.6,
                    "rain_24h_mm": 2,
                    "wind_avg_6h_kph": 21.4
                },
                "source": "archive"
            },
            "runners": [
                {
                    "trap": 1,
                    "dog_id": 1655044329,
                    "dog_name": "Full Monty",
                    "sex": "d",
                    "colour": "bebd",
                    "born": "2024-04-01",
                    "age_months": 27,
                    "sire": "Ballymac Anton",
                    "dam": "Ballymac Notoyou",
                    "trainer": {
                        "trainer_id": 1180000118,
                        "name": "P W Young"
                    },
                    "owner": {
                        "owner_id": 2231000223,
                        "name": "Mr G D R Bowmer, Mr G J Bowmer"
                    },
                    "weight_kg": 36.6,
                    "handicap_metres": null,
                    "sp": null,
                    "position": null,
                    "runner_status": "runner"
                }
            ]
        }
    ]
}
```

#### `GET /v1/racecards/upcoming`

**Upcoming races.** A rolling window of the next greyhound races to jump across GB, AU and IE, from the next hour up to the next 48, ordered by scheduled start time.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `hours` | query | integer | no | `8` |
| `region` | query | string | no | `GB` |
| `track_id` | query | integer | no | `3905994177` |
| `grade` | query | string | no | `A2` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |
| `page` | query | integer | no | `1` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ",
        "page": 1,
        "per_page": 50,
        "total": 1287,
        "total_pages": 26,
        "next_page": 2,
        "prev_page": null
    },
    "data": [
        {
            "race_id": 1229082445,
            "meeting_id": 1449422077,
            "region": "GB",
            "track": {
                "track_id": 1400000014,
                "name": "Romford",
                "region": "GB",
                "timezone": "Europe/London"
            },
            "race_number": 12,
            "grade": "S1",
            "class_letter": "S",
            "class_number": 1,
            "distance_m": 575,
            "race_type": "Flat",
            "is_handicap": false,
            "going": null,
            "field_size": 6,
            "scheduled_start": {
                "utc": "2026-07-08T20:46:00Z",
                "local": "2026-07-08T21:46:00+01:00"
            },
            "status": "scheduled",
            "off": null,
            "freshness": {
                "first_seen": "2026-07-07T05:12:10Z",
                "results_received": null,
                "results_delay_s": null
            },
            "weather": {
                "hour_utc": "2026-07-08T20:00:00Z",
                "temp_c": 15.2,
                "humidity_pct": 81,
                "wind_kph": 19.5,
                "gust_kph": 42.8,
                "wind_from": "W",
                "wind_deg": 271,
                "precip_mm": 0.3,
                "conditions": "light drizzle",
                "before_race": {
                    "rain_6h_mm": 1.6,
                    "rain_24h_mm": 2,
                    "wind_avg_6h_kph": 21.4
                },
                "source": "archive"
            },
            "runners": [
                {
                    "trap": 1,
                    "dog_id": 1655044329,
                    "dog_name": "Full Monty",
                    "sex": "d",
                    "colour": "bebd",
                    "born": "2024-04-01",
                    "age_months": 27,
                    "sire": "Ballymac Anton",
                    "dam": "Ballymac Notoyou",
                    "trainer": {
                        "trainer_id": 1180000118,
                        "name": "P W Young"
                    },
                    "owner": {
                        "owner_id": 2231000223,
                        "name": "Mr G D R Bowmer, Mr G J Bowmer"
                    },
                    "weight_kg": 36.6,
                    "handicap_metres": null,
                    "sp": null,
                    "position": null,
                    "runner_status": "runner"
                }
            ]
        }
    ]
}
```

### Races (12)

#### `GET /v1/races`

**Search races.** Search the full greyhound race archive by date range, region, track, grade, distance, handicap flag and lifecycle status, with cursor pagination.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `ids` | query | string | no |  |
| `date_from` | query | date | no | `2026-07-01` |
| `date_to` | query | date | no | `2026-07-07` |
| `region` | query | string | no | `GB` |
| `track_id` | query | integer | no | `3905994177` |
| `grade` | query | string | no | `A2` |
| `distance_m` | query | integer | no | `575` |
| `is_handicap` | query | boolean | no | `false` |
| `status` | query | string | no | `complete` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |
| `page` | query | integer | no | `1` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ",
        "page": 1,
        "per_page": 50,
        "total": 1287,
        "total_pages": 26,
        "next_page": 2,
        "prev_page": null
    },
    "data": [
        {
            "race_id": 1229082445,
            "meeting_id": 1449422077,
            "region": "GB",
            "track": {
                "track_id": 1400000014,
                "name": "Romford",
                "region": "GB",
                "timezone": "Europe/London"
            },
            "race_number": 12,
            "grade": "S1",
            "class_letter": "S",
            "class_number": 1,
            "distance_m": 575,
            "race_type": "Flat",
            "is_handicap": false,
            "going": null,
            "field_size": 6,
            "scheduled_start": {
                "utc": "2026-07-08T20:46:00Z",
                "local": "2026-07-08T21:46:00+01:00"
            },
            "status": "scheduled",
            "off": null,
            "freshness": {
                "first_seen": "2026-07-07T05:12:10Z",
                "results_received": null,
                "results_delay_s": null
            },
            "weather": {
                "hour_utc": "2026-07-08T20:00:00Z",
                "temp_c": 15.2,
                "humidity_pct": 81,
                "wind_kph": 19.5,
                "gust_kph": 42.8,
                "wind_from": "W",
                "wind_deg": 271,
                "precip_mm": 0.3,
                "conditions": "light drizzle",
                "before_race": {
                    "rain_6h_mm": 1.6,
                    "rain_24h_mm": 2,
                    "wind_avg_6h_kph": 21.4
                },
                "source": "archive"
            }
        }
    ]
}
```

#### `GET /v1/races/{race_id}`

**Get a race.** Full detail for one greyhound race: track, grade, distance, going, scheduled and official off-times, lifecycle status, freshness metadata and the complete runner line-up.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `race_id` | path | integer | yes | `3943561620` |

**Fetching more than one?** Do not call this in a loop — `/v1/races?ids=A-B-C` returns up to 50 races in a single request, and counts as one against your rate limit. See batch requests. Note this endpoint also returns runners, and the full result once it settles, which the list form does not. This is the workhorse endpoint. Once the race completes, the same call also carries the `result` block shown in [Get a race result](https://greyhoundapi.com/documentation/race-result). **Irish off times are the carded time.** An Irish result is published without a clock, so the time on these races is read from the racecard for that meeting: when the race was due rather than the moment the traps opened. A few hundred races out of 123,000 have no card to read and keep `off` as `null`.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "race_id": 1229082445,
        "meeting_id": 1449422077,
        "region": "GB",
        "track": {
            "track_id": 1400000014,
            "name": "Romford",
            "region": "GB",
            "timezone": "Europe/London"
        },
        "race_number": 12,
        "grade": "S1",
        "class_letter": "S",
        "class_number": 1,
        "distance_m": 575,
        "race_type": "Flat",
        "is_handicap": false,
        "going": null,
        "field_size": 6,
        "scheduled_start": {
            "utc": "2026-07-08T20:46:00Z",
            "local": "2026-07-08T21:46:00+01:00"
        },
        "status": "scheduled",
        "off": null,
        "freshness": {
            "first_seen": "2026-07-07T05:12:10Z",
            "results_received": null,
            "results_delay_s": null
        },
        "weather": {
            "hour_utc": "2026-07-08T20:00:00Z",
            "temp_c": 15.2,
            "humidity_pct": 81,
            "wind_kph": 19.5,
            "gust_kph": 42.8,
            "wind_from": "W",
            "wind_deg": 271,
            "precip_mm": 0.3,
            "conditions": "light drizzle",
            "before_race": {
                "rain_6h_mm": 1.6,
                "rain_24h_mm": 2,
                "wind_avg_6h_kph": 21.4
            },
            "source": "archive"
        },
        "runners": [
            {
                "trap": 1,
                "dog_id": 1655044329,
                "dog_name": "Full Monty",
                "sex": "d",
                "colour": "bebd",
                "born": "2024-04-01",
                "age_months": 27,
                "sire": "Ballymac Anton",
                "dam": "Ballymac Notoyou",
                "trainer": {
                    "trainer_id": 1180000118,
                    "name": "P W Young"
                },
                "owner": {
                    "owner_id": 2231000223,
                    "name": "Mr G D R Bowmer, Mr G J Bowmer"
                },
                "weight_kg": 36.6,
                "handicap_metres": null,
                "sp": null,
                "position": null,
                "runner_status": "runner"
            }
        ]
    }
}
```

#### `GET /v1/races/{race_id}/runners`

**Race runners.** Just the runners for one race: trap draws, dogs, breeding, trainers, owners, weights — and positions, times and prices once the result is in.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `race_id` | path | integer | yes | `3943561620` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ"
    },
    "data": [
        {
            "trap": 1,
            "dog_id": 1655044329,
            "dog_name": "Full Monty",
            "sex": "d",
            "colour": "bebd",
            "born": "2024-04-01",
            "age_months": 27,
            "sire": "Ballymac Anton",
            "dam": "Ballymac Notoyou",
            "trainer": {
                "trainer_id": 1180000118,
                "name": "P W Young"
            },
            "owner": {
                "owner_id": 2231000223,
                "name": "Mr G D R Bowmer, Mr G J Bowmer"
            },
            "weight_kg": 36.6,
            "handicap_metres": null,
            "sp": null,
            "position": null,
            "runner_status": "runner"
        }
    ]
}
```

#### `GET /v1/races/{race_id}/result`

**Get a race result.** The result view of a race: finishing order, starting prices, sectionals, run times, beaten distances, dividends and the exact off and receipt times.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `race_id` | path | integer | yes | `3943561620` |

A race we have **confirmed** will never have a result — abandoned or void — returns `200` with the race, no positions, and `meta.result_available: false`. That is a definitive answer: stop asking. If the race has no result but is not confirmed dead, the endpoint returns `409` with error code `result_not_available`, the race's current `status`, and a boolean `retry`. `404` is reserved for a race ID that does not exist, so "wrong ID" and "no result" are never the same response. **Changed 7 September 2026:** this case previously returned `404`, which said the race was missing when it was simply not settled. `retry: true` means the race has not run yet and polling will eventually succeed; `retry: false` means it ran and was abandoned, voided or never settled, so no amount of polling will produce a result.**Irish races before September 2026 have no off time.** The source publishes those results with a date and no clock, so `scheduled_start` is midnight UTC on the race date and `off` is `null`. It is not a gap in our ingest and there is nothing to wait for. Irish racing from this month onward, and all GB and AU racing, carries a real time.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "race_id": 1229082445,
        "meeting_id": 1449422077,
        "region": "GB",
        "track": {
            "track_id": 1400000014,
            "name": "Romford",
            "region": "GB",
            "timezone": "Europe/London"
        },
        "race_number": 12,
        "grade": "S1",
        "class_letter": "S",
        "class_number": 1,
        "distance_m": 575,
        "race_type": "Flat",
        "is_handicap": false,
        "going": null,
        "field_size": 6,
        "scheduled_start": {
            "utc": "2026-07-08T20:46:00Z",
            "local": "2026-07-08T21:46:00+01:00"
        },
        "status": "complete",
        "off": {
            "utc": "2026-07-08T20:47:12Z",
            "delay_s": 72
        },
        "freshness": {
            "first_seen": "2026-07-07T05:12:10Z",
            "results_received": "2026-07-08T21:03:40Z",
            "results_delay_s": 1060
        },
        "result": {
            "positions": [
                {
                    "position": 1,
                    "trap": 3,
                    "dog_id": 1654595188,
                    "dog_name": "Miami Yeats",
                    "trainer": {
                        "trainer_id": 1210000121,
                        "name": "M E Wiley"
                    },
                    "sp": {
                        "fraction": "5/2",
                        "decimal": 3.5
                    },
                    "sectional_s": 4.31,
                    "run_time_s": 35.42,
                    "adjusted_time_s": 35.42,
                    "weight_kg": 34.4,
                    "betfair": {
                        "selection_id": 84512233,
                        "back_price": 3.55,
                        "lay_price": 3.6,
                        "last_update": "2026-07-08T20:41:02Z"
                    }
                }
            ],
            "forecast_dividend": "12.40",
            "tricast_dividend": "48.12",
            "off": {
                "utc": "2026-07-08T20:47:12Z",
                "delay_s": 72
            },
            "received": {
                "utc": "2026-07-08T21:03:40Z",
                "delay_s": 1060
            },
            "weather": {
                "hour_utc": "2026-07-08T20:00:00Z",
                "temp_c": 15.2,
                "humidity_pct": 81,
                "wind_kph": 19.5,
                "gust_kph": 42.8,
                "wind_from": "W",
                "wind_deg": 271,
                "precip_mm": 0.3,
                "conditions": "light drizzle",
                "before_race": {
                    "rain_6h_mm": 1.6,
                    "rain_24h_mm": 2,
                    "wind_avg_6h_kph": 21.4
                },
                "source": "archive"
            }
        }
    }
}
```

#### `GET /v1/races/{race_id}/status`

**Race status & freshness.** The lifecycle and freshness record of a race: current status, official off delay, when results arrived and how far behind the scheduled start they were.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `race_id` | path | integer | yes | `3943561620` |

Delays are part of this product's honesty contract — read [Data freshness](https://greyhoundapi.com/documentation/data-freshness) before building anything time-sensitive.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "race_id": 1229082445,
        "status": "complete",
        "scheduled_start": {
            "utc": "2026-07-08T20:46:00Z"
        },
        "off": {
            "utc": "2026-07-08T20:47:12Z",
            "delay_s": 72
        },
        "results": {
            "received": "2026-07-08T21:03:40Z",
            "delay_s": 1060
        }
    }
}
```

#### `GET /v1/races/{race_id}/precedents`, **Live Plus only**

**Race precedents.** Races from the archive that ran under matching conditions at this track and distance, with what actually happened in them — and an explicit list of what each one matched on and where it differs.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `race_id` | path | integer | yes | `3943561620` |
| `limit` | query | integer | no | `20` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "race_id": 1229082445,
        "track": {
            "track_id": 1400000014,
            "name": "Romford",
            "region": "GB",
            "timezone": "Europe/London"
        },
        "distance_m": 400,
        "grade": "A3",
        "field_size": 6,
        "going": 0,
        "matching": {
            "axes": [
                "track",
                "distance",
                "grade",
                "field_size"
            ],
            "relaxed": [],
            "window": "last 24 months",
            "found": 112,
            "returned": 20,
            "note": null
        },
        "precedents": [
            {
                "race_id": 1229081338,
                "date": "2026-05-14",
                "grade": "A3",
                "field_size": 6,
                "matched_on": [
                    "track",
                    "distance",
                    "grade",
                    "field_size"
                ],
                "differs_on": [
                    {
                        "field": "going",
                        "this": 0,
                        "that": -20
                    }
                ],
                "result": {
                    "winner_trap": 3,
                    "winning_time_s": 24.08,
                    "vs_par_s": -0.53,
                    "winner_sp_decimal": 3.5
                }
            }
        ],
        "note": "Races that ran under matching conditions at this track and distance, with their actual results. Track and distance are never relaxed. Going is reported rather than matched, as a signed hundredths-of-a-second track adjustment where negative is slower. This is history: the frequency of any outcome here is not a probability, and nothing in it forecasts this race."
    }
}
```

#### `GET /v1/races/{race_id}/field`, **Live Plus only**

**The field.** The whole field in one call: record here, grade record, layoff and the draw, per runner.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `race_id` | path | integer | yes | `3943561620` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "race_id": 3566844427,
        "region": "GB",
        "track": {
            "track_id": 3423913378,
            "name": "Romford"
        },
        "distance_m": 575,
        "grade": "S2",
        "is_handicap": false,
        "scheduled_start": {
            "utc": "2026-09-15 14:19:00"
        },
        "field": {
            "runners": 5,
            "avg_first_split_s": 13.948,
            "with_split_figure": 5
        },
        "runners": [
            {
                "trap": 1,
                "dog_id": 2643190085,
                "dog_name": "Zenith Angel",
                "pace": {
                    "avg_first_split_s": 13.939,
                    "runs": 18
                },
                "here": {
                    "runs": 18,
                    "wins": 2,
                    "places": 10,
                    "win_pct": 11.1,
                    "scope": "this track and distance"
                },
                "from_this_trap": {
                    "track_trap_win_pct": 20.03,
                    "track_trap_runs": 2601
                },
                "at_this_grade": {
                    "grade": "S2",
                    "runs": 5,
                    "wins": 2,
                    "win_pct": 40
                },
                "layoff": {
                    "days_since_last_run": 6,
                    "band": "0-7",
                    "band_runs": 20,
                    "band_wins": 5,
                    "band_win_pct": 25
                }
            },
            {
                "trap": 5,
                "dog_id": 3043608518,
                "dog_name": "Kilbride Office",
                "pace": {
                    "avg_first_split_s": 13.99,
                    "runs": 3
                },
                "here": {
                    "runs": 3,
                    "wins": 0,
                    "places": 0,
                    "win_pct": 0,
                    "scope": "this track and distance"
                },
                "from_this_trap": {
                    "track_trap_win_pct": 15.64,
                    "track_trap_runs": 2544
                },
                "at_this_grade": {
                    "grade": "S2",
                    "runs": 1,
                    "wins": 0,
                    "win_pct": 0
                },
                "layoff": {
                    "days_since_last_run": 4,
                    "band": "0-7",
                    "band_runs": 1,
                    "band_wins": 0,
                    "band_win_pct": 0
                }
            }
        ],
        "thin": [
            {
                "trap": 5,
                "dog_name": "Kilbride Office",
                "reason": "3 runs at this track and distance",
                "min_runs": 6
            }
        ],
        "form_state_computed_utc": "2026-09-15 13:49:42",
        "note": "Measurements only, never a forecast. Form state is rebuilt nightly."
    }
}
```

#### `GET /v1/races/{race_id}/analysis`, **Live Plus only**

**Race analysis.** Conditions and measured early pace for one race in a single request, joined per runner — the trap record at this track and distance, typical winning times, field experience, and each runner's early split with the sample behind it.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `race_id` | path | integer | yes | `3943561620` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "race_id": 1229082445,
        "track": {
            "track_id": 1400000014,
            "name": "Romford",
            "region": "GB",
            "timezone": "Europe/London"
        },
        "distance_m": 400,
        "grade": "A3",
        "going": null,
        "is_handicap": false,
        "conditions": {
            "trap_bias": {
                "sample_races": 11745,
                "even_break_pct": 16.67,
                "traps": [
                    {
                        "trap": 1,
                        "wins": 2161,
                        "win_pct": 18.4
                    }
                ],
                "note": null
            },
            "winning_time": {
                "sample_races": 4820,
                "window": "last 24 months",
                "average_s": 24.61,
                "fastest_s": 23.84,
                "stdev_s": 0.412
            },
            "field": {
                "runners": 6,
                "avg_career_win_pct": 12.8,
                "avg_career_runs": 24.5,
                "least_experienced": 7,
                "most_experienced": 61
            }
        },
        "pace": {
            "field_avg_first_split_s": 3.85,
            "spread_s": 0.25,
            "quickest_away": {
                "trap": 3,
                "dog_name": "Miami Yeats"
            },
            "slowest_away": {
                "trap": 5,
                "dog_name": "Headford Eimear"
            },
            "with_data": 3,
            "without_data": [
                {
                    "trap": 4,
                    "dog_id": 1640101557,
                    "dog_name": "Savana Ruby",
                    "runs": 2,
                    "reason": "2 timed runs — below the 3 needed"
                }
            ],
            "scope": {
                "window": "last 18 months",
                "same_track": true,
                "same_distance": true,
                "handicaps": "excluded",
                "min_runs": 3,
                "note": null
            }
        },
        "runners": [
            {
                "trap": 3,
                "dog_id": 1654595188,
                "dog_name": "Miami Yeats",
                "pace": {
                    "avg_first_split_s": 3.7,
                    "best_s": 3.66,
                    "vs_field_s": -0.15,
                    "runs": 5
                }
            }
        ],
        "note": "Conditions and measured early pace for this race in one request. Every figure carries the sample it is drawn from. Nothing here rates a runner, ranks the field by chance or forecasts the result."
    }
}
```

#### `GET /v1/races/{race_id}/pace`, **Live Plus only**

**Early pace.** Each runner's measured early split from past runs at this track over this exact distance, with the number of runs behind every figure and the runners we have no data for named rather than omitted.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `race_id` | path | integer | yes | `3943561620` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "race_id": 1229082445,
        "track": {
            "track_id": 1400000014,
            "name": "Romford",
            "region": "GB",
            "timezone": "Europe/London"
        },
        "distance_m": 400,
        "grade": "A3",
        "is_handicap": false,
        "early_pace": [
            {
                "trap": 1,
                "dog_id": 1655044329,
                "dog_name": "Full Monty",
                "avg_first_split_s": 3.84,
                "best_s": 3.79,
                "runs": 4,
                "vs_field_s": -0.01
            },
            {
                "trap": 3,
                "dog_id": 1654595188,
                "dog_name": "Miami Yeats",
                "avg_first_split_s": 3.7,
                "best_s": 3.66,
                "runs": 5,
                "vs_field_s": -0.15
            },
            {
                "trap": 5,
                "dog_id": 1657712904,
                "dog_name": "Headford Eimear",
                "avg_first_split_s": 3.95,
                "best_s": 3.88,
                "runs": 3,
                "vs_field_s": 0.1
            }
        ],
        "field": {
            "avg_first_split_s": 3.85,
            "spread_s": 0.25,
            "with_data": 3,
            "without_data": [
                {
                    "trap": 4,
                    "dog_id": 1640101557,
                    "dog_name": "Savana Ruby",
                    "runs": 2,
                    "reason": "2 timed runs — below the 3 needed"
                },
                {
                    "trap": 6,
                    "dog_id": 1229081338,
                    "dog_name": "Ballyroe Bach",
                    "runs": 0,
                    "reason": "no timed runs at this track and distance"
                }
            ]
        },
        "scope": {
            "window": "last 18 months",
            "same_track": true,
            "same_distance": true,
            "handicaps": "excluded",
            "min_runs": 3,
            "note": null
        },
        "note": "Measured early splits from past runs at this track and distance. Nothing here rates a runner or forecasts this race."
    }
}
```

#### `GET /v1/races/{race_id}/conditions`, **Live Plus only**

**Race conditions.** The context one race runs in: how each trap has performed at this track over this exact distance, what a winning time looks like here, and how experienced the field is — every figure with the sample it is drawn from.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `race_id` | path | integer | yes | `3943561620` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "race_id": 1229082445,
        "track": {
            "track_id": 1400000014,
            "name": "Romford",
            "region": "GB",
            "timezone": "Europe/London"
        },
        "distance_m": 400,
        "grade": "A3",
        "going": null,
        "trap_bias": {
            "sample_races": 11745,
            "even_break_pct": 16.67,
            "traps": [
                {
                    "trap": 1,
                    "wins": 2161,
                    "win_pct": 18.4
                },
                {
                    "trap": 2,
                    "wins": 1985,
                    "win_pct": 16.9
                },
                {
                    "trap": 3,
                    "wins": 1785,
                    "win_pct": 15.2
                },
                {
                    "trap": 4,
                    "wins": 1902,
                    "win_pct": 16.19
                },
                {
                    "trap": 5,
                    "wins": 1798,
                    "win_pct": 15.31
                },
                {
                    "trap": 6,
                    "wins": 2114,
                    "win_pct": 18
                }
            ],
            "note": null
        },
        "winning_time": {
            "sample_races": 4820,
            "window": "last 24 months",
            "average_s": 24.61,
            "fastest_s": 23.84,
            "stdev_s": 0.412
        },
        "field": {
            "runners": 6,
            "avg_career_win_pct": 12.8,
            "avg_career_runs": 24.5,
            "least_experienced": 7,
            "most_experienced": 61
        },
        "note": "Historical context for this race. Trap percentages describe past results at this track and distance; field figures describe the runners' careers to date. Nothing here rates a runner or forecasts this race."
    }
}
```

#### `GET /v1/races/{race_id}/form`, **Live Plus only**

**Race card with form.** The full race card with every runner's recent form attached — the race and its dogs' form lines in a single call instead of one request per runner.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `race_id` | path | integer | yes | `3943561620` |
| `runs` | query | integer | no | `6` |

Every form line returned happened **before** this race went off, and the race itself is excluded. That matters for historical races: "recent runs by this dog" would otherwise include what it did afterwards, and anyone testing a method against it would be measuring their own lookahead. The cutoff used is echoed in `meta.form_cutoff`. A runner with no prior completed runs returns an empty `form` array — that is a first-timer, not an error. **Sectional times are timed to a fixed point on the track, not to a fixed distance from the traps**, so they compare only within the same track AND distance: at Towcester a 500m split runs about 4.0s and a 460m split about 1.4s, because the shorter start sits forty metres closer to the marker. Comparing across distances ranks a dog against the layout of the course rather than against itself. Form is drawn from completed races with a recorded finishing position; see [Dog form](https://greyhoundapi.com/documentation/dog-form) for the same lines per dog, paged.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "race_id": 1229082445,
        "track": "Romford",
        "grade": "A3",
        "distance_m": 400,
        "scheduled_start": {
            "utc": "2026-07-08T18:46:00Z",
            "local": "2026-07-08T19:46:00+01:00"
        },
        "status": "complete",
        "runners": [
            {
                "trap": 3,
                "dog_id": 1654595188,
                "dog_name": "Miami Yeats",
                "sp": {
                    "fraction": "5/2",
                    "decimal": 3.5
                },
                "position": 1,
                "form": [
                    {
                        "race_id": 1229081992,
                        "date_local": "2026-07-01",
                        "track": "Romford",
                        "grade": "A3",
                        "distance_m": 400,
                        "trap": 2,
                        "position": 2,
                        "sp": {
                            "fraction": "3/1",
                            "decimal": 4
                        },
                        "sectional_s": 3.91,
                        "run_time_s": 24.62,
                        "weight_kg": 31.4
                    },
                    {
                        "race_id": 1229081455,
                        "date_local": "2026-06-24",
                        "track": "Romford",
                        "grade": "A4",
                        "distance_m": 400,
                        "trap": 5,
                        "position": 1,
                        "sp": {
                            "fraction": "2/1",
                            "decimal": 3
                        },
                        "sectional_s": 3.88,
                        "run_time_s": 24.48,
                        "weight_kg": 31.2
                    }
                ]
            }
        ]
    }
}
```

#### `GET /v1/races/{race_id}/market`

**Race market summary.** Factual market data for a race: each runner's starting price plus the best Betfair back and lay prices captured seconds before the off, with the exchange selection id and the time they were taken.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `race_id` | path | integer | yes | `3943561620` |

**Lay prices are available from 2 September 2026 onward**, on about 94% of runners. Before that date the lay side of the market was not captured and the field is `null` — that is the absence of a recording, not a market without a lay side. Starting prices and Betfair BACK prices are unaffected and run the full length of the archive. This is historical, factual market data. GreyhoundAPI publishes no predictions, ratings or betting advice on any endpoint.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "race_id": 1229082445,
        "betfair_market_id": "1.234567890",
        "total_market_value": 12480.55,
        "runners": [
            {
                "trap": 3,
                "dog_id": 1654595188,
                "dog_name": "Miami Yeats",
                "sp": {
                    "fraction": "5/2",
                    "decimal": 3.5
                },
                "betfair": {
                    "selection_id": 84512233,
                    "back_price": 3.55,
                    "lay_price": 3.6,
                    "last_update": "2026-07-08T20:41:02Z"
                }
            }
        ]
    }
}
```

### Weather (1)

#### `GET /v1/races/{race_id}/weather`

**Weather by Race ID.** Temperature, wind, gusts and rain at the track for the hour the race went off, with the rain and wind in the hours before it.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `race_id` | path | integer | yes | `3943561620` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "race_id": 1229082445,
        "region": "GB",
        "track": {
            "track_id": 1400000014,
            "name": "Romford",
            "latitude": 51.57538,
            "longitude": 0.18269
        },
        "matched_on": "official_off",
        "weather": {
            "hour_utc": "2026-09-17T19:00:00Z",
            "temp_c": 15.2,
            "humidity_pct": 81,
            "wind_kph": 19.5,
            "gust_kph": 42.8,
            "wind_from": "W",
            "wind_deg": 271,
            "precip_mm": 0.3,
            "conditions": "light drizzle",
            "before_race": {
                "rain_6h_mm": 1.6,
                "rain_24h_mm": 2,
                "wind_avg_6h_kph": 21.4
            },
            "source": "archive"
        },
        "weather_absent": null
    }
}
```

### Comments (5)

#### `GET /v1/races/{race_id}/comments`, **Live Plus only**

**Runner comments.** How each runner ran, as structured events and a one-line summary, or for Australian racing the view before the off.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `race_id` | path | integer | yes | `3943561620` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "race_id": 1088930,
        "region": "GB",
        "track": {
            "track_id": 1400000031,
            "name": "Towcester"
        },
        "race_number": 7,
        "scheduled_start": "2026-09-19T14:48:00Z",
        "status": "complete",
        "kind": "in_running",
        "runners": [
            {
                "trap": 3,
                "dog_id": 646716,
                "dog_name": "Moyvane Image",
                "position": 1,
                "comments": {
                    "in_running": {
                        "summary": "Quick out of the traps, raced between the middle and the rail, led on the run to the first bend.",
                        "events": [
                            {
                                "event": "fast_start",
                                "place": null,
                                "text": "Quick out of the traps"
                            },
                            {
                                "event": "line_middle_to_rails",
                                "place": null,
                                "text": "Raced between the middle and the rail"
                            },
                            {
                                "event": "led",
                                "place": "run_up",
                                "text": "Led on the run to the first bend"
                            }
                        ]
                    }
                }
            },
            {
                "trap": 2,
                "dog_id": 655316,
                "dog_name": "Union Allstar",
                "position": 4,
                "comments": {
                    "in_running": {
                        "summary": "Raced between the rail and the middle, led from the first to the third bend.",
                        "events": [
                            {
                                "event": "line_rails_to_middle",
                                "place": null,
                                "text": "Raced between the rail and the middle"
                            },
                            {
                                "event": "led_from",
                                "place": "bend_1",
                                "text": "Led from the first bend"
                            },
                            {
                                "event": "led_until",
                                "place": "bend_3",
                                "text": "Led until the third bend"
                            }
                        ]
                    }
                }
            }
        ]
    }
}
```

#### `GET /v1/races/{race_id}/writeup`, **Live Plus only**

**Race write-up.** A written preview and report for one race, with a line on every runner, written from the records.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `race_id` | path | integer | yes | `3943561620` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "race_id": 1245149,
        "region": "GB",
        "track": {
            "track_id": 1400000007,
            "name": "Central Park"
        },
        "race_number": 7,
        "scheduled_start": "2026-09-18T19:34:00Z",
        "status": "complete",
        "writeup": {
            "headline": "Silver Medal too good in D3 at 6/4",
            "preview": "Fawkham Sunrise has won 2 of its last 5 runs and has won twice at this track, while Frankton Flame has 1 win and 3 top-three finishes in its last 5 runs.",
            "report": "Silver Medal justified 6/4 favouritism, jumping very quickly from trap 3 and making all down the middle. Frankton Flame ran on to take second, 0.18 seconds behind, with West Coast Star third after having every chance.",
            "runners": [
                {
                    "trap": 3,
                    "dog_id": 650112,
                    "dog_name": "Silver Medal",
                    "position": 1,
                    "line": "Silver Medal won at 6/4, jumping very quickly from the traps, making all and racing down the middle."
                },
                {
                    "trap": 1,
                    "dog_id": 648027,
                    "dog_name": "Frankton Flame",
                    "position": 2,
                    "line": "Frankton Flame finished 2nd, 0.18 seconds behind, racing on the rail and keeping on."
                }
            ],
            "written_at": "2026-09-21T10:52:14+00:00"
        }
    }
}
```

#### `GET /v1/writeups/upcoming`, **Live Plus only**

**Upcoming previews.** Previews for the races about to run, written from the field and its form before the off, over the next 1 to 48 hours.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `hours` | query | integer | no | `24` |
| `region` | query | string | no | `GB` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ",
        "page": 1,
        "per_page": 50,
        "total": 1287,
        "total_pages": 26,
        "next_page": 2,
        "prev_page": null
    },
    "data": [
        {
            "race_id": 1245320,
            "region": "GB",
            "track": {
                "track_id": 1400000007,
                "name": "Central Park"
            },
            "race_number": 3,
            "scheduled_start": "2026-09-22T11:24:00Z",
            "status": "scheduled",
            "preview": "Millbank Jasmine has won 2 of its last 5 runs, including last time out at Central Park, while Headford Sirl has 1 win and 4 top-three finishes in its last 5 runs.",
            "written_at": "2026-09-21T18:04:11+00:00"
        }
    ]
}
```

#### `GET /v1/writeups/results`, **Live Plus only**

**Completed write-ups.** Full write-ups for races already run, with the preview, the report and a line on every runner, over the last hours or for one date.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `hours` | query | integer | no | `24` |
| `date` | query | date | no | `2026-09-18` |
| `region` | query | string | no | `GB` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ",
        "page": 1,
        "per_page": 50,
        "total": 1287,
        "total_pages": 26,
        "next_page": 2,
        "prev_page": null
    },
    "data": [
        {
            "race_id": 1245149,
            "region": "GB",
            "track": {
                "track_id": 1400000007,
                "name": "Central Park"
            },
            "race_number": 7,
            "scheduled_start": "2026-09-18T19:34:00Z",
            "status": "complete",
            "headline": "Silver Medal too good in D3 at 6/4",
            "preview": "Fawkham Sunrise has won 2 of its last 5 runs and has won twice at this track, while Frankton Flame has 1 win and 3 top-three finishes in its last 5 runs.",
            "report": "Silver Medal justified 6/4 favouritism, jumping very quickly from trap 3 and making all down the middle. Frankton Flame ran on to take second, 0.18 seconds behind.",
            "runners": [
                {
                    "trap": 3,
                    "dog_id": 650112,
                    "dog_name": "Silver Medal",
                    "position": 1,
                    "line": "Silver Medal won at 6/4, jumping very quickly from the traps and making all."
                }
            ],
            "written_at": "2026-09-21T10:52:14+00:00"
        }
    ]
}
```

#### `GET /v1/comments/vocabulary`, **Live Plus only**

**Comment vocabulary.** Every event, place, verdict and trait the comments endpoint can return, with its wording.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "in_running": {
            "regions": [
                "GB",
                "IE"
            ],
            "events": {
                "slow_start": "Slow from the traps",
                "crowded": "Short of room",
                "ran_on": "Kept on"
            },
            "places": {
                "bend_1": "at the first bend",
                "halfway": "at halfway",
                "run_in": "on the run home"
            }
        },
        "pre_race": {
            "regions": [
                "AU"
            ],
            "verdicts": {
                "contender": "A real chance, one to respect"
            },
            "traits": {
                "early_speed": "Quick early, likes to lead or race on the pace"
            }
        }
    }
}
```

### Dogs (10)

#### `GET /v1/dogs/{dog_id}/layoff`, **Live Plus only**

**Layoff record.** How a greyhound has run after different lengths of time off, shown beside how greyhounds generally run after the same break — because most dogs race weekly and have very few runs after a long one.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `dog_id` | path | integer | yes | `3069598489` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "dog_id": 1655044329,
        "name": "Full Monty",
        "region": "GB",
        "buckets": [
            {
                "days": "0-7",
                "description": "a week or less",
                "dog": {
                    "runs": 18,
                    "wins": 4,
                    "places": 11,
                    "win_pct": 22.22,
                    "place_pct": 61.11,
                    "avg_vs_par_s": 0.084,
                    "note": null
                },
                "all_dogs": {
                    "runs": 320294,
                    "win_pct": 18.5,
                    "place_pct": 55.64,
                    "avg_vs_par_s": 0.315,
                    "pace": {
                        "avg_split_vs_par_s": 0.0002,
                        "avg_closing_vs_par_s": -0.0177,
                        "runs_with_sectional": 92497
                    }
                }
            },
            {
                "days": "29-60",
                "description": "one to two months",
                "dog": {
                    "runs": 3,
                    "wins": 0,
                    "places": 1,
                    "win_pct": null,
                    "place_pct": null,
                    "avg_vs_par_s": null,
                    "note": "3 runs — too few for a rate, counts only."
                },
                "all_dogs": {
                    "runs": 11496,
                    "win_pct": 15.13,
                    "place_pct": 46.2,
                    "avg_vs_par_s": 0.344,
                    "pace": {
                        "avg_split_vs_par_s": 0,
                        "avg_closing_vs_par_s": 0.0903,
                        "runs_with_sectional": 7648
                    }
                }
            }
        ],
        "scope": {
            "min_runs_for_rate": 5,
            "population_window": "last 24 months, GB racing",
            "note": "Most greyhounds race weekly, so a dog has few runs after a long break. Its counts are shown regardless; the rate is suppressed below 5 runs and the population figures beside it give the comparison."
        },
        "note": "How this greyhound has run after different breaks, beside how greyhounds generally run after the same break. avg_vs_par_s is measured against the average winning time for each course, so it is not distorted by which tracks were run. Nothing here rates this greyhound or forecasts a future run."
    }
}
```

#### `GET /v1/dogs/{dog_id}/trend`, **Live Plus only**

**Performance trend.** Whether a greyhound is running quicker or slower over its recent races, each run measured against par for that track and distance, and the change reported beside the dog's own run-to-run variation so you can tell a trend from noise.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `dog_id` | path | integer | yes | `3069598489` |
| `track_id` | query | integer | no | `3905994177` |
| `distance_m` | query | integer | no | `400` |
| `window` | query | integer | no | `12` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "dog_id": 1655044329,
        "name": "Full Monty",
        "filter": {
            "track_id": null,
            "distance_m": null,
            "window": 12
        },
        "runs": [
            {
                "date": "2026-08-28",
                "track": "Romford",
                "distance_m": 400,
                "grade": "A3",
                "trap": 1,
                "position": 1,
                "time_s": 24.11,
                "par_s": 24.61,
                "vs_par_s": -0.5,
                "sectional_s": 3.68
            },
            {
                "date": "2026-08-21",
                "track": "Romford",
                "distance_m": 400,
                "grade": "A4",
                "trap": 3,
                "position": 2,
                "time_s": 24.29,
                "par_s": 24.61,
                "vs_par_s": -0.32,
                "sectional_s": 3.74
            }
        ],
        "trend": {
            "runs": 12,
            "runs_with_par": 12,
            "window": "first 6 vs last 6",
            "vs_par_change_s": -0.31,
            "run_to_run_stdev_s": 0.213,
            "note": "Change is larger than the run-to-run variation."
        },
        "note": "Each run is shown against par — the average winning time for that track and distance — so a fast night does not read as improvement. Grade is included because a drop in class means weaker opposition. Nothing here rates this greyhound or forecasts a future run."
    }
}
```

#### `GET /v1/dogs/search`

**Search dogs.** Find greyhounds by name, sire, dam or sex — the entry point for dog IDs used across the form, entries, prices and head-to-head endpoints.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `ids` | query | string | no |  |
| `name` | query | string | no | `Full Monty` |
| `sire` | query | string | no | `Ballymac Anton` |
| `dam` | query | string | no | `Ballymac Notoyou` |
| `sex` | query | string | no | `d` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ"
    },
    "data": [
        {
            "dog_id": 1655044329,
            "name": "Full Monty",
            "born": "2024-04-01",
            "age_months": 27,
            "sex": "d",
            "colour": "bebd",
            "season": null,
            "sire": "Ballymac Anton",
            "dam": "Ballymac Notoyou",
            "career": {
                "total_races": 12,
                "total_wins": 5,
                "win_pct": 41.67,
                "by_grade": [
                    {
                        "grade": "S1",
                        "races": 3,
                        "wins": 1
                    },
                    {
                        "grade": "A2",
                        "races": 9,
                        "wins": 4
                    }
                ],
                "first_run": "2025-09-14",
                "last_run": "2026-06-30",
                "days_since_last_run": 8
            }
        }
    ]
}
```

#### `GET /v1/dogs/{dog_id}`

**Get a dog.** A greyhound's full profile: identity, colour, breeding, seasonality and career statistics including per-grade record and days since last run.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `dog_id` | path | integer | yes | `3069598489` |

**Fetching more than one?** Do not call this in a loop — `/v1/dogs/search?ids=A-B-C` returns up to 50 dogs in a single request, and counts as one against your rate limit. See batch requests. Note this endpoint also returns career aggregates and breeding, which the list form does not. For bitches, `season` carries the recorded seasonality value as published (a date, `Suppressed`, `Unknown`, or `null`).

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "dog_id": 1655044329,
        "name": "Full Monty",
        "born": "2024-04-01",
        "age_months": 27,
        "sex": "d",
        "colour": "bebd",
        "season": null,
        "sire": "Ballymac Anton",
        "dam": "Ballymac Notoyou",
        "career": {
            "total_races": 12,
            "total_wins": 5,
            "win_pct": 41.67,
            "by_grade": [
                {
                    "grade": "S1",
                    "races": 3,
                    "wins": 1
                },
                {
                    "grade": "A2",
                    "races": 9,
                    "wins": 4
                }
            ],
            "first_run": "2025-09-14",
            "last_run": "2026-06-30",
            "days_since_last_run": 8
        }
    }
}
```

#### `GET /v1/dogs/{dog_id}/form`

**Dog form.** A greyhound's complete race-by-race history, newest first: track, grade, distance, trap, position, price, sectional, run time, weight and run comment.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `dog_id` | path | integer | yes | `3069598489` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ"
    },
    "data": [
        {
            "race_id": 1225410662,
            "date_local": "2026-06-30",
            "track": "Romford",
            "grade": "A2",
            "distance_m": 400,
            "trap": 2,
            "position": 1,
            "sp": {
                "fraction": "2/1",
                "decimal": 3
            },
            "sectional_s": 3.98,
            "run_time_s": 24.61,
            "weight_kg": 36.4
        }
    ]
}
```

#### `GET /v1/dogs/{dog_id}/entries`

**Dog upcoming entries.** A greyhound's declared upcoming runs: race, track, grade, distance, trap draw and track-local start time.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `dog_id` | path | integer | yes | `3069598489` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ"
    },
    "data": [
        {
            "race_id": 1229082445,
            "track": "Romford",
            "grade": "S1",
            "distance_m": 575,
            "trap": 1,
            "scheduled_start": {
                "utc": "2026-07-08T20:46:00Z",
                "local": "2026-07-08T21:46:00+01:00"
            }
        }
    ]
}
```

#### `GET /v1/dogs/{dog_id}/prices`

**Dog price history.** Every historical run for a greyhound with its starting price and the Betfair back and lay prices taken just before the off — factual market data across the dog's whole career.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `dog_id` | path | integer | yes | `3069598489` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |

**Lay prices are available from 2 September 2026 onward**, on about 94% of runners. Before that date the lay side of the market was not captured and the field is `null` — that is the absence of a recording, not a market without a lay side. Starting prices and Betfair BACK prices are unaffected and run the full length of the archive.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ"
    },
    "data": [
        {
            "race_id": 1225410662,
            "date_local": "2026-06-30",
            "track": "Romford",
            "position": 1,
            "sp": {
                "fraction": "2/1",
                "decimal": 3
            },
            "betfair": {
                "selection_id": 84512233,
                "back_price": 3.55,
                "lay_price": 3.6,
                "last_update": "2026-07-08T20:41:02Z"
            }
        }
    ]
}
```

#### `GET /v1/dogs/{dog_id}/head-to-head/{rival_id}`

**Head-to-head.** Compare two greyhounds directly: shared races, who finished ahead and how often, plus each dog's career record — the factual rivalry view.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `dog_id` | path | integer | yes | `3069598489` |
| `rival_id` | path | integer | yes | `2550860186` |

Try it live on the [sandbox page](https://greyhoundapi.com/sandbox) — this endpoint is included in the free tier.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "dogs": [
            {
                "dog_id": 1655044329,
                "name": "Full Monty",
                "career": {
                    "total_races": 12,
                    "total_wins": 5,
                    "win_pct": 41.67
                }
            },
            {
                "dog_id": 1654595188,
                "name": "Miami Yeats",
                "career": {
                    "total_races": 14,
                    "total_wins": 4,
                    "win_pct": 28.57
                }
            }
        ],
        "shared_races": 3,
        "finished_ahead": {
            "655044": 2,
            "654595": 1
        },
        "races": [
            {
                "race_id": 1229082445,
                "date_local": "2026-07-08",
                "track": "Romford",
                "grade": "S1",
                "distance_m": 575,
                "positions": {
                    "655044": 3,
                    "654595": 1
                }
            }
        ]
    }
}
```

#### `GET /v1/dogs/{dog_id}/pace`, **Live Plus only**

**Dog pace profile.** How a greyhound RUNS a race rather than how fast: its average rank at the first bend, its average finish, and the gap between them. Positive means it passes runners, negative means it comes back to the field. Rebuilt nightly from every timed run.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `dog_id` | path | integer | no | `2481509210` |

Every figure is a RANK within the dog's own race, so nothing needs adjusting for going, track or distance — runners are compared against the six that stood beside them. Sectionals are the only per-runner quantity the source measures rather than estimates, so nothing here is built on a derived time. A profile needs 15 runs (20 per trip): about a quarter of the run-to-run variation is the dog and the rest is the night, and that is where a dog average becomes reliable enough to label. `reliability` is returned so you can weight it rather than trust it flat.

```json
{
    "dog": {
        "dog_id": 2481509210,
        "name": "A Bad Winter"
    },
    "pace": {
        "runs": 47,
        "runs_full_field": 47,
        "profile": "consistent_pace",
        "reliability": 0.93,
        "avg_bend_rank": 2.87,
        "avg_finish": 3,
        "position_gain": -0.13,
        "gain_consistency": 1.38,
        "led_at_bend": 21.3,
        "top_2_at_bend": 46.8,
        "win_rate": 21.3
    },
    "by_trip": [
        {
            "track": {
                "track_id": 3822562785,
                "name": "Kinsley"
            },
            "distance_m": 462,
            "runs": 28,
            "runs_full_field": 28,
            "profile": "mid_race_mover",
            "reliability": 0.9,
            "avg_bend_rank": 3.36,
            "avg_finish": 2.96,
            "position_gain": 0.39,
            "gain_consistency": 1.37,
            "led_at_bend": 7.1,
            "top_2_at_bend": 35.7,
            "win_rate": 21.4
        }
    ],
    "method": {
        "measured_from": "sectional rank within each race, and finishing position",
        "minimum_runs": {
            "overall": 15,
            "per_trip": 20
        },
        "position_gain": "bend rank minus finish. + passes runners, - fades."
    }
}
```

#### `GET /v1/dogs/{dog_id}/stats`, **Live Plus only**

**Dog statistics.** Career splits for one greyhound: strike rate by distance, trap, track, grade, going and trainer, with best and average winning times in each — what tends to happen, rather than what happened. Narrow every split to one track and distance with track_id and distance_m.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `dog_id` | path | integer | yes | `3069598489` |
| `track_id` | query | integer | no | `3905994177` |
| `distance_m` | query | integer | no | `400` |

`best_time_s` and `avg_time_s` appear **only in `by_distance`**. A time averaged across mixed trips is meaningless — a 268m sprint and a 500m race do not share a scale — so the fields are omitted elsewhere rather than shipped with a caveat. `going` is banded (fast, slightly fast, standard, slightly slow, slow) because the raw allowance splits a career into slices too small to read; the underlying values are returned in `going_values`. Completed runs only — reserves and withdrawals excluded, and a split with no runs is omitted rather than returned as zero.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "dog_id": 1655044329,
        "name": "A Bad Winter",
        "overall": {
            "runs": 59,
            "wins": 11,
            "places": 33,
            "win_pct": 18.64,
            "place_pct": 55.93,
            "first_run": "2022-03-25",
            "last_run": "2024-09-20"
        },
        "by_distance": [
            {
                "distance_m": 462,
                "runs": 28,
                "wins": 6,
                "places": 18,
                "win_pct": 21.43,
                "place_pct": 64.29,
                "best_time_s": 28.02,
                "avg_time_s": 28.5
            },
            {
                "distance_m": 500,
                "runs": 15,
                "wins": 4,
                "places": 11,
                "win_pct": 26.67,
                "place_pct": 73.33,
                "best_time_s": 29.05,
                "avg_time_s": 29.46
            }
        ],
        "by_trap": [
            {
                "trap": 1,
                "runs": 40,
                "wins": 9,
                "places": 23,
                "win_pct": 22.5,
                "place_pct": 57.5
            }
        ],
        "by_track": [
            {
                "track": "Kinsley",
                "runs": 39,
                "wins": 7,
                "places": 21,
                "win_pct": 17.95,
                "place_pct": 53.85
            }
        ],
        "by_grade": [
            {
                "grade": "A4",
                "runs": 14,
                "wins": 3,
                "places": 9,
                "win_pct": 21.43,
                "place_pct": 64.29
            }
        ],
        "by_going": [
            {
                "going": "standard",
                "going_values": [
                    0
                ],
                "runs": 28,
                "wins": 7,
                "places": 16,
                "win_pct": 25,
                "place_pct": 57.14
            }
        ],
        "by_trainer": [
            {
                "trainer": {
                    "trainer_id": 3095937498,
                    "name": "M E Wiley"
                },
                "runs": 27,
                "wins": 8,
                "places": 17,
                "win_pct": 29.63,
                "place_pct": 62.96,
                "avg_vs_par_s": -0.05,
                "first_run": "2025-04-01",
                "last_run": "2026-05-18",
                "note": null
            },
            {
                "trainer": {
                    "trainer_id": 1180000118,
                    "name": "P W Young"
                },
                "runs": 3,
                "wins": 0,
                "places": 1,
                "win_pct": null,
                "place_pct": null,
                "avg_vs_par_s": 0.277,
                "first_run": "2026-06-11",
                "last_run": "2026-08-11",
                "note": "3 runs — too few for a rate, counts only."
            }
        ]
    }
}
```

### Results (4)

#### `GET /v1/results/today`

**Today's results.** Every greyhound race completed today across GB, AU and IE, most recent first, with finishing order, prices, times and dividends.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `region` | query | string | no | `GB` |
| `track_id` | query | integer | no | `3905994177` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |
| `page` | query | integer | no | `1` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ",
        "page": 1,
        "per_page": 50,
        "total": 1287,
        "total_pages": 26,
        "next_page": 2,
        "prev_page": null
    },
    "data": [
        {
            "race_id": 1229082445,
            "meeting_id": 1449422077,
            "region": "GB",
            "track": {
                "track_id": 1400000014,
                "name": "Romford",
                "region": "GB",
                "timezone": "Europe/London"
            },
            "race_number": 12,
            "grade": "S1",
            "class_letter": "S",
            "class_number": 1,
            "distance_m": 575,
            "race_type": "Flat",
            "is_handicap": false,
            "going": null,
            "field_size": 6,
            "scheduled_start": {
                "utc": "2026-07-08T20:46:00Z",
                "local": "2026-07-08T21:46:00+01:00"
            },
            "status": "complete",
            "off": {
                "utc": "2026-07-08T20:47:12Z",
                "delay_s": 72
            },
            "freshness": {
                "first_seen": "2026-07-07T05:12:10Z",
                "results_received": "2026-07-08T21:03:40Z",
                "results_delay_s": 1060
            },
            "result": {
                "positions": [
                    {
                        "position": 1,
                        "trap": 3,
                        "dog_id": 1654595188,
                        "dog_name": "Miami Yeats",
                        "trainer": {
                            "trainer_id": 1210000121,
                            "name": "M E Wiley"
                        },
                        "sp": {
                            "fraction": "5/2",
                            "decimal": 3.5
                        },
                        "sectional_s": 4.31,
                        "run_time_s": 35.42,
                        "adjusted_time_s": 35.42,
                        "weight_kg": 34.4,
                        "betfair": {
                            "selection_id": 84512233,
                            "back_price": 3.55,
                            "lay_price": 3.6,
                            "last_update": "2026-07-08T20:41:02Z"
                        }
                    }
                ],
                "forecast_dividend": "12.40",
                "tricast_dividend": "48.12",
                "off": {
                    "utc": "2026-07-08T20:47:12Z",
                    "delay_s": 72
                },
                "received": {
                    "utc": "2026-07-08T21:03:40Z",
                    "delay_s": 1060
                },
                "weather": {
                    "hour_utc": "2026-07-08T20:00:00Z",
                    "temp_c": 15.2,
                    "humidity_pct": 81,
                    "wind_kph": 19.5,
                    "gust_kph": 42.8,
                    "wind_from": "W",
                    "wind_deg": 271,
                    "precip_mm": 0.3,
                    "conditions": "light drizzle",
                    "before_race": {
                        "rain_6h_mm": 1.6,
                        "rain_24h_mm": 2,
                        "wind_avg_6h_kph": 21.4
                    },
                    "source": "archive"
                }
            }
        }
    ]
}
```

#### `GET /v1/results/yesterday`

**Yesterday's results.** Every greyhound race completed yesterday across GB, AU and IE, most recent first, with finishing order, prices, times and dividends.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `region` | query | string | no | `GB` |
| `track_id` | query | integer | no | `3905994177` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |
| `page` | query | integer | no | `1` |

Yesterday is the racing day just gone AT EACH TRACK, counted in that track's own timezone rather than as a UTC date — so an Australian card is returned whole rather than split across two calls. The same race therefore appears here and in `/v1/results/today` at no point; the two never overlap.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ",
        "page": 1,
        "per_page": 50,
        "total": 1287,
        "total_pages": 26,
        "next_page": 2,
        "prev_page": null
    },
    "data": [
        {
            "race_id": 1229082445,
            "meeting_id": 1449422077,
            "region": "GB",
            "track": {
                "track_id": 1400000014,
                "name": "Romford",
                "region": "GB",
                "timezone": "Europe/London"
            },
            "race_number": 12,
            "grade": "S1",
            "class_letter": "S",
            "class_number": 1,
            "distance_m": 575,
            "race_type": "Flat",
            "is_handicap": false,
            "going": null,
            "field_size": 6,
            "scheduled_start": {
                "utc": "2026-07-08T20:46:00Z",
                "local": "2026-07-08T21:46:00+01:00"
            },
            "status": "complete",
            "off": {
                "utc": "2026-07-08T20:47:12Z",
                "delay_s": 72
            },
            "freshness": {
                "first_seen": "2026-07-07T05:12:10Z",
                "results_received": "2026-07-08T21:03:40Z",
                "results_delay_s": 1060
            },
            "result": {
                "positions": [
                    {
                        "position": 1,
                        "trap": 3,
                        "dog_id": 1654595188,
                        "dog_name": "Miami Yeats",
                        "trainer": {
                            "trainer_id": 1210000121,
                            "name": "M E Wiley"
                        },
                        "sp": {
                            "fraction": "5/2",
                            "decimal": 3.5
                        },
                        "sectional_s": 4.31,
                        "run_time_s": 35.42,
                        "adjusted_time_s": 35.42,
                        "weight_kg": 34.4,
                        "betfair": {
                            "selection_id": 84512233,
                            "back_price": 3.55,
                            "lay_price": 3.6,
                            "last_update": "2026-07-08T20:41:02Z"
                        }
                    }
                ],
                "forecast_dividend": "12.40",
                "tricast_dividend": "48.12",
                "off": {
                    "utc": "2026-07-08T20:47:12Z",
                    "delay_s": 72
                },
                "received": {
                    "utc": "2026-07-08T21:03:40Z",
                    "delay_s": 1060
                },
                "weather": {
                    "hour_utc": "2026-07-08T20:00:00Z",
                    "temp_c": 15.2,
                    "humidity_pct": 81,
                    "wind_kph": 19.5,
                    "gust_kph": 42.8,
                    "wind_from": "W",
                    "wind_deg": 271,
                    "precip_mm": 0.3,
                    "conditions": "light drizzle",
                    "before_race": {
                        "rain_6h_mm": 1.6,
                        "rain_24h_mm": 2,
                        "wind_avg_6h_kph": 21.4
                    },
                    "source": "archive"
                }
            }
        }
    ]
}
```

#### `GET /v1/results`

**Search results.** Search historical greyhound results across the whole archive by date range, region, track, grade, distance, dog, trainer or owner.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `ids` | query | string | no |  |
| `date_from` | query | date | no | `2026-07-01` |
| `date_to` | query | date | no | `2026-07-07` |
| `region` | query | string | no | `GB` |
| `track_id` | query | integer | no | `3905994177` |
| `grade` | query | string | no | `A2` |
| `distance_m` | query | integer | no | `575` |
| `dog_id` | query | integer | no | `3069598489` |
| `trainer_id` | query | integer | no | `3340692099` |
| `owner_id` | query | integer | no | `1889531601` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |
| `page` | query | integer | no | `1` |

Returns races with a recorded result. `meta.unresolved` counts races that ran inside the same query but have no result — abandoned meetings, void races, and cards that were never settled. A day showing `total: 130` with `unresolved: 10` means the card was 140 and ten races have no finishing positions anywhere. Races scheduled within the last six hours are excluded from the count, so tonight's cards do not inflate it.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ",
        "page": 1,
        "per_page": 50,
        "total": 1287,
        "total_pages": 26,
        "next_page": 2,
        "prev_page": null,
        "unresolved": 12
    },
    "data": [
        {
            "race_id": 1229082445,
            "meeting_id": 1449422077,
            "region": "GB",
            "track": {
                "track_id": 1400000014,
                "name": "Romford",
                "region": "GB",
                "timezone": "Europe/London"
            },
            "race_number": 12,
            "grade": "S1",
            "class_letter": "S",
            "class_number": 1,
            "distance_m": 575,
            "race_type": "Flat",
            "is_handicap": false,
            "going": null,
            "field_size": 6,
            "scheduled_start": {
                "utc": "2026-07-08T20:46:00Z",
                "local": "2026-07-08T21:46:00+01:00"
            },
            "status": "complete",
            "off": {
                "utc": "2026-07-08T20:47:12Z",
                "delay_s": 72
            },
            "freshness": {
                "first_seen": "2026-07-07T05:12:10Z",
                "results_received": "2026-07-08T21:03:40Z",
                "results_delay_s": 1060
            },
            "result": {
                "positions": [
                    {
                        "position": 1,
                        "trap": 3,
                        "dog_id": 1654595188,
                        "dog_name": "Miami Yeats",
                        "trainer": {
                            "trainer_id": 1210000121,
                            "name": "M E Wiley"
                        },
                        "sp": {
                            "fraction": "5/2",
                            "decimal": 3.5
                        },
                        "sectional_s": 4.31,
                        "run_time_s": 35.42,
                        "adjusted_time_s": 35.42,
                        "weight_kg": 34.4,
                        "betfair": {
                            "selection_id": 84512233,
                            "back_price": 3.55,
                            "lay_price": 3.6,
                            "last_update": "2026-07-08T20:41:02Z"
                        }
                    }
                ],
                "forecast_dividend": "12.40",
                "tricast_dividend": "48.12",
                "off": {
                    "utc": "2026-07-08T20:47:12Z",
                    "delay_s": 72
                },
                "received": {
                    "utc": "2026-07-08T21:03:40Z",
                    "delay_s": 1060
                },
                "weather": {
                    "hour_utc": "2026-07-08T20:00:00Z",
                    "temp_c": 15.2,
                    "humidity_pct": 81,
                    "wind_kph": 19.5,
                    "gust_kph": 42.8,
                    "wind_from": "W",
                    "wind_deg": 271,
                    "precip_mm": 0.3,
                    "conditions": "light drizzle",
                    "before_race": {
                        "rain_6h_mm": 1.6,
                        "rain_24h_mm": 2,
                        "wind_avg_6h_kph": 21.4
                    },
                    "source": "archive"
                }
            }
        }
    ]
}
```

#### `GET /v1/results/latest`

**Latest results.** Results ordered by the moment they reached the pipeline — the polling companion to the WebSocket stream, with a since cursor for gapless catch-up.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `since` | query | datetime | no | `2026-07-08T20:00:00Z` |
| `region` | query | string | no | `GB` |
| `limit` | query | integer | no | `50` |

Ordered by `freshness.results_received`, not by off-time — that is what makes catch-up gapless after a disconnect. Pairs with the [WebSocket stream](https://greyhoundapi.com/documentation/websocket).

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ",
        "page": 1,
        "per_page": 50,
        "total": 1287,
        "total_pages": 26,
        "next_page": 2,
        "prev_page": null
    },
    "data": [
        {
            "race_id": 1229082445,
            "meeting_id": 1449422077,
            "region": "GB",
            "track": {
                "track_id": 1400000014,
                "name": "Romford",
                "region": "GB",
                "timezone": "Europe/London"
            },
            "race_number": 12,
            "grade": "S1",
            "class_letter": "S",
            "class_number": 1,
            "distance_m": 575,
            "race_type": "Flat",
            "is_handicap": false,
            "going": null,
            "field_size": 6,
            "scheduled_start": {
                "utc": "2026-07-08T20:46:00Z",
                "local": "2026-07-08T21:46:00+01:00"
            },
            "status": "complete",
            "off": {
                "utc": "2026-07-08T20:47:12Z",
                "delay_s": 72
            },
            "freshness": {
                "first_seen": "2026-07-07T05:12:10Z",
                "results_received": "2026-07-08T21:03:40Z",
                "results_delay_s": 1060
            },
            "result": {
                "positions": [
                    {
                        "position": 1,
                        "trap": 3,
                        "dog_id": 1654595188,
                        "dog_name": "Miami Yeats",
                        "trainer": {
                            "trainer_id": 1210000121,
                            "name": "M E Wiley"
                        },
                        "sp": {
                            "fraction": "5/2",
                            "decimal": 3.5
                        },
                        "sectional_s": 4.31,
                        "run_time_s": 35.42,
                        "adjusted_time_s": 35.42,
                        "weight_kg": 34.4,
                        "betfair": {
                            "selection_id": 84512233,
                            "back_price": 3.55,
                            "lay_price": 3.6,
                            "last_update": "2026-07-08T20:41:02Z"
                        }
                    }
                ],
                "forecast_dividend": "12.40",
                "tricast_dividend": "48.12",
                "off": {
                    "utc": "2026-07-08T20:47:12Z",
                    "delay_s": 72
                },
                "received": {
                    "utc": "2026-07-08T21:03:40Z",
                    "delay_s": 1060
                },
                "weather": {
                    "hour_utc": "2026-07-08T20:00:00Z",
                    "temp_c": 15.2,
                    "humidity_pct": 81,
                    "wind_kph": 19.5,
                    "gust_kph": 42.8,
                    "wind_from": "W",
                    "wind_deg": 271,
                    "precip_mm": 0.3,
                    "conditions": "light drizzle",
                    "before_race": {
                        "rain_6h_mm": 1.6,
                        "rain_24h_mm": 2,
                        "wind_avg_6h_kph": 21.4
                    },
                    "source": "archive"
                }
            }
        }
    ]
}
```

### Meetings (5)

#### `GET /v1/meetings`

**Search meetings.** Find greyhound meetings by track-local date, region and track — each with a summary of the races staged at the fixture.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `date` | query | date | no | `2026-07-08` |
| `region` | query | string | no | `GB` |
| `track_id` | query | integer | no | `3905994177` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ"
    },
    "data": [
        {
            "meeting_id": 1449422077,
            "track": {
                "track_id": 1400000014,
                "name": "Romford",
                "region": "GB",
                "timezone": "Europe/London"
            },
            "date_local": "2026-07-08",
            "races": [
                {
                    "race_id": 1229082445,
                    "race_number": 12,
                    "grade": "S1",
                    "distance_m": 575,
                    "scheduled_start": {
                        "utc": "2026-07-08T20:46:00Z",
                        "local": "2026-07-08T21:46:00+01:00"
                    },
                    "status": "scheduled",
                    "field_size": 6
                }
            ]
        }
    ]
}
```

#### `GET /v1/meetings/today`

**Today's meetings.** Today's greyhound fixtures across GB, AU and IE, each meeting with its full race list, resolved per track-local calendar day.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `region` | query | string | no | `GB` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ"
    },
    "data": [
        {
            "meeting_id": 1449422077,
            "track": {
                "track_id": 1400000014,
                "name": "Romford",
                "region": "GB",
                "timezone": "Europe/London"
            },
            "date_local": "2026-07-08",
            "races": [
                {
                    "race_id": 1229082445,
                    "race_number": 12,
                    "grade": "S1",
                    "distance_m": 575,
                    "scheduled_start": {
                        "utc": "2026-07-08T20:46:00Z",
                        "local": "2026-07-08T21:46:00+01:00"
                    },
                    "status": "scheduled",
                    "field_size": 6
                }
            ]
        }
    ]
}
```

#### `GET /v1/meetings/calendar`

**Racing calendar.** Which tracks raced on which days: one row per track per day with the number of meetings and races on each. Coverage as data, in a single call instead of one per day.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `date_from` | query | date | no | `2026-07-01` |
| `date_to` | query | date | no | `2026-07-07` |
| `region` | query | string | no | `GB` |
| `track_id` | query | integer | no | `3905994177` |
| `limit` | query | integer | no | `50` |

Dates are each track's OWN local date, so an Australian evening card lands on the day it ran locally rather than the UTC day it started. Only days with racing appear — a missing day means nothing ran, which is the point of the endpoint. Defaults to the last 30 days; `date_from` reaches back as far as the archive of daily counts goes, currently 400 days. The same view in a browser is the [racing calendar](https://greyhoundapi.com/racing-calendar), and [What we cover](https://greyhoundapi.com/documentation/coverage) explains the edges.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 3,
        "date_from": "2026-06-09",
        "date_to": "2026-07-08"
    },
    "data": [
        {
            "date": "2026-07-08",
            "track_id": 1400000014,
            "track": "Romford",
            "region": "GB",
            "meetings": 1,
            "races": 12
        },
        {
            "date": "2026-07-08",
            "track_id": 1610000061,
            "track": "Shepparton",
            "region": "AU",
            "meetings": 1,
            "races": 10
        },
        {
            "date": "2026-07-07",
            "track_id": 1400000014,
            "track": "Romford",
            "region": "GB",
            "meetings": 2,
            "races": 24
        }
    ]
}
```

#### `GET /v1/meetings/{meeting_id}/analysis`, **Live Plus only**

**Meeting analysis.** A whole card as one unit: how fast the track actually ran, measured against the average winning time for each distance, and whether the published going agreed with it.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `meeting_id` | path | integer | yes | `2360100585` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "meeting_id": 1449422077,
        "track": {
            "track_id": 1400000014,
            "name": "Romford",
            "region": "GB",
            "timezone": "Europe/London"
        },
        "date_local": "2026-09-02",
        "track_speed": {
            "measured_vs_par_s": -0.14,
            "spread_s": 0.231,
            "races_measured": 12,
            "published_going": 20,
            "published_implies_s": -0.2,
            "agreement_s": 0.06,
            "note": "Measured and published agree to within a tenth of a second."
        },
        "summary": {
            "races": 12,
            "races_with_times": 12,
            "distances_m": [
                400,
                575
            ],
            "grades": [
                "A1",
                "A2",
                "A3",
                "S1"
            ],
            "fastest": {
                "race_id": 1229082445,
                "vs_par_s": -0.53
            }
        },
        "trap_wins": [
            {
                "trap": 1,
                "wins": 3
            },
            {
                "trap": 2,
                "wins": 1
            },
            {
                "trap": 3,
                "wins": 3
            },
            {
                "trap": 4,
                "wins": 2
            },
            {
                "trap": 5,
                "wins": 1
            },
            {
                "trap": 6,
                "wins": 2
            }
        ],
        "races": [
            {
                "race_id": 1229082445,
                "race_number": 1,
                "grade": "A3",
                "distance_m": 400,
                "winner_trap": 3,
                "winning_time_s": 24.08,
                "vs_par_s": -0.53
            }
        ],
        "note": "How this card ran, measured against the average winning time for each distance at this track. Trap wins are counts from a single card and are far too few to indicate bias — the trap statistics on /races/{id}/conditions draw on thousands of races. Nothing here forecasts a future race."
    }
}
```

#### `GET /v1/meetings/{meeting_id}`

**Get a meeting.** One meeting in full: the track, the track-local date and every race staged at the fixture with grade, distance, start time and status.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `meeting_id` | path | integer | yes | `2360100585` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "meeting_id": 1449422077,
        "track": {
            "track_id": 1400000014,
            "name": "Romford",
            "region": "GB",
            "timezone": "Europe/London"
        },
        "date_local": "2026-07-08",
        "races": [
            {
                "race_id": 1229082445,
                "race_number": 12,
                "grade": "S1",
                "distance_m": 575,
                "scheduled_start": {
                    "utc": "2026-07-08T20:46:00Z",
                    "local": "2026-07-08T21:46:00+01:00"
                },
                "status": "scheduled",
                "field_size": 6
            }
        ]
    }
}
```

### Trainers (5)

#### `GET /v1/trainers/search`

**Search trainers.** Find greyhound trainers by name and get the trainer IDs used across the profile, runners and results endpoints.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `name` | query | string | yes | `Wiley` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ"
    },
    "data": [
        {
            "trainer_id": 1210000121,
            "name": "M E Wiley",
            "slug": "m-e-wiley",
            "regions": [
                "GB"
            ],
            "stats": {
                "runners_12m": 486,
                "wins_12m": 118,
                "win_pct_12m": 24.28,
                "by_track": [
                    {
                        "track": "Romford",
                        "runners": 302,
                        "wins": 81
                    }
                ]
            }
        }
    ]
}
```

#### `GET /v1/trainers/{trainer_id}`

**Get a trainer.** A trainer's profile with factual aggregates: runners and wins over the last 12 months, strike rate and the per-track breakdown.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `trainer_id` | path | integer | yes | `3340692099` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "trainer_id": 1210000121,
        "name": "M E Wiley",
        "slug": "m-e-wiley",
        "regions": [
            "GB"
        ],
        "stats": {
            "runners_12m": 486,
            "wins_12m": 118,
            "win_pct_12m": 24.28,
            "by_track": [
                {
                    "track": "Romford",
                    "runners": 302,
                    "wins": 81
                }
            ]
        }
    }
}
```

#### `GET /v1/trainers/{trainer_id}/runners`

**Trainer upcoming runners.** Every upcoming declared runner for a trainer across GB, AU and IE, with race, trap, grade, distance and track-local start time.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `trainer_id` | path | integer | yes | `3340692099` |
| `region` | query | string | no | `GB` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ"
    },
    "data": [
        {
            "race_id": 1229081338,
            "track": "Romford",
            "grade": "A2",
            "distance_m": 400,
            "trap": 5,
            "dog_id": 1657712904,
            "dog_name": "Headford Eimear",
            "scheduled_start": {
                "utc": "2026-07-08T20:28:00Z",
                "local": "2026-07-08T21:28:00+01:00"
            }
        }
    ]
}
```

#### `GET /v1/trainers/{trainer_id}/results`

**Trainer results.** A trainer's historical results, newest first: every runner with race, position, price and time, filterable by date range.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `trainer_id` | path | integer | yes | `3340692099` |
| `date_from` | query | date | no | `2026-07-01` |
| `date_to` | query | date | no | `2026-07-07` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ"
    },
    "data": [
        {
            "dog_id": 1657712904,
            "dog_name": "Headford Eimear",
            "race_id": 1225410662,
            "date_local": "2026-06-30",
            "track": "Romford",
            "grade": "A2",
            "distance_m": 400,
            "trap": 2,
            "position": 1,
            "sp": {
                "fraction": "2/1",
                "decimal": 3
            },
            "sectional_s": 3.98,
            "run_time_s": 24.61,
            "weight_kg": 36.4
        }
    ]
}
```

#### `GET /v1/trainers/{trainer_id}/stats`, **Live Plus only**

**Trainer statistics.** Strike rate for one trainer split by track, distance, trap and grade over a rolling window — answering how a yard performs AT A GIVEN TRACK, which a flat results list cannot.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `trainer_id` | path | integer | yes | `3340692099` |
| `months` | query | integer | no | `12` |
| `track_id` | query | integer | no | `3905994177` |

The window is bounded on purpose: a busy yard has thousands of runners and an unbounded scan would be slow for every caller. Widen it with `months`, up to 60. As with dog statistics, times appear only in `by_distance`.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "trainer_id": 1180000118,
        "name": "M E Wiley",
        "window_months": 12,
        "track_id": null,
        "overall": {
            "runs": 1887,
            "wins": 342,
            "places": 991,
            "win_pct": 18.12,
            "place_pct": 52.52,
            "first_run": "2025-08-19",
            "last_run": "2026-08-18"
        },
        "by_track": [
            {
                "track": "Romford",
                "runs": 1831,
                "wins": 335,
                "places": 959,
                "win_pct": 18.3,
                "place_pct": 52.38
            },
            {
                "track": "Central Park",
                "runs": 43,
                "wins": 6,
                "places": 26,
                "win_pct": 13.95,
                "place_pct": 60.47
            }
        ],
        "by_distance": [
            {
                "distance_m": 400,
                "runs": 1687,
                "wins": 304,
                "places": 886,
                "win_pct": 18.02,
                "place_pct": 52.52,
                "best_time_s": 23.64,
                "avg_time_s": 24.93
            }
        ],
        "by_going": [
            {
                "going": "standard",
                "going_values": [
                    0
                ],
                "runs": 210,
                "wins": 61,
                "places": 118,
                "win_pct": 29.05,
                "place_pct": 56.19
            }
        ],
        "by_trap": [
            {
                "trap": 1,
                "runs": 370,
                "wins": 89,
                "places": 219,
                "win_pct": 24.05,
                "place_pct": 59.19
            }
        ],
        "by_grade": [
            {
                "grade": "A12",
                "runs": 313,
                "wins": 49,
                "places": 157,
                "win_pct": 15.65,
                "place_pct": 50.16
            }
        ]
    }
}
```

### Owners (3)

#### `GET /v1/owners/search`

**Search owners.** Find greyhound owners and syndicates by name — the entry point for owner IDs used on the owner profile endpoint.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `name` | query | string | yes | `Bowmer` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ"
    },
    "data": [
        {
            "owner_id": 2231000223,
            "name": "Mr G D R Bowmer, Mr G J Bowmer",
            "slug": "mr-g-d-r-bowmer-mr-g-j-bowmer",
            "dogs": [
                {
                    "dog_id": 1655044329,
                    "name": "Full Monty"
                }
            ]
        }
    ]
}
```

#### `GET /v1/owners/{owner_id}`

**Get an owner.** An owner or syndicate in full: registered greyhounds, upcoming entries and the results achieved under the ownership.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `owner_id` | path | integer | yes | `1889531601` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "owner_id": 2231000223,
        "name": "Mr G D R Bowmer, Mr G J Bowmer",
        "slug": "mr-g-d-r-bowmer-mr-g-j-bowmer",
        "dogs": [
            {
                "dog_id": 1655044329,
                "name": "Full Monty"
            }
        ]
    }
}
```

#### `GET /v1/owners/{owner_id}/stats`, **Live Plus only**

**Owner statistics.** Strike rate for one ownership by track, distance, trap and grade — and by trainer, the question only an owner can answer: which yards is this ownership actually winning with.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `owner_id` | path | integer | yes | `1889531601` |
| `months` | query | integer | no | `12` |
| `track_id` | query | integer | no | `3905994177` |

`by_trainer` is unique to this endpoint. Same rolling window as trainer statistics.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "owner_id": 2231000223,
        "name": "Justin Bowman",
        "window_months": 12,
        "track_id": null,
        "overall": {
            "runs": 97,
            "wins": 9,
            "places": 34,
            "win_pct": 9.28,
            "place_pct": 35.05,
            "first_run": "2025-08-24",
            "last_run": "2026-08-16"
        },
        "by_trainer": [
            {
                "trainer": "Justin Bowman",
                "runs": 66,
                "wins": 8,
                "places": 28,
                "win_pct": 12.12,
                "place_pct": 42.42
            },
            {
                "trainer": "Chloe Bowman",
                "runs": 19,
                "wins": 0,
                "places": 3,
                "win_pct": 0,
                "place_pct": 15.79
            }
        ],
        "by_track": [
            {
                "track": "Sale",
                "runs": 40,
                "wins": 3,
                "places": 16,
                "win_pct": 7.5,
                "place_pct": 40
            }
        ],
        "by_distance": [
            {
                "distance_m": 435,
                "runs": 28,
                "wins": 2,
                "places": 10,
                "win_pct": 7.14,
                "place_pct": 35.71,
                "best_time_s": 24.58,
                "avg_time_s": 25.21
            }
        ],
        "by_going": [
            {
                "going": "standard",
                "going_values": [
                    0
                ],
                "runs": 51,
                "wins": 5,
                "places": 18,
                "win_pct": 9.8,
                "place_pct": 35.29
            }
        ],
        "by_trap": [
            {
                "trap": 6,
                "runs": 9,
                "wins": 3,
                "places": 6,
                "win_pct": 33.33,
                "place_pct": 66.67
            }
        ],
        "by_grade": [
            {
                "grade": "G5",
                "runs": 97,
                "wins": 9,
                "places": 34,
                "win_pct": 9.28,
                "place_pct": 35.05
            }
        ]
    }
}
```

### Tracks (4)

#### `GET /v1/tracks`

**List tracks.** Every GB, AU and IE greyhound track that has raced in the last two weeks (courses currently running, not the all-time roster) with its region, IANA timezone, active flag, the distances raced there and the grades it has actually carded in the last four weeks.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `ids` | query | string | no |  |
| `region` | query | string | no | `GB` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 2,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ"
    },
    "data": [
        {
            "track_id": 1400000014,
            "name": "Romford",
            "slug": "romford",
            "region": "GB",
            "timezone": "Europe/London",
            "active": true,
            "distances": [
                225,
                400,
                575,
                750
            ]
        },
        {
            "track_id": 1610000061,
            "name": "Shepparton",
            "slug": "shepparton",
            "region": "AU",
            "timezone": "Australia/Melbourne",
            "active": true,
            "distances": [
                385,
                450,
                650
            ],
            "grades": [
                "5",
                "FFA",
                "M",
                "T3"
            ]
        }
    ]
}
```

#### `GET /v1/tracks/{track_id}`

**Get a track.** One track's record: name, region, timezone, active status, every distance raced there and the grades carded in the last four weeks.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `track_id` | path | integer | yes | `3905994177` |

**Fetching more than one?** Do not call this in a loop — `/v1/tracks?ids=A-B-C` returns up to 50 tracks in a single request, and counts as one against your rate limit. See batch requests. Note this endpoint also returns distances and grade coverage, which the list form does not.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "track_id": 1400000014,
        "name": "Romford",
        "slug": "romford",
        "region": "GB",
        "timezone": "Europe/London",
        "active": true,
        "distances": [
            225,
            400,
            575,
            750
        ]
    }
}
```

#### `GET /v1/tracks/{track_id}/races`

**Races at a track.** Racecards and results at one track, filterable by track-local date range and lifecycle status.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `track_id` | path | integer | yes | `3905994177` |
| `date_from` | query | date | no | `2026-07-01` |
| `date_to` | query | date | no | `2026-07-07` |
| `status` | query | string | no | `complete` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |
| `page` | query | integer | no | `1` |

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ",
        "page": 1,
        "per_page": 50,
        "total": 1287,
        "total_pages": 26,
        "next_page": 2,
        "prev_page": null
    },
    "data": [
        {
            "race_id": 1229082445,
            "meeting_id": 1449422077,
            "region": "GB",
            "track": {
                "track_id": 1400000014,
                "name": "Romford",
                "region": "GB",
                "timezone": "Europe/London"
            },
            "race_number": 12,
            "grade": "S1",
            "class_letter": "S",
            "class_number": 1,
            "distance_m": 575,
            "race_type": "Flat",
            "is_handicap": false,
            "going": null,
            "field_size": 6,
            "scheduled_start": {
                "utc": "2026-07-08T20:46:00Z",
                "local": "2026-07-08T21:46:00+01:00"
            },
            "status": "scheduled",
            "off": null,
            "freshness": {
                "first_seen": "2026-07-07T05:12:10Z",
                "results_received": null,
                "results_delay_s": null
            },
            "weather": {
                "hour_utc": "2026-07-08T20:00:00Z",
                "temp_c": 15.2,
                "humidity_pct": 81,
                "wind_kph": 19.5,
                "gust_kph": 42.8,
                "wind_from": "W",
                "wind_deg": 271,
                "precip_mm": 0.3,
                "conditions": "light drizzle",
                "before_race": {
                    "rain_6h_mm": 1.6,
                    "rain_24h_mm": 2,
                    "wind_avg_6h_kph": 21.4
                },
                "source": "archive"
            }
        }
    ]
}
```

#### `GET /v1/tracks/{track_id}/stats`

**Track statistics.** Descriptive statistics for a track: trap-by-trap win counts and percentages per distance, and the track record at each distance — recomputed nightly from results.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `track_id` | path | integer | yes | `3905994177` |

These are descriptive statistics derived from published results — counts and records, not ratings or advice.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "track_id": 1400000014,
        "name": "Romford",
        "trap_stats": [
            {
                "distance_m": 400,
                "races": 1120,
                "traps": [
                    {
                        "trap": 1,
                        "wins": 224,
                        "win_pct": 20
                    },
                    {
                        "trap": 2,
                        "wins": 202,
                        "win_pct": 18
                    },
                    {
                        "trap": 3,
                        "wins": 179,
                        "win_pct": 16
                    },
                    {
                        "trap": 4,
                        "wins": 168,
                        "win_pct": 15
                    },
                    {
                        "trap": 5,
                        "wins": 168,
                        "win_pct": 15
                    },
                    {
                        "trap": 6,
                        "wins": 179,
                        "win_pct": 16
                    }
                ]
            }
        ],
        "records": [
            {
                "distance_m": 400,
                "time_s": 23.6,
                "dog_id": 1640101557,
                "dog_name": "Savana Ruby",
                "date_local": "2025-10-14"
            }
        ]
    }
}
```

### Breeding (6)

#### `GET /v1/sires`

**Sires.** Every sire we hold figures for, searchable by name and sortable by runs, strike rate, wins against the market, early pace or debut wins. Great Britain and Ireland.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `name` | query | string | no | `Droopys` |
| `sort` | query | string | no | `actual_vs_expected` |
| `min_runs` | query | integer | no | `1000` |
| `active` | query | boolean | no | `true` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |

Rebuilt weekly from the archive, so a race run today reaches these figures at the next rebuild; `meta.coverage.built_at` says when the current figures were made. `meta.total` is the number of sires matching the filters.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 2,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ",
        "total": 1180,
        "filter": {
            "name": null,
            "sort": "runs",
            "min_runs": 100,
            "active": false
        },
        "coverage": {
            "counted": [
                {
                    "region": "GB",
                    "years": [
                        2018,
                        2019,
                        2020,
                        2021,
                        2022,
                        2023,
                        2024,
                        2025,
                        2026
                    ]
                },
                {
                    "region": "IE",
                    "years": [
                        2026
                    ]
                }
            ],
            "not_counted": [
                {
                    "region": "AU",
                    "years": [
                        2022,
                        2023,
                        2024,
                        2025,
                        2026
                    ],
                    "known_pct": {
                        "2022": 0,
                        "2023": 0,
                        "2024": 0,
                        "2025": 0,
                        "2026": 0
                    },
                    "reason": "No sire or dam is held for these dogs."
                },
                {
                    "region": "IE",
                    "years": [
                        2019,
                        2020,
                        2021,
                        2022,
                        2023,
                        2024,
                        2025
                    ],
                    "known_pct": {
                        "2019": 0,
                        "2020": 0,
                        "2021": 0.2,
                        "2022": 1.6,
                        "2023": 7.1,
                        "2024": 22.6,
                        "2025": 60.3
                    },
                    "reason": "Breeding is on record for only part of these fields."
                }
            ],
            "rule": "A region's year is counted once breeding is on record for 95% of its runs. It is measured again at every build, so a year joins by itself.",
            "built_at": "2026-09-22T08:56:36+00:00",
            "rebuilt": "weekly"
        }
    },
    "data": [
        {
            "sire_id": 2503556926,
            "name": "Droopys Sydney",
            "offspring": 2917,
            "active_offspring": 427,
            "first_run": "2018-02-17",
            "last_run": "2026-09-21",
            "runs": 120813,
            "wins": 23459,
            "win_pct": 19.42,
            "places": 67051,
            "place_pct": 55.5,
            "priced_runs": 120657,
            "priced_wins": 23430,
            "expected_wins": 22970.6,
            "actual_vs_expected": 1.02,
            "timed_runs": 96291,
            "early_pace_pct": 19.8,
            "debut_runs": 2182,
            "debut_wins": 488,
            "debut_win_pct": 22.36
        },
        {
            "sire_id": 1460734678,
            "name": "Laughil Blake",
            "offspring": 1988,
            "active_offspring": 392,
            "first_run": "2018-01-04",
            "last_run": "2026-09-21",
            "runs": 89169,
            "wins": 16764,
            "win_pct": 18.8,
            "places": 47349,
            "place_pct": 53.1,
            "priced_runs": 89040,
            "priced_wins": 16741,
            "expected_wins": 15793.4,
            "actual_vs_expected": 1.06,
            "timed_runs": 71210,
            "early_pace_pct": 25.7,
            "debut_runs": 1466,
            "debut_wins": 347,
            "debut_win_pct": 23.67
        }
    ]
}
```

#### `GET /v1/sires/{sire_id}/stats`, **Live Plus only**

**Sire statistics.** How a sire's offspring run: strike rate, wins against the market, early pace and debut wins, whole and split by region, distance, trap, age and track.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `sire_id` | path | integer | yes | `2503556926` |

Rebuilt weekly from the archive; `meta.coverage.built_at` says when. Each split adds up to the overall figures, except that a run with no trap or no birth date on record is left out of `by_trap` or `by_age`. A sire not in the figures answers `404 not_found`, with a message naming what is covered.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "coverage": {
            "counted": [
                {
                    "region": "GB",
                    "years": [
                        2018,
                        2019,
                        2020,
                        2021,
                        2022,
                        2023,
                        2024,
                        2025,
                        2026
                    ]
                },
                {
                    "region": "IE",
                    "years": [
                        2026
                    ]
                }
            ],
            "not_counted": [
                {
                    "region": "AU",
                    "years": [
                        2022,
                        2023,
                        2024,
                        2025,
                        2026
                    ],
                    "known_pct": {
                        "2022": 0,
                        "2023": 0,
                        "2024": 0,
                        "2025": 0,
                        "2026": 0
                    },
                    "reason": "No sire or dam is held for these dogs."
                },
                {
                    "region": "IE",
                    "years": [
                        2019,
                        2020,
                        2021,
                        2022,
                        2023,
                        2024,
                        2025
                    ],
                    "known_pct": {
                        "2019": 0,
                        "2020": 0,
                        "2021": 0.2,
                        "2022": 1.6,
                        "2023": 7.1,
                        "2024": 22.6,
                        "2025": 60.3
                    },
                    "reason": "Breeding is on record for only part of these fields."
                }
            ],
            "rule": "A region's year is counted once breeding is on record for 95% of its runs. It is measured again at every build, so a year joins by itself.",
            "built_at": "2026-09-22T08:56:36+00:00",
            "rebuilt": "weekly"
        }
    },
    "data": {
        "sire_id": 2503556926,
        "name": "Droopys Sydney",
        "kind": "sire",
        "offspring": 2917,
        "active_offspring": 427,
        "first_run": "2018-02-17",
        "last_run": "2026-09-21",
        "overall": {
            "runs": 120813,
            "wins": 23459,
            "win_pct": 19.42,
            "places": 67051,
            "place_pct": 55.5,
            "priced_runs": 120657,
            "priced_wins": 23430,
            "expected_wins": 22970.6,
            "actual_vs_expected": 1.02,
            "timed_runs": 96291,
            "early_pace_pct": 19.8,
            "debut_runs": 2182,
            "debut_wins": 488,
            "debut_win_pct": 22.36
        },
        "by_region": [
            {
                "region": "GB",
                "runs": 118834,
                "wins": 23125,
                "win_pct": 19.46,
                "places": 65990,
                "place_pct": 55.53,
                "priced_runs": 118690,
                "priced_wins": 23097,
                "expected_wins": 22644.1,
                "actual_vs_expected": 1.02,
                "timed_runs": 94640,
                "early_pace_pct": 19.9,
                "debut_runs": 2141,
                "debut_wins": 481,
                "debut_win_pct": 22.47
            },
            {
                "region": "IE",
                "runs": 1979,
                "wins": 334,
                "win_pct": 16.88,
                "places": 1061,
                "place_pct": 53.61,
                "priced_runs": 1967,
                "priced_wins": 333,
                "expected_wins": 336.4,
                "actual_vs_expected": 0.99,
                "timed_runs": 1651,
                "early_pace_pct": 16.1,
                "debut_runs": 41,
                "debut_wins": 7,
                "debut_win_pct": 17.07
            }
        ],
        "by_distance": [
            {
                "band": "standard",
                "metres": "350 to 574",
                "runs": 95043,
                "wins": 18628,
                "win_pct": 19.6,
                "places": 53240,
                "place_pct": 56.02,
                "priced_runs": 94920,
                "priced_wins": 18604,
                "expected_wins": 18062.1,
                "actual_vs_expected": 1.03,
                "timed_runs": 76310,
                "early_pace_pct": 20.2,
                "debut_runs": 1731,
                "debut_wins": 392,
                "debut_win_pct": 22.65
            },
            {
                "band": "staying",
                "metres": "575 to 749",
                "runs": 5909,
                "wins": 1223,
                "win_pct": 20.7,
                "places": 3354,
                "place_pct": 56.76,
                "priced_runs": 5901,
                "priced_wins": 1221,
                "expected_wins": 1174,
                "actual_vs_expected": 1.04,
                "timed_runs": 4702,
                "early_pace_pct": 17.9,
                "debut_runs": 22,
                "debut_wins": 5,
                "debut_win_pct": 22.73
            }
        ],
        "by_trap": [
            {
                "traps": "1-2",
                "runs": 44790,
                "wins": 9003,
                "win_pct": 20.1,
                "places": 25401,
                "place_pct": 56.71,
                "priced_runs": 44731,
                "priced_wins": 8991,
                "expected_wins": 8902,
                "actual_vs_expected": 1.01,
                "timed_runs": 35690,
                "early_pace_pct": 24.6,
                "debut_runs": 812,
                "debut_wins": 196,
                "debut_win_pct": 24.14
            }
        ],
        "by_age": [
            {
                "age": "under-2",
                "label": "under 2 years",
                "runs": 16960,
                "wins": 3901,
                "win_pct": 23,
                "places": 9955,
                "place_pct": 58.7,
                "priced_runs": 16934,
                "priced_wins": 3896,
                "expected_wins": 3607.4,
                "actual_vs_expected": 1.08,
                "timed_runs": 13480,
                "early_pace_pct": 21.3,
                "debut_runs": 2019,
                "debut_wins": 461,
                "debut_win_pct": 22.83
            }
        ],
        "by_track": [
            {
                "track": "Monmore",
                "runs": 11276,
                "wins": 2142,
                "win_pct": 19,
                "places": 6212,
                "place_pct": 55.09,
                "priced_runs": 11262,
                "priced_wins": 2139,
                "expected_wins": 2076.7,
                "actual_vs_expected": 1.03,
                "timed_runs": 8860,
                "early_pace_pct": 19.5,
                "debut_runs": 197,
                "debut_wins": 44,
                "debut_win_pct": 22.34
            }
        ],
        "top_offspring": [
            {
                "dog_id": 2893016637,
                "name": "Yahoo Megan",
                "runs": 92,
                "wins": 44,
                "win_pct": 47.83,
                "places": 71,
                "first_run": "2023-09-08",
                "last_run": "2026-09-16"
            },
            {
                "dog_id": 1655044329,
                "name": "Swift Alien",
                "runs": 131,
                "wins": 40,
                "win_pct": 30.53,
                "places": 84,
                "first_run": "2022-11-29",
                "last_run": "2025-10-06"
            }
        ]
    }
}
```

#### `GET /v1/sires/{sire_id}/progeny`

**Sire progeny.** Every recorded offspring of a sire with each dog's career aggregates — the pedigree view for greyhound breeding research.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `sire_id` | path | integer | yes | `2503556926` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |

Australian dogs carry no sire or dam on record, so no Australian runner appears here. Irish dogs appear only where their breeding has been recorded: every dog racing in 2026, and a smaller share each year before that. For how the offspring perform, see [Sire statistics](https://greyhoundapi.com/documentation/sire-stats).

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ"
    },
    "data": [
        {
            "dog_id": 1655044329,
            "name": "Full Monty",
            "dam": "Ballymac Notoyou",
            "career": {
                "total_races": 12,
                "total_wins": 5,
                "win_pct": 41.67
            }
        }
    ]
}
```

#### `GET /v1/dams`

**Dams.** Every dam we hold figures for, searchable by name and sortable by runs, strike rate, wins against the market, early pace or debut wins. Great Britain and Ireland.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `name` | query | string | no | `Ballymac` |
| `sort` | query | string | no | `actual_vs_expected` |
| `min_runs` | query | integer | no | `1000` |
| `active` | query | boolean | no | `true` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |

Rebuilt weekly from the archive, so a race run today reaches these figures at the next rebuild; `meta.coverage.built_at` says when the current figures were made. `meta.total` is the number of dams matching the filters.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 2,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ",
        "total": 1180,
        "filter": {
            "name": null,
            "sort": "runs",
            "min_runs": 100,
            "active": false
        },
        "coverage": {
            "counted": [
                {
                    "region": "GB",
                    "years": [
                        2018,
                        2019,
                        2020,
                        2021,
                        2022,
                        2023,
                        2024,
                        2025,
                        2026
                    ]
                },
                {
                    "region": "IE",
                    "years": [
                        2026
                    ]
                }
            ],
            "not_counted": [
                {
                    "region": "AU",
                    "years": [
                        2022,
                        2023,
                        2024,
                        2025,
                        2026
                    ],
                    "known_pct": {
                        "2022": 0,
                        "2023": 0,
                        "2024": 0,
                        "2025": 0,
                        "2026": 0
                    },
                    "reason": "No sire or dam is held for these dogs."
                },
                {
                    "region": "IE",
                    "years": [
                        2019,
                        2020,
                        2021,
                        2022,
                        2023,
                        2024,
                        2025
                    ],
                    "known_pct": {
                        "2019": 0,
                        "2020": 0,
                        "2021": 0.2,
                        "2022": 1.6,
                        "2023": 7.1,
                        "2024": 22.6,
                        "2025": 60.3
                    },
                    "reason": "Breeding is on record for only part of these fields."
                }
            ],
            "rule": "A region's year is counted once breeding is on record for 95% of its runs. It is measured again at every build, so a year joins by itself.",
            "built_at": "2026-09-22T08:56:36+00:00",
            "rebuilt": "weekly"
        }
    },
    "data": [
        {
            "dam_id": 3187649021,
            "name": "Ballymac Notoyou",
            "offspring": 17,
            "active_offspring": 6,
            "first_run": "2021-05-12",
            "last_run": "2026-09-19",
            "runs": 1244,
            "wins": 268,
            "win_pct": 21.54,
            "places": 713,
            "place_pct": 57.32,
            "priced_runs": 1240,
            "priced_wins": 267,
            "expected_wins": 245,
            "actual_vs_expected": 1.09,
            "timed_runs": 1001,
            "early_pace_pct": 21.6,
            "debut_runs": 16,
            "debut_wins": 5,
            "debut_win_pct": 31.25
        },
        {
            "dam_id": 2250931774,
            "name": "Droopys Blossom",
            "offspring": 14,
            "active_offspring": 5,
            "first_run": "2020-10-02",
            "last_run": "2026-09-20",
            "runs": 1107,
            "wins": 214,
            "win_pct": 19.33,
            "places": 610,
            "place_pct": 55.1,
            "priced_runs": 1103,
            "priced_wins": 213,
            "expected_wins": 210.9,
            "actual_vs_expected": 1.01,
            "timed_runs": 882,
            "early_pace_pct": 18.4,
            "debut_runs": 14,
            "debut_wins": 3,
            "debut_win_pct": 21.43
        }
    ]
}
```

#### `GET /v1/dams/{dam_id}/stats`, **Live Plus only**

**Dam statistics.** How a dam's offspring run: strike rate, wins against the market, early pace and debut wins, whole and split by region, distance, trap, age and track.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `dam_id` | path | integer | yes | `1424708384` |

Rebuilt weekly from the archive; `meta.coverage.built_at` says when. Each split adds up to the overall figures, except that a run with no trap or no birth date on record is left out of `by_trap` or `by_age`. A dam not in the figures answers `404 not_found`, with a message naming what is covered.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "coverage": {
            "counted": [
                {
                    "region": "GB",
                    "years": [
                        2018,
                        2019,
                        2020,
                        2021,
                        2022,
                        2023,
                        2024,
                        2025,
                        2026
                    ]
                },
                {
                    "region": "IE",
                    "years": [
                        2026
                    ]
                }
            ],
            "not_counted": [
                {
                    "region": "AU",
                    "years": [
                        2022,
                        2023,
                        2024,
                        2025,
                        2026
                    ],
                    "known_pct": {
                        "2022": 0,
                        "2023": 0,
                        "2024": 0,
                        "2025": 0,
                        "2026": 0
                    },
                    "reason": "No sire or dam is held for these dogs."
                },
                {
                    "region": "IE",
                    "years": [
                        2019,
                        2020,
                        2021,
                        2022,
                        2023,
                        2024,
                        2025
                    ],
                    "known_pct": {
                        "2019": 0,
                        "2020": 0,
                        "2021": 0.2,
                        "2022": 1.6,
                        "2023": 7.1,
                        "2024": 22.6,
                        "2025": 60.3
                    },
                    "reason": "Breeding is on record for only part of these fields."
                }
            ],
            "rule": "A region's year is counted once breeding is on record for 95% of its runs. It is measured again at every build, so a year joins by itself.",
            "built_at": "2026-09-22T08:56:36+00:00",
            "rebuilt": "weekly"
        }
    },
    "data": {
        "dam_id": 3187649021,
        "name": "Ballymac Notoyou",
        "kind": "dam",
        "offspring": 17,
        "active_offspring": 6,
        "first_run": "2021-05-12",
        "last_run": "2026-09-19",
        "overall": {
            "runs": 1244,
            "wins": 268,
            "win_pct": 21.54,
            "places": 713,
            "place_pct": 57.32,
            "priced_runs": 1240,
            "priced_wins": 267,
            "expected_wins": 245,
            "actual_vs_expected": 1.09,
            "timed_runs": 1001,
            "early_pace_pct": 21.6,
            "debut_runs": 16,
            "debut_wins": 5,
            "debut_win_pct": 31.25
        },
        "by_region": [
            {
                "region": "GB",
                "runs": 1198,
                "wins": 259,
                "win_pct": 21.62,
                "places": 687,
                "place_pct": 57.35,
                "priced_runs": 1194,
                "priced_wins": 258,
                "expected_wins": 234.5,
                "actual_vs_expected": 1.1,
                "timed_runs": 961,
                "early_pace_pct": 21.9,
                "debut_runs": 15,
                "debut_wins": 5,
                "debut_win_pct": 33.33
            },
            {
                "region": "IE",
                "runs": 46,
                "wins": 9,
                "win_pct": 19.57,
                "places": 26,
                "place_pct": 56.52,
                "priced_runs": 46,
                "priced_wins": 9,
                "expected_wins": 9.2,
                "actual_vs_expected": 0.98,
                "timed_runs": 40,
                "early_pace_pct": 15,
                "debut_runs": 1,
                "debut_wins": 0,
                "debut_win_pct": 0
            }
        ],
        "by_distance": [
            {
                "band": "standard",
                "metres": "350 to 574",
                "runs": 1032,
                "wins": 227,
                "win_pct": 22,
                "places": 596,
                "place_pct": 57.75,
                "priced_runs": 1029,
                "priced_wins": 226,
                "expected_wins": 203.6,
                "actual_vs_expected": 1.11,
                "timed_runs": 830,
                "early_pace_pct": 22.4,
                "debut_runs": 14,
                "debut_wins": 5,
                "debut_win_pct": 35.71
            },
            {
                "band": "staying",
                "metres": "575 to 749",
                "runs": 151,
                "wins": 28,
                "win_pct": 18.54,
                "places": 82,
                "place_pct": 54.3,
                "priced_runs": 150,
                "priced_wins": 28,
                "expected_wins": 27.5,
                "actual_vs_expected": 1.02,
                "timed_runs": 121,
                "early_pace_pct": 14.9,
                "debut_runs": 0,
                "debut_wins": 0,
                "debut_win_pct": null
            }
        ],
        "by_trap": [
            {
                "traps": "1-2",
                "runs": 452,
                "wins": 104,
                "win_pct": 23.01,
                "places": 267,
                "place_pct": 59.07,
                "priced_runs": 451,
                "priced_wins": 104,
                "expected_wins": 92.9,
                "actual_vs_expected": 1.12,
                "timed_runs": 363,
                "early_pace_pct": 27.3,
                "debut_runs": 6,
                "debut_wins": 3,
                "debut_win_pct": 50
            }
        ],
        "by_age": [
            {
                "age": "under-2",
                "label": "under 2 years",
                "runs": 201,
                "wins": 49,
                "win_pct": 24.38,
                "places": 118,
                "place_pct": 58.71,
                "priced_runs": 200,
                "priced_wins": 49,
                "expected_wins": 42.6,
                "actual_vs_expected": 1.15,
                "timed_runs": 160,
                "early_pace_pct": 23.1,
                "debut_runs": 13,
                "debut_wins": 5,
                "debut_win_pct": 38.46
            }
        ],
        "by_track": [
            {
                "track": "Towcester",
                "runs": 301,
                "wins": 66,
                "win_pct": 21.93,
                "places": 170,
                "place_pct": 56.48,
                "priced_runs": 300,
                "priced_wins": 66,
                "expected_wins": 60,
                "actual_vs_expected": 1.1,
                "timed_runs": 241,
                "early_pace_pct": 22.8,
                "debut_runs": 5,
                "debut_wins": 2,
                "debut_win_pct": 40
            }
        ],
        "top_offspring": [
            {
                "dog_id": 1655044329,
                "name": "Full Monty",
                "runs": 64,
                "wins": 21,
                "win_pct": 32.81,
                "places": 44,
                "first_run": "2024-03-11",
                "last_run": "2026-09-19"
            },
            {
                "dog_id": 2893016637,
                "name": "Ballymac Notnow",
                "runs": 88,
                "wins": 19,
                "win_pct": 21.59,
                "places": 51,
                "first_run": "2023-06-02",
                "last_run": "2026-08-30"
            }
        ]
    }
}
```

#### `GET /v1/dams/{dam_id}/progeny`

**Dam progeny.** Every recorded offspring of a dam with each dog's career aggregates, mirroring the sire progeny endpoint.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `dam_id` | path | integer | yes | `1424708384` |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | `50` |

Australian dogs carry no sire or dam on record, so no Australian runner appears here. Irish dogs appear only where their breeding has been recorded: every dog racing in 2026, and a smaller share each year before that. For how the offspring perform, see [Dam statistics](https://greyhoundapi.com/documentation/dam-stats).

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "count": 1,
        "next_cursor": "eyJzIjoiMjAyNi0wNy0wOFQyMDo0NjowMFoifQ"
    },
    "data": [
        {
            "dog_id": 1655044329,
            "name": "Full Monty",
            "sire": "Ballymac Anton",
            "career": {
                "total_races": 12,
                "total_wins": 5,
                "win_pct": 41.67
            }
        }
    ]
}
```

### Platform (5)

#### `GET /v1/status`

**Pipeline status.** Live health of the data pipeline: last sync per region, races currently awaiting results, and 24-hour delay statistics — the same numbers shown on the public status page.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "regions": [
            {
                "region": "GB",
                "last_sync": "2026-07-08T13:04:31Z",
                "races_awaiting_results": 3,
                "delay_24h": {
                    "median_s": 940,
                    "p95_s": 8460
                }
            },
            {
                "region": "AU",
                "last_sync": "2026-07-08T13:02:10Z",
                "races_awaiting_results": 1,
                "delay_24h": {
                    "median_s": 1120,
                    "p95_s": 10800
                }
            }
        ]
    }
}
```

#### `GET /v1/coverage`

**Coverage ledger.** Every track and day: races carded, run, resulted and abandoned, checked against Betfair's own market list for anything we never received. On every key.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `date` | query | date | no | `2026-09-22` |
| `region` | query | string | no | `GB` |

Free on every tier, sandbox keys included, and not held to the free key's date window: it carries counts, not racing data. Rebuilt every 15 minutes; `meta.updated` says when this day was last rebuilt. `track_id` is null for a venue we do not carry.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z",
        "list": {
            "source": "betfair",
            "held": true,
            "kind": "live",
            "first_seen": "2026-09-22T18:00:04Z"
        },
        "updated": "2026-09-23T21:45:07Z",
        "rebuilt": "every 15 minutes"
    },
    "data": {
        "date": "2026-09-23",
        "regions": [
            {
                "region": "GB",
                "checked": true,
                "tracks": 12,
                "carded": 154,
                "run": 154,
                "resulted": 153,
                "abandoned": 1,
                "pending": 0,
                "listed": 154,
                "missing": 0,
                "unlisted": 0,
                "before_list": 0,
                "not_carried": {
                    "venues": 0,
                    "listed": 0
                }
            },
            {
                "region": "AU",
                "checked": true,
                "tracks": 6,
                "carded": 50,
                "run": 50,
                "resulted": 50,
                "abandoned": 0,
                "pending": 0,
                "listed": 50,
                "missing": 0,
                "unlisted": 0,
                "before_list": 0,
                "not_carried": {
                    "venues": 2,
                    "listed": 21
                }
            }
        ],
        "tracks": [
            {
                "track_id": 1400000014,
                "name": "Romford",
                "region": "GB",
                "carried": true,
                "carded": 12,
                "run": 12,
                "resulted": 12,
                "abandoned": 0,
                "pending": 0,
                "checked": true,
                "listed": 12,
                "missing": 0,
                "unlisted": 0,
                "before_list": 0
            },
            {
                "track_id": 1400000031,
                "name": "Towcester",
                "region": "GB",
                "carried": true,
                "carded": 12,
                "run": 12,
                "resulted": 11,
                "abandoned": 1,
                "pending": 0,
                "checked": true,
                "listed": 12,
                "missing": 0,
                "unlisted": 0,
                "before_list": 0
            },
            {
                "track_id": null,
                "name": "Capalaba",
                "region": "AU",
                "carried": false,
                "carded": 0,
                "run": 0,
                "resulted": 0,
                "abandoned": 0,
                "pending": 0,
                "checked": true,
                "listed": 12,
                "missing": null,
                "unlisted": 0,
                "before_list": 0
            }
        ]
    }
}
```

#### `GET /v1/usage`

**Your usage.** Your own API key's consumption: requests today and this billing month, your plan's limits, and when each window resets.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "key_prefix": "gapi_live_4Kq9",
        "plan": "live",
        "minute": {
            "used": 12,
            "limit": 120,
            "resets": "2026-07-08T13:06:00Z"
        },
        "today": {
            "used": 4211
        },
        "month": {
            "used": 61240,
            "limit": 250000,
            "resets": "2026-08-01T00:00:00Z"
        }
    }
}
```

#### `GET /v1/archive`

**All statistics.** The size of the dataset, counted rather than claimed: how many dogs, trainers, owners and tracks exist, how many runs and meetings, how many pace profiles and trap tables have been built, and races by track and distance with the earliest and latest date and how many carry starting prices, exchange prices and sectionals.

| Parameter | In | Type | Required | Example |
| --- | --- | --- | --- | --- |
| `region` | query | string | no |  |

Free on every tier, sandbox keys included. Two figures are worth reading together: `races` is how much there is, and `coverage` is how much of it carries the field you need — exchange prices exist on roughly two thirds of races and are concentrated in recent years, so a strategy priced on the exchange has less to work with than the headline count suggests. Per-distance coverage varies within a single track. Counts come from completed races and are recomputed nightly, so they lag the live feed by a day. A `count` of `null` means that figure was not computed on the last run, which is not the same as zero.

```json
{
    "computed_utc": "2026-09-10T02:14:00Z",
    "entities": [
        {
            "name": "runs",
            "count": 3804648,
            "serves": "dogs, races",
            "note": null
        },
        {
            "name": "dogs",
            "count": 76732,
            "serves": "dogs",
            "note": null
        },
        {
            "name": "pace_profiles",
            "count": 62234,
            "serves": "dogs",
            "note": null
        },
        {
            "name": "meetings",
            "count": 53078,
            "serves": "meetings",
            "note": null
        },
        {
            "name": "owners",
            "count": 15786,
            "serves": "owners",
            "note": null
        },
        {
            "name": "trainers",
            "count": 2636,
            "serves": "trainers",
            "note": null
        },
        {
            "name": "tracks",
            "count": 48,
            "serves": "tracks",
            "note": null
        }
    ],
    "totals": {
        "races": 610357,
        "runners": 3750021,
        "first_race": "2018-02-03",
        "last_race": "2026-09-10",
        "coverage": {
            "with_sp": 610038,
            "with_betfair": 393663,
            "with_sectionals": 516873
        }
    },
    "tracks": [
        {
            "track_id": 1400000014,
            "name": "Romford",
            "region": "GB",
            "races": 18422,
            "runners": 109880,
            "first_race": "2018-02-05",
            "last_race": "2026-09-09",
            "coverage": {
                "with_sp": 18400,
                "with_betfair": 9120,
                "with_sectionals": 14203
            },
            "distances": [
                {
                    "distance_m": 400,
                    "races": 9871,
                    "with_betfair": 4902
                },
                {
                    "distance_m": 575,
                    "races": 5104,
                    "with_betfair": 2610
                }
            ]
        }
    ]
}
```

#### `GET /v1/reference`

**Reference data.** Decode tables for the whole API: race grades, regions and timezones, going descriptions and runner statuses.

```json
{
    "meta": {
        "request_id": "req_8f2ac1",
        "data_as_of": "2026-07-08T13:05:12Z"
    },
    "data": {
        "regions": [
            {
                "code": "GB",
                "name": "Great Britain"
            },
            {
                "code": "AU",
                "name": "Australia"
            }
        ],
        "race_statuses": [
            "scheduled",
            "off",
            "results_pending",
            "complete",
            "void",
            "abandoned"
        ],
        "runner_statuses": [
            "runner",
            "reserve",
            "withdrawn",
            "disqualified"
        ]
    }
}
```

---

## About this document

Assembled from the endpoint registry, the guide corpus and the pricing
configuration that the website itself renders from. It is regenerated
whenever any of those change, so a stale copy is the only way it can be
wrong: check the date above against https://greyhoundapi.com/documentation/manual.md.

Machine-readable alternatives: `https://greyhoundapi.com/openapi.json` for the OpenAPI
description, `https://greyhoundapi.com/postman-collection.json` for Postman, and
`https://greyhoundapi.com/mcp` for the Model Context Protocol server.

