# Sportily Scores API

> Sportily's horse-racing model output — win probabilities, speed figures,
> value edge — as clean JSON, for programmatic use. GB, IRE and AUS racing.
> **Lifecycle: preview. Invitation-only, under a signed data licence.**
> Request access: https://sportily.app/developers

## At a glance

- Base URL: `https://sportily.app/api/v1`
- Auth: `Authorization: Bearer sk_live_…` (per-account key; store hashed, treat as a secret).
- Identity: every runner carries `horseId`; `(raceId, horseId)` is the join key across
  every endpoint.
- Window: rolling 365 days, enforced in the query.
- Publish: a day returns `409 not_yet_published` until its data reaches publish time
  (08:00 Europe/London on the racing day).
- Errors: one envelope, `{ "error": { "code": "...", "message": "..." } }`.
- Quota: metered responses carry `x-quota-remaining-minute` / `x-quota-remaining-day`
  for the lane they spent from.
- Machine contract: [OpenAPI 3.1](https://sportily.app/api/v1/openapi.json)

## Endpoints

- `GET /scores?date=…&region=…&course=…` (1 unit): Every scored race for one racing day, with per-runner model output.
- `GET /races/{raceId}` (1 unit): One race card by id — the drill-down for stored ids and lens matches.
- `GET /lenses` (free): The account's Race Lens definitions — the id dictionary for /lenses/matches.
- `GET /lenses/matches?date=…&lens=…` (Race Lens allowance): Races where your lenses matched, served as the FULL field with each runner flagged by the lenses it passed. Precomputed before publish and refreshed through the racing day — a lens saved mid-day updates within seconds, and a stale read queues its own recompute (see `stale[].refresh` and `retryAfterSeconds`). Up to 6 lenses per call, batched far cheaper than separate calls.
- `GET /usage` (free): Current spend against both allowances, so you never poll a metered endpoint for it.
- `GET /status` (free): Key check plus today's publish clock.

## What it returns

Sportily's model output for every scored runner, with the detail needed to identify a
race and a runner.

### Race fields
  - `raceId` (string): Stable Sportily race id. Use it to key your own records.
  - `course` (string): Racecourse name.
  - `offTime` (string): Scheduled off time, ISO 8601.
  - `raceName` (string): Published race title.
  - `fieldSize` (integer): Number of declared runners.

### Runner fields
  - `horseId` (string): Stable horse id. With raceId, the key for your own records and for /lenses/matches.
  - `runnerNumber` (integer): Saddlecloth number.
  - `horseName` (string): Horse name.
  - `jockeyName` (string, nullable): Jockey.
  - `trainerName` (string, nullable): Trainer.
  - `modelWinPct` (number, nullable): Win probability as a percentage, 0–100. Null when the runner is not scored.
  - `modelRank` (integer, nullable): Rank within the field by model win probability, 1 = most favoured.
  - `modelGapNext` (number, nullable): Model win-probability gap to the next-ranked runner. A separation measure.
  - `speedFigure` (number, nullable): Sportily speed figure for the runner's recent form.
  - `edgePct` (number, nullable): Model probability minus market-implied probability, in points.
  - `impliedProbPct` (number, nullable): Market-implied probability as a percentage, 0–100.

## Also available for machines

- MCP connector for ChatGPT and Claude: https://sportily.app/mcp-connect — conversational, per-account.

## Licence

Own use only. No redistribution, resale, or onward sharing of Sportily data or any derived value. Access is per-account and non-transferable.

## Docs

- Overview: https://sportily.app/developers
- API reference: https://sportily.app/developers/docs
- OpenAPI spec: https://sportily.app/api/v1/openapi.json
