{"openapi":"3.1.0","info":{"title":"PuntersEdge Odds API","description":"Australian and New Zealand racing odds, British and Irish racing on a panel of its own, and Australian sports odds, in one API. The [coverage report](https://puntersedge.online/coverage-report) lists the bookmakers served, measured from the live feed. Racing and sports are SEPARATE endpoint families: racing odds live under /v1/racing/next-to-go, and sports under /v1/sports/{sport_key}/odds and /v1/racing/events — there is no 'horse-racing' sport_key. \n\n**Two client settings worth more than any server tuning.** Send `Accept-Encoding: gzip` — responses are JSON and compress about 9x, and a caller that omits the header downloads roughly 55KB where 6KB would do. And reuse the connection (`requests.Session()` or `httpx.Client()`) — measured against this API, a cold TLS handshake costs 137ms versus 40.5ms on a reused connection, so connection reuse alone is a ~70% cut in observed latency. Neither can be fixed from our side.\n\n**Try every endpoint before you write code.** [The Postman collection](https://api.puntersedge.online/postman.json) ships ready-to-run requests, grouped into folders, with query parameters pre-listed and switched off — paste that URL into Postman's Import → Link. Set the `apiKey` collection variable once and every authenticated request picks it up in the `X-API-Key` header; the Sandbox folder needs no key at all.\n\n**What each call costs.** Credits are deducted per successful request. A malformed request — unknown sport_key, bad date, unrecognised bookmaker key — is refused free, before billing. Every response carries `X-Credits-Used` and `X-Credits-Remaining` headers, so checking your balance never costs a call.\n\n| Endpoint | Credits |\n| --- | --- |\n| `/v1/usage`, `/v1/billing/portal`, `/v1/billing/overage`, `/v1/bulk/datasets`, `/v1/bulk/restatements`, `/v1/keys/team`, `/v1/widgets`, `/v1/link-templates`, sandbox (`/v1/demo/*`) | 0 |\n| `/v1/sports` · `/v1/racing/events` · `/v1/racing/venues` · `/v1/racing/track-conditions` · `/v1/racing/closing-lines/coverage` · `/v1/data-quality/summary` · `/v1/data-quality/audit/latest` · `/v1/data-quality/audit/results` · `/v1/health/connectors` · `/v1/widget/sports` | 1 |\n| `/v1/sports/{sport_key}/odds` | 1 per market requested |\n| `/v1/racing/next-to-go` | 2 for up to 20 races, 3 for 21–60, 4 for 61–200 (keys issued before 22 Sep 2026 pay 2 at any size) |\n| `/v1/racing/results` · `/v1/racing/changes` · `/v1/racing/jockeys/stats` · `/v1/racing/trainers/stats` · `/v1/arb/best-prices` · `/v1/racing/acceptances` · `/v1/widget/racing` · `/v1/racing/markets` | 2 |\n| `/v1/racing/results/coverage` | 1 |\n| `/v1/racing/best-odds` · `/v1/racing/movers` · `/v1/racing/greyhounds/form` · `/v1/racing/greyhounds/stats` · `/v1/racing/greyhounds/field` · `/v1/racing/horses/form` · `/v1/racing/horses/runs` · `/v1/racing/form` · `/v1/racing/horses/backfill` · `/v1/best-odds/{sport_key}` · `/v1/arb/sports` · `/v1/arb/lines` · `/v1/racing/consensus` · `/v1/racing/intelligence` | 3 |\n| `/v1/racing/price-history` · `/v1/racing/clv` · `/v1/sports/{sport_key}/odds/history` · `/v1/sports/{sport_key}/odds/movements` · `/v1/sports/{sport_key}/closing-lines` · `/v1/racing/intelligence/scan` | 5 |\n| `/v1/racing/closing-lines` · `/v1/racing/price-paths` · `/v1/racing/market-summary` | 5 (JSON) / 20 (CSV) |\n| `/v1/bulk/{dataset}/{period}` | 20 (day) / 100 (month) |\n\nThe bulk form backfill bills 3 credits per horse when that horse is DELIVERED, never at submit: a horse we cannot resolve, or that Racing Australia has no page for, costs nothing. Polling a job's status, collecting its results and cancelling it are all free.\n\nWithheld endpoints (`/v1/racing/exchange` and `/v1/arb/racing`, pending a Betfair Exchange data licence) and the retired `/v1/value/*` board answer 410 and charge nothing.\n\n**Coverage.** **Australia**: thoroughbred, harness and greyhound. **New Zealand**: thoroughbred and harness, through the same endpoints, split with `country=AU` or `country=NZ`; there are no New Zealand greyhounds in the feed. **Britain and Ireland** (`country=GB,IE`): a panel of its own, the UK-licensed books (`ladbrokes_uk`, `williamhill`, `betway_uk`) alongside the Australian books that quote the race. **Hong Kong** (`country=HK`): several Australian books on HKJC race nights, win and place, prices only. A race in any other country appears only because a book lists the meeting, usually at a single bookmaker, and is not coverage. Results carry placings with settled prices and exotic dividends on AU and NZ races, and starting prices on British and Irish thoroughbred races (Betway's settlement feed). The full-field `runners` finishing order comes from Racing Australia (AU thoroughbred), our own collection (AU greyhound) and TAB New Zealand (AU harness and both NZ codes) and is not universal: every result row says which case it is in `field_status` (complete/pending/overdue/unavailable/unsupported/not_applicable), and /v1/racing/results/coverage counts them per day, code and venue. `overdue` and `unavailable` are the distinction worth knowing: `overdue` means the pass that normally fills the race has been and gone but the collector is still re-attempting the row, while `unavailable` means no scheduled pass still selects it — a statement about our schedule, not a promise the field can never arrive. Bookmakers served, races per day, books per race and results latency are measured live, every 30 minutes: https://puntersedge.online/coverage-report\n\nCovering AFL, NRL, NBA, tennis, cricket, rugby and more; Australian and New Zealand thoroughbred and harness racing, Australian greyhounds, and British and Irish racing.","termsOfService":"https://puntersedge.online/terms","contact":{"name":"PuntersEdge","url":"https://puntersedge.online/api","email":"hamish@punters-edge.com"},"license":{"name":"Proprietary — see Terms of Service","url":"https://puntersedge.online/terms"},"version":"1.0.0"},"servers":[{"url":"https://api.puntersedge.online","description":"Production"}],"paths":{"/v1/sports":{"get":{"tags":["Sports"],"summary":"List available sports","description":"Cost: 1 credit. Returns the active sports catalogue as an array of objects containing key, title, group and active fields.\n\nRacing is not in this catalogue. Horse, harness and greyhound racing have their own\nendpoints: GET /v1/racing/next-to-go and GET /v1/racing/events.","operationId":"list_sports_v1_sports_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SportOut"},"title":"Response List Sports V1 Sports Get"},"example":[{"key":"afl","title":"AFL","group":"Australian Rules","active":true,"cached":false,"cache_age_seconds":0},{"key":"nrl","title":"NRL","group":"Rugby League","active":true,"cached":false,"cache_age_seconds":0},{"key":"nba","title":"NBA","group":"Basketball","active":true,"cached":false,"cache_age_seconds":0},{"key":"soccer_epl","title":"EPL","group":"Soccer","active":true,"cached":false,"cache_age_seconds":0}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/sports/{sport_key}/odds":{"get":{"tags":["Sports"],"summary":"Get odds by sport","description":"Cost: 1 credit per requested market. Parameters: sport_key selects the sport (for example afl or nrl); markets is a comma-separated list such as h2h,spreads,totals; bookmakers optionally filters bookmaker keys; oddsFormat accepts decimal or american.\n\n`markets` is deduplicated and validated before billing, so a repeated market is charged\nonce and an unknown one is a free 422 rather than a paid empty list.\n\nThere is no racing sport_key. For horse, harness or greyhound racing use\nGET /v1/racing/next-to-go (2 credits) or GET /v1/racing/events instead — passing\n'horse-racing' here returns 404, because racing is a separate endpoint family.\n\n**Pre-match only. There is no in-play feed.** An event is listed until its `commence_time`\nand leaves this endpoint the moment it starts; no bookmaker connector reads in-play\nmarkets, so nothing here updates during a match. The last pre-match prices for an event\nthat has started are kept in GET /v1/sports/{sport_key}/odds/history and the move feed\nat GET /v1/sports/{sport_key}/odds/movements. (Documented 2026-09-10 after a customer\npolled a game through kick-off expecting live prices.)","operationId":"get_sport_odds_v1_sports__sport_key__odds_get","parameters":[{"name":"sport_key","in":"path","required":true,"schema":{"type":"string","title":"Sport Key"}},{"name":"markets","in":"query","required":false,"schema":{"type":"string","description":"Comma-separated: h2h,spreads,totals","default":"h2h","title":"Markets"},"description":"Comma-separated: h2h,spreads,totals"},{"name":"bookmakers","in":"query","required":false,"schema":{"type":"string","description":"Comma-separated bookmaker keys, or omit for all. Case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing.","title":"Bookmakers"},"description":"Comma-separated bookmaker keys, or omit for all. Case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing."},{"name":"competition","in":"query","required":false,"schema":{"type":"string","description":"Filter to one competition, case-insensitive exact match (e.g. NRLW). A value this sport has never carried is a 422 listing the ones it has, not an empty 200 — see `include_unknown_competition` for why that distinction matters. See the `competition` field on each event.","title":"Competition"},"description":"Filter to one competition, case-insensitive exact match (e.g. NRLW). A value this sport has never carried is a 422 listing the ones it has, not an empty 200 — see `include_unknown_competition` for why that distinction matters. See the `competition` field on each event."},{"name":"include_unknown_competition","in":"query","required":false,"schema":{"type":"boolean","description":"When filtering by `competition`, ALSO return events whose competition is null — not every bookmaker supplies one, and on some sports most of the card is unlabelled (ncaaf 40 of 47 upcoming events, tennis_atp 21 of 72). Because of this the filter is 'this competition OR unlabelled', so a result set is not proof that the competition matched anything: an unrecognised value is rejected with 422 rather than answering with the unlabelled events alone. Set false for a strict exact match that returns only events explicitly labelled with this competition.","default":true,"title":"Include Unknown Competition"},"description":"When filtering by `competition`, ALSO return events whose competition is null — not every bookmaker supplies one, and on some sports most of the card is unlabelled (ncaaf 40 of 47 upcoming events, tennis_atp 21 of 72). Because of this the filter is 'this competition OR unlabelled', so a result set is not proof that the competition matched anything: an unrecognised value is rejected with 422 rather than answering with the unlabelled events alone. Set false for a strict exact match that returns only events explicitly labelled with this competition."},{"name":"oddsFormat","in":"query","required":false,"schema":{"type":"string","description":"decimal or american","default":"decimal","title":"Oddsformat"},"description":"decimal or american"},{"name":"maxAgeMinutes","in":"query","required":false,"schema":{"type":"integer","maximum":1440,"minimum":5,"description":"Exclude bookmaker markets older than this many minutes; default 360 allows 3-hour supplemental feeds while hiding multi-day stale prices.","default":360,"title":"Maxageminutes"},"description":"Exclude bookmaker markets older than this many minutes; default 360 allows 3-hour supplemental feeds while hiding multi-day stale prices."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SportOddsEventOut"},"title":"Response Get Sport Odds V1 Sports  Sport Key  Odds Get"},"example":[{"id":"6b1608c5-a9b8-4235-9a12-6db9656fef74","sport_key":"nrl","sport_title":"NRL","competition":"NRL","odds_format":"decimal","commence_time":"2026-09-09T02:00:00Z","home_team":"Cronulla Sharks","away_team":"Nth Queensland Cowboys","bookmakers":[{"key":"sportsbet","title":"Sportsbet","last_update":"2026-09-07T00:34:45.060482","age_seconds":41,"stale":false,"quality":{"score":99.9,"status":"ok","age_seconds":41,"issues":[],"source_count":5,"stale_after_seconds":1800},"markets":[{"key":"h2h","quality":{"score":99.9,"status":"ok","age_seconds":41,"issues":[],"source_count":5,"stale_after_seconds":1800},"outcomes":[{"name":"Cronulla Sharks","price":1.44},{"name":"Nth Queensland Cowboys","price":2.81}]},{"key":"spreads","quality":{"score":99.9,"status":"ok","age_seconds":41,"issues":[],"source_count":5,"stale_after_seconds":1800},"outcomes":[{"name":"Cronulla Sharks","price":1.87,"point":-7.5},{"name":"Nth Queensland Cowboys","price":1.93,"point":7.5}]},{"key":"totals","quality":{"score":99.9,"status":"ok","age_seconds":41,"issues":[],"source_count":5,"stale_after_seconds":1800},"outcomes":[{"name":"Over","price":1.89,"point":47.5},{"name":"Under","price":1.91,"point":47.5}]}]},{"key":"pointsbetau","title":"Pointsbetau","last_update":"2026-09-07T00:48:33.497304","age_seconds":12,"stale":false,"quality":{"score":100.0,"status":"ok","age_seconds":12,"issues":[],"source_count":5,"stale_after_seconds":1800},"markets":[{"key":"h2h","quality":{"score":100.0,"status":"ok","age_seconds":12,"issues":[],"source_count":5,"stale_after_seconds":1800},"outcomes":[{"name":"Cronulla Sharks","price":1.42},{"name":"North Queensland Cowboys","price":2.85}]},{"key":"spreads","quality":{"score":100.0,"status":"ok","age_seconds":12,"issues":[],"source_count":5,"stale_after_seconds":1800},"outcomes":[{"name":"Cronulla Sharks","price":1.9,"point":-7.5},{"name":"North Queensland Cowboys","price":1.9,"point":7.5}]}]},{"key":"tab","title":"Tab","last_update":"2026-09-07T00:48:33.220409","age_seconds":12,"stale":false,"quality":{"score":100.0,"status":"ok","age_seconds":12,"issues":[],"source_count":5,"stale_after_seconds":1800},"markets":[{"key":"h2h","quality":{"score":100.0,"status":"ok","age_seconds":12,"issues":[],"source_count":5,"stale_after_seconds":1800},"outcomes":[{"name":"Cronulla","price":1.43},{"name":"Nth Qld","price":2.85}]},{"key":"totals","quality":{"score":100.0,"status":"ok","age_seconds":12,"issues":[],"source_count":5,"stale_after_seconds":1800},"outcomes":[{"name":"Over","price":1.87,"point":47.5},{"name":"Under","price":1.87,"point":47.5}]}]}],"canonical_event_id":"nrl:cronulla-sharks:nth-queensland-cowboys:2026-09-09","data_quality":{"score":99.9,"status":"ok","source_count":5,"bookmaker_count":3},"fetched_at":"2026-09-07T00:48:33.497304","data_age_seconds":41,"freshest_age_seconds":12,"stale":false,"stale_bookmakers":[],"cached":false,"cache_age_seconds":0}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/next-to-go":{"get":{"tags":["Racing"],"summary":"Next races to run","description":"Cost: 2 credits for up to 20 races, 3 for 21–60, 4 for 61–200 — the default request of 10 races costs 2, and keys issued before 22 September 2026 pay 2 at any size. Returns the next races to run with runners and prices — EVERY country by default, not only AU and NZ. Measured over 10.5 days, 60.8% of the race catalogue is non-AU, and a race outside AU/NZ carries a median of ONE bookmaker against 13 for an Australian one, so pass `country=AU` (pairing it with `include_unresolved=true`, per that parameter) if you want an Australian card. `categories` can filter horse, greyhound and harness racing. NZ runs thoroughbred and harness only — there are no New Zealand greyhounds in the feed.\n\n**One call can be the whole priced card.** `num_races=200` returns every race currently\ncarrying odds — the practical \"bulk odds\" pull. It looks 24\nhours ahead, so on an Australian morning it returns the whole day's card. Which races carry prices that far out is a property\nof the bookmakers, not of this API: several open their fixed-odds markets the night\nbefore (TAB's own price history on a race probed 2026-09-03 ran back to the previous\nevening) while others open close to the jump, so read `refresh_tier` and each\nbookmaker's `age_seconds` rather than assuming every race has a full panel.\n/v1/racing/events (hours_ahead up to 24) is the day's CARD, priced or not. To stop\nschedule-polling entirely, subscribe a webhook to `race.odds_open` — it fires the moment\na race's first prices land, and this endpoint (or price-history by race_id) serves them\nfrom then on.\n\n**`venue` selects a specific meeting.** There is no race-class or black-type field anywhere\nin this API, so \"give me the Group 1s\" cannot be expressed directly. The published\nGroup 1 calendar names the meeting and race number ahead of time, so\n`venue=Randwick` plus the race number off `race_number` is how that selection is actually\nmade. Combine with `categories=horse` to drop the harness and greyhound cards that share\na venue name at some tracks.\n\n**Freshness is reported worst-first.** `data_age_seconds` is the age of the OLDEST bookmaker\nquote in the race and `stale` is true when that exceeds the race's `refresh_tier`\nthreshold, so the flag describes the\nstaleness you are exposed to rather than the best leg in the payload. `stale_bookmakers`\nnames the affected books, `freshest_age_seconds` is the best-case age, and every entry in\n`bookmakers` carries its own `age_seconds`, `last_update`, `stale` and `refresh_tier`. A\nstale book does not invalidate the race — drop that leg. `win_price` is the fixed win price\nand `place_price` the bookmaker's place price; for the UK-licensed books (`ladbrokes_uk`,\n`williamhill`, `betway_uk`) no place market is quoted, so `place_price` is DERIVED from the\neach-way terms the book publishes for the race — 1 + (win − 1) × the each-way fraction — and\nthe race's `places_paid` is the number of places those terms pay. It is what the place half of\nan each-way bet returns, not a quoted place market. Each entry's `age_basis` says where\nits age counts from: `poll` from our own read of the book, `push_receipt` from when a pushed\nupdate reached us, in which case the true age is longer by the sender's own delay. Every\nage is recomputed when the response is sent, including on a race served from the stored\nboard (`cached: true`, `cache_age_seconds`). The full rules:\nhttps://puntersedge.online/developers/freshness\n\n**`refresh_tier` says which staleness rule applies.** Inside three hours of the jump a race\nis `live`: every book re-prices it on every 8-20s poll and `stale` fires past 120 seconds.\nBeyond three hours it is `card`: books re-price it about every 15 minutes, which is what\nmakes a full day's coverage affordable within their rate limits, and `stale` fires past\n1800 seconds. `age_seconds` is the true age in both — only the threshold moves. The tier\nis also published on EVERY bookmaker entry, and a book's tier can be `card` on a `live`\nrace: Betr's upstream rate-limits us, so it stops re-pricing every poll 30 minutes out\nrather than three hours (PlayUp from 6 minutes out, on its request budget), and its\nquotes on races 30-180 minutes from the jump carry\n`refresh_tier: \"card\"`, are judged against 1800 seconds, and report their true\n`age_seconds` (typically 5-8 minutes). If you apply your own freshness rule, branch on\neach entry's `refresh_tier` rather than on a single number, or every price on the\nmorning card — and every Betr price outside half an hour — will read as stale.\n\nScratched runners are returned in `scratchings` only. They are never in `runners`, even\nwhile a book is still publishing a price for them. A scratching no Australian bookmaker has\nreported, while two or more of them still price the runner, is held back: the runner stays\nin `runners`, and the scratching is listed once a bookmaker reports it.\n\n**Every bookmaker leg carries `source_url` — a deep link to that book's own page for this\nrace.** Built at ingest from the book's native race id, on the same route its iOS/Android\napp-links use, so on a phone it opens the bookmaker's app on the exact race. It is a plain\nrace-page URL: you may append your own query parameters (affiliate or campaign tags) —\nwhether a bookmaker's programme credits them is between you and that programme, and nothing\nhere promises attribution. The field is absent when no reliable URL can be built — a\nguessed link that lands a punter on the wrong page is worse than a missing one — which\ntoday means betgold, boostbet and the Betfair exchange (no provable race-page route;\nmeasured 2026-09-16 over 48 hours, every other book links 100% of its quotes).\n**This list moves, so do not code against it.** betdeluxe published no usable route when\nthis was last measured on 2026-09-01 and links 100% of its quotes now; the Betfair\nexchange was missing from the sentence entirely. Branch on whether `source_url` is\npresent on the quote in front of you, never on this list.\n\n**Same Race Multi legs** (`top2_price`, `top3_price`, `top4_price`, since 2026-09-05).\nThe price for the runner to finish in the first two, three or four — the legs a Same\nRace Multi is built from. Present on a bookmaker entry only when that book publishes\nthe leg: Ladbrokes, Neds and PointsBet carry all three, Sportsbet where its racecard\noffers the SRM markets; every other book omits the keys. Guaranteed\n`win_price >= top2_price >= top3_price >= top4_price > 1.0` — a leg that breaks the\norder at the source is dropped rather than published. A Top 3 on a race paying three\nplaces is the same bet as the place and prices the same; on a race paying two it is\nnot. These legs do not advance `updated_at` — /v1/racing/changes keys on the win price.\n\n**Runner-level betslip links** (`betslip_win_url`, `betslip_place_url`,\n`betslip_app_only`). Where `source_url` opens the book's race PAGE, these open the book's\nBETSLIP with this runner already selected. **Plus and above** (since 21 September 2026):\nthe betslip fields are served on Plus, Business, Platform and Scale, and a Standard or\nHobby key issued before that date keeps them - a key never loses what it was sold. On every\nother plan the fields are simply absent, exactly as on a book with no route; `source_url`,\nthe race-page link, is on every plan and is unaffected. Coverage, honestly:\n\n| book | opens in | preselects |\n|---|---|---|\n| `unibet` | web browser | runner **and** market (fixed win or fixed place) |\n| `tab` | web browser | runner only - the slip shows a win box and a place box, so `betslip_win_url` and `betslip_place_url` are deliberately the same URL |\n| `ladbrokes_au` | **native app only** (iOS/Android 8.69.0+) | runner and market |\n| `neds` | **native app only** | runner and market |\n\nEvery other bookmaker has no such route and carries none of these fields.\n`betslip_app_only` is `true` for Ladbrokes and Neds: in a desktop browser those URLs\nredirect to the app-download page, so send them to a phone with the app installed, or\nfall back to `source_url`. Measured 2026-09-03. The Ladbrokes pattern is vendor\ndocumented; the Neds one is **inferred** from it (same platform, same entrant ids) and\nhas not been tap-tested on a Neds install.\n\nThese routes are read from the bookmakers' own site code. **None of them is published or\nsupported by the bookmaker**, so any can change without notice. A daily automated check\nloads a live Unibet link and a live TAB link in a real browser and confirms the betslip\npopulates; when a book fails that check its betslip fields go **null** until it passes\nagain, so a broken route degrades to \"no link\" rather than sending a member to a dead\npage. Ladbrokes and Neds cannot be checked this way - a headless browser cannot open a\nnative app - and ship on the vendor's documentation instead.\n\nThe fields are **absent** on a book that has no link, exactly as `source_url` is; treat\nabsent and `null` alike as \"no betslip link\". Nothing about `source_url` changed.\n\nLike `source_url` these are plain URLs, and extra query parameters you append are passed\nthrough untouched. A link that opens a native app leaves the browser, so cookie-based\nattribution set by your own site does not travel with it.\n\n**Your own links** (`source_link`, `betslip_win_link`, `betslip_place_link`, since\n2026-10-14, Plus and above). Store your bookmaker link templates once with\n`PUT /v1/link-templates` (for example your affiliate tracking link per bookmaker, with\n`{url}` where the bookmaker's URL goes) and every leg also carries a ready-made twin of each\nURL, built through them: put the `*_link` value on your page. A twin is absent where there\nis nothing to link (no URL from the book, or your rule for that book is \"none\"), and an\naccount with no stored templates sees none. The `*_url` fields are unchanged, and the\n`ETag` changes when your templates do.\n\n**A foreign race usually has nothing to compare.** Measured over 13 days, a race outside\nAustralia and New Zealand carries a median of one bookmaker's price and at most three —\nabout one in nine has a second book. Australia and New Zealand are the only two countries\nwith a real panel: re-measured 2026-09-02 over 7 days across the books served to customers,\nan Australian race carries a median of 13 quoting bookmakers and a New Zealand race a median\nof 10. So a cross-book comparison on foreign racing is usually comparing one price with\nitself.\n\n**Hong Kong** (feature_requests #29, 2026-09-09). Sha Tin and Happy Valley are served\nunder `country=HK` whenever an Australian bookmaker quotes them. **Prices only** — win\nand place, from the books that carry the meeting. No results, no form, no race\nconditions and no `runner_ref`: those come from Racing Australia and cover AU\nthoroughbreds only, so every form field on a Hong Kong race is null. `horse_ref` is the\nexception, because it is derived from the runner's name rather than read from anywhere:\na Hong Kong thoroughbred carries one, and it joins that runner to itself across Hong\nKong meetings. Coverage follows\nthe HKJC's own calendar rather than ours — Happy Valley on Wednesday evenings and Sha\nTin on Saturday or Sunday afternoons (AEST), and nothing at all on the days between.\nDepth is the foreign-race depth described above, not the Australian panel.\n\n**`country=AU` also drops Australian races that are not labelled yet.** A race's country\nis set when the meeting is resolved, so the later races on an Australian card sit in the\nwindow with `country` null for a while. In a sample window 33 of 65 races were unlabelled\nand every one was at an Australian venue — Angle Park, Menangle, Ballarat, Albury and the\nlike, most of them the same venues carrying labelled races earlier in the day. Filter on\n`country=AU` and you lose them; leave it off and read `country` per race instead.\n\n**An Australian race gathers books as it approaches the jump**, so depth depends on how far\nout you look rather than sitting at one number. Measured 2026-08-17: a median of 10\nbookmakers inside 30 minutes of the jump, 9 at 30-60 minutes, 5 at one to two hours, and 2\nbeyond that. Since this endpoint returns the NEXT races, expect the near-jump end of that\nrange. A window-wide average is not meaningful here — it just tracks whichever mix of\nstart times happens to be loaded.\n\nHow much of the card is foreign is NOT a fixed ratio: the foreign share of the window runs\nnear zero through the Australian afternoon and reaches 100% overnight. Filter on `country`\nrather than assuming a mix.\n\n**Form fields and race conditions are AU/NZ-only, and which of them you get is decided by\nthe racing code, not by the country.** For AU thoroughbreds, Racing Australia's own\nacceptance pages fill the gaps: runner `jockey`/`trainer`/`weight`/`form` where\nno book supplies them, `runner_ref` (Racing Australia's code for the runner, from its\nlatest read of the field — on race day the same value `/v1/racing/results` and the archives\ncarry for this race; Racing Australia issues a new code for the same horse each day, so join\na horse across meetings on `horse_ref`), and the race-level `race_class`,\n`conditions` (RA's line verbatim), `prize_total` and `rail`. Otherwise the runner fields\n`barrier`, `jockey`, `trainer`,\n`weight` and `form`, and the race-level `race_name`, `distance_m`, `track_condition`,\n`weather` and `places_paid`, are supplied by exactly one bookmaker in the panel, and that\nconnector fetches Australian and New Zealand meetings only. A race outside Australia and\nNew Zealand therefore carries none of them, and this is a structural ceiling rather than a\nthin sample: across 13 days of snapshots that book appears on 2,813 Australian and 166 New\nZealand races and on none of the 5,624 races held in the 31 other countries the panel\nquotes.\n\n**barrier: for AU thoroughbreds the drawn barrier from Racing Australia; for greyhounds\nand harness as the book publishes it — BetRight, BetGold and BoostBet renumber after\nscratchings, BetDeluxe does not (measured 2026-09-28).** That renumbering was measured on\nthoroughbred fields against Racing Australia's draw (39% of those three books' rows one or\nmore gates LOWER than RA's), which is why, since\n2026-09-29, RA's draw REPLACES every book's own value on an AU thoroughbred RA's\nacceptances name, rather than only filling a gap. A runner RA does not name — any NZ\nrace, or a name RA spells differently — still carries whatever the books sent.\n**`jockey` is a person's name** (2026-09-29): an apprentice's weight claim is stripped\nrather than served glued to it (\"Sharni Webster(A3.0)\" is served \"Sharni Webster\").\nGreyhound runners carry the trainer in `trainer` and `jockey` null; harness runners carry\nthe driver in `jockey`. A greyhound `jockey` value you stored from a response before\n2026-09-29 may hold the trainer.\n\n**`horse_ref` is the one runner field that is DERIVED rather than reported** (2026-09-21).\nEvery thoroughbred runner here carries `horse_ref`, `pe:<name>` — the registered name\nfolded to letters and digits — because it is computed from the name the panel already\nagreed on rather than fetched from anywhere, so it is present on races no book enriched\nand on foreign meetings alike. It is the same value on /v1/racing/results runners and\nplacings, the closing-line and price-path archives, /v1/racing/horses/form and\n/v1/racing/horses/runs, and it is the key to join this runner to itself at another\nmeeting — which `runner_ref` cannot do. Null on greyhound and harness runners, where\n`runner_ref` (`grv:`/`grsa:`) is already a per-dog registry id that persists.\n\nOn Australian races the fields you get depend on the code. Measured 2026-08-17 over the\nsame runner merge this endpoint serves: greyhound runners carried `trainer` 99%, `form` 99%\nand `barrier` 99%, with `jockey` 0% and `weight` 16%; harness runners carried `trainer`\n100%, `form` 96% and `jockey` — the driver — 100%, with `barrier` and `weight` 0%. Those\nzeroes are what that book publishes per code, not gaps in Australian coverage. The measured\nwindow held no Australian thoroughbred meeting, so do not read these as fixed rates.\nNull-check every one of these fields rather than inferring its presence from `country`. Conditional requests: every 200 carries an `ETag`; send it back as `If-None-Match` and an unchanged board is answered `304 Not Modified` for 0 credits (served from the stored board, which a paid key reuses for at most 20 s and a Free key for at most 65 s; a poll that lands on a cache miss is billed as usual).","operationId":"next_to_go_v1_racing_next_to_go_get","parameters":[{"name":"num_races","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Up to 200. Pass 200 to bulk-pull every currently quoted race in one call instead of paging.","default":10,"title":"Num Races"},"description":"Up to 200. Pass 200 to bulk-pull every currently quoted race in one call instead of paging."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Alias for `num_races`, accepted because it is the near-universal REST spelling and because /v1/racing/results already takes it — learning `limit` on one racing endpoint and being refused on the next is the inconsistency this closes. Same bounds and same meaning; when both are given, `limit` wins. Measured 2026-09-14: callers sent `limit` here 28 times and were refused every time.","title":"Limit"},"description":"Alias for `num_races`, accepted because it is the near-universal REST spelling and because /v1/racing/results already takes it — learning `limit` on one racing endpoint and being refused on the next is the inconsistency this closes. Same bounds and same meaning; when both are given, `limit` wins. Measured 2026-09-14: callers sent `limit` here 28 times and were refused every time."},{"name":"categories","in":"query","required":false,"schema":{"type":"string","description":"horse,greyhound,harness or omit for all","title":"Categories"},"description":"horse,greyhound,harness or omit for all"},{"name":"category","in":"query","required":false,"schema":{"type":"string","description":"Alias of `categories`. Both spellings are accepted on every racing endpoint. An unknown value is a free 422 naming the valid ones.","title":"Category"},"description":"Alias of `categories`. Both spellings are accepted on every racing endpoint. An unknown value is a free 422 naming the valid ones."},{"name":"bookmakers","in":"query","required":false,"schema":{"type":"string","description":"Comma-separated bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing.","title":"Bookmakers"},"description":"Comma-separated bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing."},{"name":"include_unresolved","in":"query","required":false,"schema":{"type":"boolean","description":"Include races whose country is not resolved yet (country is null). OFF by default. Most of an Australian card is unresolved until the meeting is confirmed — 58.8% of horse races in a measured window — so country=AU alone can return far fewer races than are running, or none at all. Pass true to get them, and read `country` per race to see which are still unresolved.","default":false,"title":"Include Unresolved"},"description":"Include races whose country is not resolved yet (country is null). OFF by default. Most of an Australian card is unresolved until the meeting is confirmed — 58.8% of horse races in a measured window — so country=AU alone can return far fewer races than are running, or none at all. Pass true to get them, and read `country` per race to see which are still unresolved."},{"name":"country","in":"query","required":false,"schema":{"type":"string","description":"ISO country codes, comma-separated (e.g. AU, AU,NZ, GB,IE or ZA). Omit for every country. GB,IE is the British and Irish card, priced by the UK-licensed bookmakers (ladbrokes_uk, williamhill, betway_uk — region gb) alongside the Australian books that quote it; UK and Irish racecards number races by running order, and the UK books' place_price is derived from their each-way terms (see the field notes). ZA is the South African card, priced by the South African bookmakers (easybet_za, topbet_za, betway_za — region za) alongside the Australian books that quote it; races are numbered in running order, easybet_za and topbet_za carry a place_price with places_paid on the race, betway_za does not (no each-way market); South African thoroughbred results (the placed runners ranked, no official starting price) are served from the 2026-10-15 card, see /v1/racing/results. HK serves Sha Tin and Happy Valley (prices only, no results or form) on the HKJC race nights. Most of an AU card is unresolved until the meeting is confirmed, so pair this with include_unresolved=true or you will silently lose them. Outside AU, NZ, GB/IE and ZA a race carries a median of ONE bookmaker, while an Australian race gathers books toward the jump (median 10 inside 30 minutes, 2 at 2-4 hours). NOTE that this filter also excludes Australian races whose country is not labelled yet — typically the later races on the card — so it is not a clean AU/foreign split. Omit it and read `country` per race if you need those.","title":"Country"},"description":"ISO country codes, comma-separated (e.g. AU, AU,NZ, GB,IE or ZA). Omit for every country. GB,IE is the British and Irish card, priced by the UK-licensed bookmakers (ladbrokes_uk, williamhill, betway_uk — region gb) alongside the Australian books that quote it; UK and Irish racecards number races by running order, and the UK books' place_price is derived from their each-way terms (see the field notes). ZA is the South African card, priced by the South African bookmakers (easybet_za, topbet_za, betway_za — region za) alongside the Australian books that quote it; races are numbered in running order, easybet_za and topbet_za carry a place_price with places_paid on the race, betway_za does not (no each-way market); South African thoroughbred results (the placed runners ranked, no official starting price) are served from the 2026-10-15 card, see /v1/racing/results. HK serves Sha Tin and Happy Valley (prices only, no results or form) on the HKJC race nights. Most of an AU card is unresolved until the meeting is confirmed, so pair this with include_unresolved=true or you will silently lose them. Outside AU, NZ, GB/IE and ZA a race carries a median of ONE bookmaker, while an Australian race gathers books toward the jump (median 10 inside 30 minutes, 2 at 2-4 hours). NOTE that this filter also excludes Australian races whose country is not labelled yet — typically the later races on the card — so it is not a clean AU/foreign split. Omit it and read `country` per race if you need those."},{"name":"venue","in":"query","required":false,"schema":{"type":"string","description":"Venue names, comma-separated and case-insensitive (e.g. Randwick or Randwick,Flemington). Matched exactly against the race's `venue` after case folding, so pass the venue as this API spells it -- read `venue` off an unfiltered call rather than guessing. Omit for every venue. This narrows the SAME window the endpoint always serves (24 hours ahead since 2026-09-03, six before that); it does not look beyond it, so a venue with no race inside the window returns an empty list rather than an error.","title":"Venue"},"description":"Venue names, comma-separated and case-insensitive (e.g. Randwick or Randwick,Flemington). Matched exactly against the race's `venue` after case folding, so pass the venue as this API spells it -- read `venue` off an unfiltered call rather than guessing. Omit for every venue. This narrows the SAME window the endpoint always serves (24 hours ahead since 2026-09-03, six before that); it does not look beyond it, so a venue with no race inside the window returns an empty list rather than an error."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/NextRaceOut"},"title":"Response Next To Go V1 Racing Next To Go Get"},"example":[{"race_id":"2f0e2df8-5386-4aae-bbdb-7b784dee3e43","source_id":"src_9e2b7c41aa","venue":"Grafton","venue_id":"grafton","venue_canonical":"Grafton","venue_site":"grafton","race_number":1,"category":"horse","start_time":"2026-09-07T03:20:00Z","country":"AU","race_name":"TEAM AIDAN MARKETS @ CRJC 22 NOVEMBER CB MDN PLT","distance_m":1700,"track_condition":"Soft (5)","weather":"Clear Sky","places_paid":3,"track_condition_changed_at":null,"race_class":"MDN","conditions":"Maiden Plate","prize_total":27000,"rail":"True","track_name":"Grafton","market_closed_at":null,"inplay_at":null,"market_state_source":null,"scratchings":[{"name":"Airhawk","number":1,"barrier":9,"scratched_at":"2026-09-03T11:47:44Z"},{"name":"Sweet September","number":9,"barrier":3,"scratched_at":"2026-09-06T18:58:41Z"}],"runners":[{"name":"Fabulous Fiano","number":2,"barrier":6,"jockey":"Matthew McGuren","trainer":"Matthew Dunn","horse_ref":"pe:fabulousfiano","bookmakers":[{"key":"tab","win_price":8.0,"place_price":2.6,"age_seconds":6,"stale":false,"refresh_tier":"live","source_url":"https://www.tab.com.au/racing/2026-09-07/GRAFTON/GRA/R/1"},{"key":"sportsbet","win_price":7.5,"place_price":2.5,"top2_price":2.5,"top3_price":1.53,"top4_price":1.2,"age_seconds":9,"stale":false,"refresh_tier":"live","source_url":"https://www.sportsbet.com.au/horse-racing/australia-nz/grafton/race-1-10895656"},{"key":"ladbrokes_au","win_price":7.0,"place_price":2.6,"top2_price":2.6,"top3_price":1.6,"top4_price":1.22,"age_seconds":11,"stale":false,"refresh_tier":"live","source_url":"https://www.ladbrokes.com.au/racing/grafton/1564cb51-fcbb-4038-b488-372027041b0e","betslip_win_url":"https://www.ladbrokes.com.au/racing/grafton/1564cb51-fcbb-4038-b488-372027041b0e/fw","betslip_place_url":"https://www.ladbrokes.com.au/racing/grafton/1564cb51-fcbb-4038-b488-372027041b0e/fp","betslip_app_only":true},{"key":"betright","win_price":7.0,"place_price":2.0,"age_seconds":14,"stale":false,"refresh_tier":"live","source_url":"https://www.betright.com.au/racing/grafton/1/62386616/win"},{"key":"unibet","win_price":6.0,"place_price":2.4,"age_seconds":12,"stale":false,"refresh_tier":"live","source_url":"https://www.unibet.com.au/racing#/event/202609070300.T.AUS.grafton.1"}]},{"name":"Foxwedge Arrow","number":3,"barrier":2,"jockey":"Ben Looker","trainer":"Kris Lees","horse_ref":"pe:foxwedgearrow","bookmakers":[{"key":"tab","win_price":51.0,"place_price":11.0,"age_seconds":6,"stale":false,"refresh_tier":"live","source_url":"https://www.tab.com.au/racing/2026-09-07/GRAFTON/GRA/R/1"},{"key":"ladbrokes_au","win_price":46.0,"place_price":9.5,"age_seconds":11,"stale":false,"refresh_tier":"live","source_url":"https://www.ladbrokes.com.au/racing/grafton/1564cb51-fcbb-4038-b488-372027041b0e"},{"key":"sportsbet","win_price":41.0,"place_price":9.0,"age_seconds":9,"stale":false,"refresh_tier":"live","source_url":"https://www.sportsbet.com.au/horse-racing/australia-nz/grafton/race-1-10895656"}]}],"data_age_seconds":14,"freshest_age_seconds":6,"refresh_tier":"live","stale":false,"stale_bookmakers":[],"cached":false,"cache_age_seconds":0}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/events":{"get":{"tags":["Racing"],"summary":"Upcoming race list","description":"Cost: 1 credit. The CARD - every race scheduled in the requested window, optionally\nfiltered by racing category and country. EVERY country is served by default, not only\nAU and NZ — pass `country=AU` (pairing it with `include_unresolved=true`, per that\nparameter) if you want an Australian card.\n\n**How far ahead you can see (2026-09-22).** `hours_ahead` reaches 24h on Free and\nHobby, 48h on Standard, and 168h (7 days) on Plus and above. Every response carries\nyour ceiling as `X-Horizon-Hours` and the window actually used as\n`X-Horizon-Used-Hours`, so you never have to discover it by trial. Asking past it is a\n403 naming the tier that reaches further, and costs no credit -- it is never a silent\nclamp to a shorter window. `date=YYYY-MM-DD` is the single-day alias for an Australian\nEastern meeting day; a day is reachable when its last moment is inside your horizon,\nso it is bounded by the same ceiling rather than being a second entitlement.\n\nNothing lost anything here: 24h was the hard cap for every plan, free included, until\nthis landed, and Free and Hobby still have exactly that. AU cards carry\nthoroughbred, harness and greyhound; NZ cards carry thoroughbred and harness.\n\nHong Kong (`country=HK`, feature_requests #29, 2026-09-09) is listed too, on the HKJC's\nown nights — Happy Valley on Wednesday evenings and Sha Tin on Saturday or Sunday\nafternoons, AEST — whenever an Australian bookmaker quotes the meeting. Thoroughbred\nonly, and PRICES ONLY on /v1/racing/next-to-go and /v1/racing/best-odds: a Hong Kong\nrace carries no results, form, race conditions or `runner_ref`, because those come from\nRacing Australia and cover AU thoroughbreds only (`horse_ref` is derived from the\nrunner's name, so it is there). Foreign meetings other than Hong Kong\nstill appear here whenever a book in the panel lists them; this endpoint has never\nfiltered them out.\n\nThis is the schedule endpoint, and it lists a race before any book has priced it. Since\n2026-09-01 the feed reads the early markets (Sportsbet's full card 3-12h out, BetRight's\nearly lane, a PointsBet wide sweep), so the FIRST price on a race now lands well ahead of\nthe jump: measured over the 48h to 2026-09-03 11:00 UTC, median 10.1h before the jump on AU\nthoroughbreds (79 of 80 races priced 3h+ out), 5.3h on AU greyhounds, 6.8h on AU harness.\nThat first price is one or two books; the full board still fills inside about an hour of\nthe jump. A race listed here with no prices yet is one no book has opened -\n/v1/racing/next-to-go carries the priced window, and the `race.odds_open` webhook fires\nthe moment a race's first prices land.\n\n`scratchings` is served here as well as on /v1/racing/next-to-go, so a scratching is visible\nfrom the moment a book reports it rather than only once the race enters the priced window.\nIts entries are {name, number, barrier, scratched_at, late, emergency}, merged across\nevery reporting book — `scratched_at` (UTC) is the book's own stamp where it sends one\n(Ladbrokes, Neds, BetRight) and Racing Australia's official scratching sheet otherwise\nfor AU thoroughbreds, read every two hours; `late` and `emergency` come from the same\nsources. On greyhound\ncards some entries are box-vacancy placeholders - a literal '....' or 'Vacant Box' - and\nseveral books can report the same empty box separately, so count boxes by `number`, not by\nlist length.\n\n`status` is written when the race row is created and never revised, so every race in this\nwindow reads 'open'. `market_status` and `inplay` are the live betting-market state.\n\n`market_closed_at` and `inplay_at` are the race lifecycle: when the market was first seen\nshut, and when the race was first seen in-play — the actual off, where `start_time` is the\nadvertised one and moves. Both are POLL-OBSERVED: each is the first poll at which the state\nwas seen, so it lands up to one polling pass after it, and passes are most frequent in the\nminutes before the jump. Both are SET ONCE, so a market\nthat flickers SUSPENDED -> OPEN -> SUSPENDED reports the first suspension. Null never means\nthe market stayed open. This endpoint serves upcoming races and the live row is purged 10\nminutes after the jump; /v1/racing/results carries the same fields permanently.\n\n**Who reports market state, and what you see.** Two feeds do. PointsBet closes its book at\nthe real jump — measured 2026-09-03 over 9 live races followed across their jump, 0.2 to\n1.4 minutes AFTER the advertised start on AU/NZ cards — and that is what populates `market_closed_at` for most races. The\nBetfair Exchange reports state while it quotes the market, which ends about two minutes\nBEFORE the jump (measured 2026-09-01 on Horsham R3, advertised 05:24 UTC: betfair last seen\n119s before the off), which is why these fields read null on every race before 2026-09-03.\nExchange-sourced market state is withheld from customer plans pending a data licence, so a\nrace whose state only the exchange saw reads null for customers; bookmaker-sourced state is\nserved to everybody. `market_state_source` names the feed behind the values you receive, and\nis itself null whenever they are withheld.\n\n`inplay_at` stays exchange-only: bookmakers do not run racing in-play, so a bookmaker feed\ncan say when it CLOSED its book but not when the race went. For a race the exchange has\nstopped quoting, `market_closed_at` is the closest measured instant to the off.","operationId":"race_events_v1_racing_events_get","parameters":[{"name":"hours_ahead","in":"query","required":false,"schema":{"type":"integer","maximum":168,"minimum":1,"description":"How many hours ahead to list, from now. Your ceiling depends on the plan and is returned on every response as X-Horizon-Hours: 24 on Free and Hobby, 48 on Standard, 168 (7 days) on Plus and above. Asking past your ceiling is a free 403 naming it, never a silent clamp. Mutually exclusive with date.","default":4,"title":"Hours Ahead"},"description":"How many hours ahead to list, from now. Your ceiling depends on the plan and is returned on every response as X-Horizon-Hours: 24 on Free and Hobby, 48 on Standard, 168 (7 days) on Plus and above. Asking past your ceiling is a free 403 naming it, never a silent clamp. Mutually exclusive with date."},{"name":"date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Single-day alias: date=YYYY-MM-DD lists that Australian Eastern meeting day instead of a rolling hours_ahead window. Mutually exclusive with hours_ahead. A day is reachable when its last moment is inside your horizon, so this is bounded by the same X-Horizon-Hours ceiling and is not a separate entitlement. Forward-looking only -- a past date is a free 422 pointing at /v1/racing/results and /v1/racing/closing-lines.","title":"Date"},"description":"Single-day alias: date=YYYY-MM-DD lists that Australian Eastern meeting day instead of a rolling hours_ahead window. Mutually exclusive with hours_ahead. A day is reachable when its last moment is inside your horizon, so this is bounded by the same X-Horizon-Hours ceiling and is not a separate entitlement. Forward-looking only -- a past date is a free 422 pointing at /v1/racing/results and /v1/racing/closing-lines."},{"name":"categories","in":"query","required":false,"schema":{"type":"string","title":"Categories"}},{"name":"category","in":"query","required":false,"schema":{"type":"string","description":"Alias of `categories`. Both spellings are accepted on every racing endpoint. An unknown value is a free 422 naming the valid ones.","title":"Category"},"description":"Alias of `categories`. Both spellings are accepted on every racing endpoint. An unknown value is a free 422 naming the valid ones."},{"name":"country","in":"query","required":false,"schema":{"type":"string","description":"ISO country codes, comma-separated (e.g. AU, AU,NZ, GB,IE for the British and Irish card or ZA for the South African card). Omit for every country. HK lists Sha Tin and Happy Valley on the HKJC race nights (prices only, no results or form). Pair with include_unresolved=true or you will silently lose races whose meeting is not confirmed yet.","title":"Country"},"description":"ISO country codes, comma-separated (e.g. AU, AU,NZ, GB,IE for the British and Irish card or ZA for the South African card). Omit for every country. HK lists Sha Tin and Happy Valley on the HKJC race nights (prices only, no results or form). Pair with include_unresolved=true or you will silently lose races whose meeting is not confirmed yet."},{"name":"include_unresolved","in":"query","required":false,"schema":{"type":"boolean","description":"Include races whose country is not resolved yet (country is null). OFF by default. Most of an Australian card is unresolved until the meeting is confirmed — 58.8% of horse races in a measured window — so country=AU alone can return far fewer races than are running, or none at all. Pass true to get them, and read `country` per race to see which are still unresolved.","default":false,"title":"Include Unresolved"},"description":"Include races whose country is not resolved yet (country is null). OFF by default. Most of an Australian card is unresolved until the meeting is confirmed — 58.8% of horse races in a measured window — so country=AU alone can return far fewer races than are running, or none at all. Pass true to get them, and read `country` per race to see which are still unresolved."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RaceEventOut"},"title":"Response Race Events V1 Racing Events Get"},"example":[{"race_id":"2f0e2df8-5386-4aae-bbdb-7b784dee3e43","venue":"Grafton","venue_id":"grafton","venue_canonical":"Grafton","venue_site":"grafton","race_number":1,"category":"horse","start_time":"2026-09-07T03:20:00Z","country":"AU","race_name":"TEAM AIDAN MARKETS @ CRJC 22 NOVEMBER CB MDN PLT","distance_m":1700,"track_condition":"Soft (5)","weather":"Clear Sky","race_class":"MDN","conditions":"Maiden Plate","prize_total":27000,"rail":"True","track_name":"Grafton","scratchings":[{"name":"Airhawk","number":1,"barrier":9,"scratched_at":"2026-09-03T11:47:44Z"},{"name":"Sweet September","number":9,"barrier":3,"scratched_at":"2026-09-06T18:58:41Z"}],"track_condition_changed_at":null,"status":"open","market_status":null,"inplay":null,"market_closed_at":null,"inplay_at":null,"market_state_source":null},{"race_id":"c2cfc9b1-a037-4bcb-841f-5496bb0c9b82","venue":"Tatura","venue_id":"tatura","venue_canonical":"Tatura","venue_site":"tatura","race_number":1,"category":"horse","start_time":"2026-09-07T03:30:00Z","country":"AU","race_name":"Shepparton Club Maiden Plate","distance_m":1000,"track_condition":"Heavy (8)","weather":"Broken Clouds","race_class":"MDN","conditions":"Maiden Plate","prize_total":27000,"rail":"True Entire Circuit","track_name":"Tatura","scratchings":[],"track_condition_changed_at":null,"status":"open","market_status":null,"inplay":null,"market_closed_at":null,"inplay_at":null,"market_state_source":null}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/venues":{"get":{"tags":["Racing"],"summary":"Canonical venue directory","description":"Cost: 1 credit. Every venue this API has actually served recently, with its canonical\nidentity — built for joining our data against official sources (Racing Australia, form\nguides) that spell venues differently from bookmakers.\n\nTHE LIST IS MEASURED, the identity is curated. Rows come from the live race window plus\nthe trailing 60 days of results — a venue appears because races actually ran there, so\nthe directory can never advertise coverage that does not exist. Each observed spelling is\nthen resolved by the same registry that stamps venue_id / venue_canonical / venue_site on\nevery racing response, so this endpoint and those fields can\nnever disagree.\n\n`venue_site` is the join key for multi-track complexes: Sandown, Sandown Hillside,\nSandown Lakeside and the Sandown Park greyhound track all carry site \"sandown\";\nQ Straight / Q1 Lakeside / Q2 Parklands carry \"the-q\". `spellings` lists the raw venue\nstrings observed under each id, so you can map historical data you have already stored.","operationId":"racing_venues_v1_racing_venues_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/VenueOut"},"title":"Response Racing Venues V1 Racing Venues Get"},"example":[{"venue_id":"grafton","venue_canonical":"Grafton","venue_site":"grafton","country":"AU","categories":["horse"],"spellings":["GRAFTON","Grafton"]},{"venue_id":"sandown-hillside","venue_canonical":"Sandown Hillside","venue_site":"sandown","country":"AU","categories":["horse"],"spellings":["Sandown - HILLSIDE","Sandown Hillside"]},{"venue_id":"sandown-park","venue_canonical":"Sandown Park","venue_site":"sandown","country":"AU","categories":["greyhound"],"spellings":["Sandown Park"]}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/results":{"get":{"tags":["Racing"],"summary":"Settled race results","description":"Cost: 2 credits. Settled AU/NZ race results — finishing order with the bookmaker's own\nsettled fixed prices, plus scratchings and their deductions — and, from 2026-10-06, British and\nIrish thoroughbred results from Betway's settlement feed: every runner with its starting price\n(`sp` on placings and runners) and its finishing position where Betway publishes one (the whole\norder on the British races measured; on the Irish, the first four first and the rest within\nabout 25 minutes of the off, while the result is still `interim`), non-runners as deductions with\nno Rule 4 figure, no dividends and no fixed settling prices (`win_price`/`place_price` null). A starter\nBetway has still not ranked once the result is `final` did not finish -- Betway ranks every finisher and\nleaves fallers and pulled-up horses unranked -- and from 2026-10-12 carries `abnormal: did not finish`\nwith `position` null. UK racecards\npublish no race numbers; a GB/IE result takes the running-order number of the race it matched.\nFrom 2026-10-12, British and Irish greyhound results come from PointsBet's settled race card: the\npaid places (three on a six-dog field) with PointsBet's own settled fixed prices as\n`win_price`/`place_price`, the whole field as `runners` with a position on each placed dog,\nscratchings as deductions; no starting prices and no dividends on these races.\n\n`status` is `interim` until the placings have been stable for 30 minutes, then `final`.\nA race that did not run is `abandoned` (Racing Australia's official notice, applied to every\nrace of the meeting, with `status_note` carrying RA's word: abandoned, postponed, or\ntransferred to another venue) — empty `placings`, null `runners`, filterable with\n`status=abandoned`.\nSettle only against `final`: an interim placing can still change on protest.\n\n**A `runners` entry is not necessarily a starter.** `status` says whether the runner ran:\n`ran`, or `scratched` / `late_scratching` / `reserve` for one that did not. Since\n2026-09-08 an AU thoroughbred scratched at the barrier arrives as `status: scratched`\ncarrying `abnormal: 'SB'`; a result written before that date carries such a runner as\n`status: ran` with a null `position`. Such a row\ncan still carry `sp` — the last price RA printed for it. That price is NOT what its\n`deductions` row was struck from (measured: 1/`sp` matches the published win deduction on\nnone of the five stored priced rows), so take the deduction from `deductions` itself and\nnever derive it. Settle on `status`, not on presence in `runners`. The thoroughbred and\ngreyhound collectors do NOT spell a late scratching the same way; the `runners` field\ndescription says exactly how to tell them apart.\n\n**Settled prices can be null while `status` is `interim` — this is the SP timing, not a\ngap.** The source book publishes the finishing order before its settled dividends, so a\nresult fetched in the minutes after the jump can carry placings whose `win_price` /\n`place_price` are still null. They fill in during the interim window: measured over 7\ndays (1,541 final results, 2026-09-01), every `final` result carried settled prices and\nnone was missing them. If you hit null prices, you are inside that window — poll again\nor wait for `final`, which is already the only status you should settle against.\n\n`sp` on `runners` and on `placings` is the OFFICIAL starting price — not our consensus, and\nnot the fixed price the bookmaker settled at. The two differ, and both are returned so you\ncan compare them. Thoroughbred SP is Racing Australia's (its favouritism marker lands in\n`sp_note`), greyhound SP is our own collection's (Victorian tracks via FastTrack, every other\nstate via the national results pages, from 2026-09-13), and harness SP is always null because\nno source we hold publishes it. A null `sp` means unknown — never zero.\n\n`race_id` joins a result to /v1/racing/price-history, and to /v1/racing/next-to-go while the\nrace was still open — use it rather than matching on venue + race number + start time.\n\n**Joining one RACE across endpoints: `runner_ref`. Joining one HORSE across meetings:\n`horse_ref`.** Every thoroughbred entry in `runners` carries `runner_ref` (`ra:<code>`),\nthe code Racing Australia published for the runner on race day — the same value the\nclosing-line, price-path and markets archives carry and the live board carried that day,\nso it joins this result to every other row about the race. Racing Australia issues a new\ncode for the same horse each day, so grouping a career on `runner_ref` returns one row per\nstart. For a career, every thoroughbred entry in `runners` and `placings` carries\n`horse_ref`, `pe:<name>` — the registered name folded to letters and digits — the same\nvalue on the live board, the closing-line and price-path archives, /v1/racing/horses/form\nand /v1/racing/horses/runs. `horse_ref` is derived at serve time from the name already on\nthe row, so it is on every historical result too, and it is null on greyhound and harness\nrows — a greyhound's `runner_ref` (`grv:`/`grsa:`) is a per-dog registry id and already\npersists.\n\n`country` splits the feed: `country=AU` and `country=NZ` are the two-URL form of an AU/NZ\nsplit. NZ rows come from the bookmaker results feed, which publishes placegetters, so they\ncarry `placings` without the full `runners` finishing order the Racing Australia collector\nadds to AU thoroughbred results.\n\n`dividends` carries the declared win/place dividends per placegetter per product and the\nexotic dividends (quinella, exacta, trifecta, first four), each as a return on a $1 stake.\nExotics are declared a few minutes after the placings, so they land on a later poll than the\nresult itself — see the field description for the full shape.\n\n`market_closed_at` and `inplay_at` are the race lifecycle, captured live and kept here\npermanently: when the market was first seen shut, and when the race was first seen in-play —\nthe actual off, where `start_time` is the advertised one and moves. Both are POLL-OBSERVED\n(polling passes are most frequent in the minutes before the jump), so each is the first\npoll at which the state was seen rather than the exact instant, and both are SET ONCE.\nThey are copied from\nthe live race row when the result is written. That row is deleted 10 minutes after the jump\nand results land a median 4.9 minutes after the start (p90 7.4), so a late result keeps\nnull. Null never means the market stayed open. Capture began 2026-09-01, so every result\nbefore that date carries null.\n\n**Who reports market state, and what you see.** Two feeds do. PointsBet closes its book at\nthe real jump — measured 2026-09-03 over 9 live races followed across their jump, 0.2 to\n1.4 minutes AFTER the advertised start on AU/NZ cards — and that is what populates `market_closed_at` for most races. The\nBetfair Exchange reports state while it quotes the market, which ends about two minutes\nBEFORE the jump (measured 2026-09-01 on Horsham R3, advertised 05:24 UTC: betfair last seen\n119s before the off), which is why results between 2026-09-01 and 2026-09-03 carry null.\nExchange-sourced market state is withheld from customer plans pending a data licence, so a\nrace whose state only the exchange saw reads null for customers; bookmaker-sourced state is\nserved to everybody. `market_state_source` names the feed behind the values you receive.\n`inplay_at` stays exchange-only — bookmakers do not run racing in-play.\n\n**Coverage:** results collection began 2026-08-15, so `hours_back` cannot reach further than\nthat no matter what you pass. Every row carries `coverage_from`, the earliest result\ncurrently retained, so an empty window is distinguishable from an unsupported one.\n\n**Field coverage, which is a different question, and `runners: null` is not its answer.**\nEvery row now carries `field_status`, `field_source` and `field_note`. `field_status` is\n`complete` | `pending` | `overdue` | `unavailable` | `unsupported` | `not_applicable`: a\nfield that is here; one the pass that NORMALLY fills this race has not reached yet, with\nthat instant in `field_note`; one where that pass has been and gone without writing a\nfield but the collector still re-attempts the row; one no scheduled pass still selects,\nits collector's own candidate window having closed; a code no collector has ever existed\nfor; and a race that did not run. The scheduled progression is pending -> overdue ->\nunavailable, one direction only, and every step is bounded by a collector's real candidate\nquery rather than by its next run — so nothing here asks you to poll forever. Read\n`overdue` carefully: it is NOT terminal. Most rows that reach it stay null, so do not build\non a field arriving, but do not write the race off either. And any of those three can\nbecome `complete`, `unavailable` INCLUDED — nothing un-writes a `runners` array, so a\nhand-run collector, a widened window or a late upstream publication can still fill a row we\nhave stopped scheduling passes for. Treat `unavailable` as 'nothing further is scheduled',\nnever as 'this can never arrive', and re-read rather than caching it as final. The full\nper-state contract is on the `field_status` field itself. AU thoroughbred fields come from\nRacing Australia and AU greyhound fields from our own collection; AU harness, NZ\nthoroughbred and NZ harness fields have come from TAB New Zealand since 2026-10-04\n(`field_source: tabnz`, hourly, seven-day window). An entry carrying `origin: board` is a\ncopy of the pre-race board (driver, trainer, barrier, form, positions for placegetters\nonly, no times, SP or margins), not an official finishing field. Collection floors are the\n2026-08-21 Sydney card for AU greyhound and the 2026-09-01 card for AU thoroughbred; no\ncollector pass reaches back beyond its own floor, so a race older than one carries\n`unsupported` and will not fill.\n`runners` is JSON null in every one of these cases and has never been an empty array (0 in\nthe whole store), so `runners is None` is the complete test. GET\n/v1/racing/results/coverage answers the same question per Sydney day, category and venue\nfor 1 credit, so you can see what a day holds without paging its results.\n\n`going` and `going_rating` (2026-09-10) are `track_condition` split into the vocabulary\n/v1/racing/horses/form already uses — 'Good (4)' becomes `going: \"Good\"`,\n`going_rating: 4` — parsed by the one shared helper so the two surfaces cannot disagree.\n`track_condition` is unchanged and is still the raw string the source published.","operationId":"race_results_v1_racing_results_get","parameters":[{"name":"hours_back","in":"query","required":false,"schema":{"type":"integer","maximum":168,"minimum":1,"description":"How far back to look, in hours (max 7 days)","default":24,"title":"Hours Back"},"description":"How far back to look, in hours (max 7 days)"},{"name":"date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Single-day alias: date=YYYY-MM-DD returns the races that start on that calendar date in Sydney time (AET), instead of a rolling hours_back window (YYYYMMDD is read the same way). It is the same meeting_date_aet that /v1/racing/closing-lines stores, so the two join on date, venue and race_number. Each race belongs to exactly one date: a race that starts after midnight Sydney time (the last races of a Perth night meeting, British and Irish afternoon and evening racing) is under the next date. Mutually exclusive with hours_back. Also accepted by /v1/racing/closing-lines and /v1/racing/acceptances. NOT by /v1/racing/events, which is forward-looking and takes hours_ahead.","title":"Date"},"description":"Single-day alias: date=YYYY-MM-DD returns the races that start on that calendar date in Sydney time (AET), instead of a rolling hours_back window (YYYYMMDD is read the same way). It is the same meeting_date_aet that /v1/racing/closing-lines stores, so the two join on date, venue and race_number. Each race belongs to exactly one date: a race that starts after midnight Sydney time (the last races of a Perth night meeting, British and Irish afternoon and evening racing) is under the next date. Mutually exclusive with hours_back. Also accepted by /v1/racing/closing-lines and /v1/racing/acceptances. NOT by /v1/racing/events, which is forward-looking and takes hours_ahead."},{"name":"categories","in":"query","required":false,"schema":{"type":"string","description":"horse,greyhound,harness or omit for all","title":"Categories"},"description":"horse,greyhound,harness or omit for all"},{"name":"category","in":"query","required":false,"schema":{"type":"string","description":"Alias of `categories`. Both spellings are accepted on every racing endpoint. An unknown value is a free 422 naming the valid ones.","title":"Category"},"description":"Alias of `categories`. Both spellings are accepted on every racing endpoint. An unknown value is a free 422 naming the valid ones."},{"name":"venue","in":"query","required":false,"schema":{"type":"string","description":"Case-insensitive exact venue match, comma-separated for several, e.g. 'Rosehill' or 'Randwick,Eagle Farm,Flemington' — the same spelling /v1/racing/next-to-go takes","title":"Venue"},"description":"Case-insensitive exact venue match, comma-separated for several, e.g. 'Rosehill' or 'Randwick,Eagle Farm,Flemington' — the same spelling /v1/racing/next-to-go takes"},{"name":"country","in":"query","required":false,"schema":{"type":"string","description":"ISO country codes, comma-separated, case-insensitive (e.g. AU or AU,NZ). Matched against the result's `country`. Which countries have results at all is published by /v1/racing/results/coverage and the results_scope note on /v1/racing/closing-lines/coverage; GB and IE thoroughbred results come from Betway's settlement feed from 2026-10-06, GB and IE greyhound results from PointsBet's settled race card from 2026-10-12, and ZA thoroughbred results from Easybet's settled race card from 2026-10-15 (the placed runners ranked, `sp` null because the feed publishes no official starting price); a country with prices but no results source returns no rows here. A result whose country was never resolved (null) is excluded by any value here, so omit the filter if you need those rows.","title":"Country"},"description":"ISO country codes, comma-separated, case-insensitive (e.g. AU or AU,NZ). Matched against the result's `country`. Which countries have results at all is published by /v1/racing/results/coverage and the results_scope note on /v1/racing/closing-lines/coverage; GB and IE thoroughbred results come from Betway's settlement feed from 2026-10-06, GB and IE greyhound results from PointsBet's settled race card from 2026-10-12, and ZA thoroughbred results from Easybet's settled race card from 2026-10-15 (the placed runners ranked, `sp` null because the feed publishes no official starting price); a country with prices but no results source returns no rows here. A result whose country was never resolved (null) is excluded by any value here, so omit the filter if you need those rows."},{"name":"status","in":"query","required":false,"schema":{"type":"string","description":"Filter to 'final', 'interim' or 'abandoned'","title":"Status"},"description":"Filter to 'final', 'interim' or 'abandoned'"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RaceResultOut"},"title":"Response Race Results V1 Racing Results Get"},"example":[{"race_id":"104702cc-8a42-4b14-88b0-1fb6de91e913","coverage_from":"2026-08-15T05:53:00Z","venue":"Kalgoorlie","venue_id":"kalgoorlie","venue_canonical":"Kalgoorlie","venue_site":"kalgoorlie","race_number":8,"category":"horse","country":"AU","start_time":"2026-09-06T09:00:00Z","race_name":"COOLGARDIE DAY CELEBRATIONS HANDICAP","distance_m":1200,"track_condition":"Good (4)","going":"Good","going_rating":4,"weather":"Light Rain","status":"final","status_note":null,"field_status":"complete","field_source":"racing_australia","field_note":null,"placings":[{"name":"Vomo Island","number":8,"position":1,"win_price":4.6,"place_price":1.9,"sp":4.6,"sp_note":null,"horse_ref":"pe:vomoisland"},{"name":"Dublin Red","number":15,"position":2,"win_price":126.0,"place_price":17.0,"sp":101.0,"sp_note":null,"horse_ref":"pe:dublinred"},{"name":"Lavish Charm","number":1,"position":3,"win_price":7.5,"place_price":2.5,"sp":8.5,"sp_note":null,"horse_ref":"pe:lavishcharm"}],"runners":[{"name":"Vomo Island","number":8,"box":3,"position":1,"status":"ran","dead_heat":false,"jockey":"Austin Galati","trainer":"John Lugg","sp":4.6,"sp_note":null,"margin":null,"time_s":null,"abnormal":null,"runner_ref":"ra:NzkyMzc2NTE1MA","horse_ref":"pe:vomoisland"},{"name":"Dublin Red","number":15,"box":5,"position":2,"status":"ran","dead_heat":false,"jockey":"Alex Hearn","trainer":"Paul Tapper","sp":101.0,"sp_note":null,"margin":"2.75L","time_s":null,"abnormal":null,"runner_ref":"ra:NzkyNjU3OTg0Ng","horse_ref":"pe:dublinred"},{"name":"Lavish Charm","number":1,"box":4,"position":3,"status":"ran","dead_heat":false,"jockey":"Jason Li","trainer":"Peter Fernie","sp":8.5,"sp_note":null,"margin":"3.27L","time_s":null,"abnormal":null,"runner_ref":"ra:Nzk1MjAxMDcxNA","horse_ref":"pe:lavishcharm"}],"deductions":[{"name":"Bonny Rock","number":10,"win":0.0,"place":0.0,"scratched_at":"2026-09-06T08:39:45Z"}],"dividends":{"straight":[{"name":"Vomo Island","number":8,"position":1,"market":"WIN","product":"FIXED","amount":4.6},{"name":"Vomo Island","number":8,"position":1,"market":"WIN","product":"MIDDIV","amount":5.5},{"name":"Vomo Island","number":8,"position":1,"market":"PLC","product":"FIXED","amount":1.9},{"name":"Dublin Red","number":15,"position":2,"market":"PLC","product":"FIXED","amount":17.0},{"name":"Lavish Charm","number":1,"position":3,"market":"PLC","product":"FIXED","amount":2.5}],"exotics":[{"code":"QN","type":"Quinella","product":"VIC","selection":"8,15","amount":444.4},{"code":"EX","type":"Exacta","product":"VIC","selection":"8,15","amount":478.1},{"code":"TF","type":"Trifecta","product":"VIC","selection":"8,15,1","amount":4868.5},{"code":"FF","type":"First Four","product":"VIC","selection":"8,15,1,13","amount":58687.9}],"straight_types":["FIXED","RD+","BT+SP","MIDDIV"]},"resulted_at":"2026-09-06T09:05:44Z","market_closed_at":"2026-09-06T09:02:18Z","inplay_at":null,"market_state_source":"pointsbetau"},{"race_id":"c56efd85-cbee-4743-87b2-94245b6446be","coverage_from":"2026-08-15T05:53:00Z","venue":"Rosehill","venue_id":"rosehill","venue_canonical":"Rosehill","venue_site":"rosehill","race_number":10,"category":"horse","country":"AU","start_time":"2026-09-26T07:25:00Z","race_name":"Tab Handicap","distance_m":1100,"track_condition":"Good (4)","going":"Good","going_rating":4,"weather":null,"status":"final","status_note":null,"field_status":"complete","field_source":"racing_australia","field_note":null,"placings":[{"name":"Annie's Rose","number":11,"position":1,"win_price":6.5,"place_price":2.2,"sp":6.5,"sp_note":null,"horse_ref":"pe:anniesrose"},{"name":"Lulumon","number":6,"position":2,"win_price":21.0,"place_price":5.0,"sp":21.0,"sp_note":null,"horse_ref":"pe:lulumon"},{"name":"Mercy Me","number":15,"position":3,"win_price":3.5,"place_price":1.5,"sp":3.5,"sp_note":"F","horse_ref":"pe:mercyme"}],"runners":[{"name":"Annie's Rose","number":11,"box":3,"position":1,"status":"ran","dead_heat":false,"jockey":"Reece Jones","trainer":"Kristen Buchanan","sp":6.5,"sp_note":null,"margin":null,"time_s":null,"abnormal":null,"runner_ref":"ra:MzcwOTA0ODk4Nzg","horse_ref":"pe:anniesrose"},{"name":"Lulumon","number":6,"box":4,"position":2,"status":"ran","dead_heat":false,"jockey":"Ms Siena Grima","trainer":"Jason Coyle","sp":21.0,"sp_note":null,"margin":"0.75L","time_s":null,"abnormal":null,"runner_ref":"ra:MzcxMTY2MTk0NjI","horse_ref":"pe:lulumon"},{"name":"Mercy Me","number":15,"box":5,"position":3,"status":"ran","dead_heat":false,"jockey":"Regan Bayliss","trainer":"Nathan Doyle","sp":3.5,"sp_note":"F","margin":"0.85L","time_s":null,"abnormal":null,"runner_ref":"ra:MzczNzU2NDIyNTI","horse_ref":"pe:mercyme"}],"deductions":[{"name":"Cantiamo","number":5,"win":0.05,"place":0.06,"scratched_at":"2026-09-25T21:03:05.507Z"},{"name":"Banjora","number":9,"win":0.15,"place":0.12,"scratched_at":"2026-09-24T08:17:40.433Z"}],"dividends":{"straight":[{"name":"Annie's Rose","number":11,"position":1,"market":"WIN","product":"FIXED","amount":6.5},{"name":"Annie's Rose","number":11,"position":1,"market":"PLC","product":"FIXED","amount":2.2},{"name":"Lulumon","number":6,"position":2,"market":"PLC","product":"FIXED","amount":5.0},{"name":"Mercy Me","number":15,"position":3,"market":"PLC","product":"FIXED","amount":1.5}],"exotics":[{"code":"QN","type":"Quinella","product":"VIC","selection":"11,6","amount":77.95},{"code":"EX","type":"Exacta","product":"VIC","selection":"11,6","amount":108.3},{"code":"TF","type":"Trifecta","product":"VIC","selection":"11,6,15","amount":432.2},{"code":"FF","type":"First Four","product":"VIC","selection":"11,6,15,17","amount":6979.0}],"multi_race":[{"code":"RC-GIANT6","type":"Giant 6","product":"VIC","selection":"6,1,1,4,2,11","amount":544.4,"legs":[5,6,7,8,9,10]},{"code":"RC-GIANT6","type":"Giant 6","product":"VIC","selection":"6,1,1,4,2","amount":1.0,"legs":[5,6,7,8,9,10]},{"code":"RC-QUADDIE","type":"Quaddie","product":"VIC","selection":"1,4,2,11","amount":128.4,"legs":[7,8,9,10]},{"code":"RC-QUADDIE","type":"Quaddie","product":"BT","selection":"1,4,2,11","amount":128.4,"legs":[7,8,9,10]}],"straight_types":["FIXED","RD+","BT+SP","MIDDIV"]},"resulted_at":"2026-09-26T07:31:54Z","market_closed_at":"2026-09-26T07:27:07Z","inplay_at":null,"market_state_source":"pointsbetau"},{"race_id":"1f737be2-2e3e-4636-b2f8-1f42968eef7a","coverage_from":"2026-08-15T05:53:00Z","venue":"Wagga","venue_id":"wagga","venue_canonical":"Wagga","venue_site":"wagga","race_number":1,"category":"harness","country":"AU","start_time":"2026-10-04T01:03:00Z","race_name":"Download The Tab App Today Pace","distance_m":1740,"track_condition":"Good","weather":"Scattered Clouds","status":"final","status_note":null,"placings":[{"name":"Wrong Answer","number":1,"position":1,"win_price":2.2,"place_price":1.18,"horse_ref":null},{"name":"Rise Again","number":7,"position":2,"win_price":4.0,"place_price":1.55,"horse_ref":null},{"name":"Rusty Mudshark","number":3,"position":3,"win_price":7.0,"place_price":2.0,"horse_ref":null},{"name":"Typhoon Lenny","number":2,"position":4,"win_price":34.0,"place_price":5.25,"horse_ref":null}],"runners":[{"sp":null,"age":4,"box":1,"sex":"G","name":"Wrong Answer","jockey":"Bayley Duck","margin":0,"number":1,"origin":"tabnz","status":"ran","time_s":null,"sp_note":null,"trainer":"B K Duck","abnormal":null,"position":1,"tote_win":1.9,"dead_heat":false,"prize_won":null,"race_name":"Download The Tab App Today Pace","weight_kg":null,"carried_kg":null,"race_class":null,"runner_ref":null,"start_type":"Mobile","tote_place":1.4,"last_600m_s":null,"prize_total":null,"race_time_s":null,"scratched_at":null,"tabnz_weight":null,"result_status":"final","tabnz_horse_id":2354177,"barrier_position":"Fr1","positional_margin":null,"horse_ref":null},{"sp":null,"age":3,"box":6,"sex":"C","name":"Rise Again","jockey":"Peter Mcrae","margin":1.34,"number":7,"origin":"tabnz","status":"ran","time_s":null,"sp_note":null,"trainer":"Peter Romero","abnormal":null,"position":2,"tote_win":null,"dead_heat":false,"prize_won":null,"race_name":"Download The Tab App Today Pace","weight_kg":null,"carried_kg":null,"race_class":null,"runner_ref":null,"start_type":"Mobile","tote_place":1.3,"last_600m_s":null,"prize_total":null,"race_time_s":null,"scratched_at":null,"tabnz_weight":null,"result_status":"final","tabnz_horse_id":2373085,"barrier_position":"Fr6","positional_margin":null,"horse_ref":null},{"sp":null,"age":4,"box":3,"sex":"H","name":"Rusty Mudshark","jockey":"Jackson Painting","margin":3.1,"number":3,"origin":"tabnz","status":"ran","time_s":null,"sp_note":null,"trainer":"M J Painting","abnormal":null,"position":3,"tote_win":null,"dead_heat":false,"prize_won":null,"race_name":"Download The Tab App Today Pace","weight_kg":null,"carried_kg":null,"race_class":null,"runner_ref":null,"start_type":"Mobile","tote_place":2.4,"last_600m_s":null,"prize_total":null,"race_time_s":null,"scratched_at":null,"tabnz_weight":null,"result_status":"final","tabnz_horse_id":2331468,"barrier_position":"Fr3","positional_margin":null,"horse_ref":null},{"sp":null,"age":2,"box":2,"sex":"C","name":"Typhoon Lenny","jockey":"Roger Strong","margin":8.02,"number":2,"origin":"tabnz","status":"ran","time_s":null,"sp_note":null,"trainer":"J R Turner","abnormal":null,"position":4,"tote_win":null,"dead_heat":false,"prize_won":null,"race_name":"Download The Tab App Today Pace","weight_kg":null,"carried_kg":null,"race_class":null,"runner_ref":null,"start_type":"Mobile","tote_place":null,"last_600m_s":null,"prize_total":null,"race_time_s":null,"scratched_at":null,"tabnz_weight":null,"result_status":"final","tabnz_horse_id":2386291,"barrier_position":"Fr2","positional_margin":null,"horse_ref":null},{"sp":null,"age":4,"box":7,"sex":"M","name":"Sweet Bella Rosa Nz","jockey":"Blake Micallef","margin":11.29,"number":8,"origin":"tabnz","status":"ran","time_s":null,"sp_note":null,"trainer":"Sam Hewitt","abnormal":null,"position":5,"tote_win":null,"dead_heat":false,"prize_won":null,"race_name":"Download The Tab App Today Pace","weight_kg":null,"carried_kg":null,"race_class":null,"runner_ref":null,"start_type":"Mobile","tote_place":null,"last_600m_s":null,"prize_total":null,"race_time_s":null,"scratched_at":null,"tabnz_weight":null,"result_status":"final","tabnz_horse_id":2334037,"barrier_position":"Fr7","positional_margin":null,"horse_ref":null},{"sp":null,"age":4,"box":2,"sex":"M","name":"Elleschance","jockey":"Scott Hewitt","margin":12.63,"number":10,"origin":"tabnz","status":"ran","time_s":null,"sp_note":null,"trainer":"Scott Hewitt","abnormal":null,"position":6,"tote_win":null,"dead_heat":false,"prize_won":null,"race_name":"Download The Tab App Today Pace","weight_kg":null,"carried_kg":null,"race_class":null,"runner_ref":null,"start_type":"Mobile","tote_place":null,"last_600m_s":null,"prize_total":null,"race_time_s":null,"scratched_at":null,"tabnz_weight":null,"result_status":"final","tabnz_horse_id":2233871,"barrier_position":"Sr2","positional_margin":null,"horse_ref":null},{"sp":null,"age":4,"box":5,"sex":"G","name":"Im The Sniper","jockey":"Michael Towers","margin":13.52,"number":6,"origin":"tabnz","status":"ran","time_s":null,"sp_note":null,"trainer":"M B Towers","abnormal":null,"position":7,"tote_win":null,"dead_heat":false,"prize_won":null,"race_name":"Download The Tab App Today Pace","weight_kg":null,"carried_kg":null,"race_class":null,"runner_ref":null,"start_type":"Mobile","tote_place":null,"last_600m_s":null,"prize_total":null,"race_time_s":null,"scratched_at":null,"tabnz_weight":null,"result_status":"final","tabnz_horse_id":2157745,"barrier_position":"Fr5","positional_margin":null,"horse_ref":null},{"sp":null,"age":4,"box":1,"sex":"M","name":"Jiveandwhisper","jockey":"Harrison Ross","margin":17.07,"number":9,"origin":"tabnz","status":"ran","time_s":null,"sp_note":null,"trainer":"E J Galea","abnormal":null,"position":8,"tote_win":null,"dead_heat":false,"prize_won":null,"race_name":"Download The Tab App Today Pace","weight_kg":null,"carried_kg":null,"race_class":null,"runner_ref":null,"start_type":"Mobile","tote_place":null,"last_600m_s":null,"prize_total":null,"race_time_s":null,"scratched_at":null,"tabnz_weight":null,"result_status":"final","tabnz_horse_id":2371488,"barrier_position":"Sr1","positional_margin":null,"horse_ref":null},{"sp":null,"age":3,"box":4,"sex":"G","name":"Crankinover","jockey":"Shane Hallcroft","margin":18.08,"number":5,"origin":"tabnz","status":"ran","time_s":null,"sp_note":null,"trainer":"P S Bryce","abnormal":null,"position":9,"tote_win":null,"dead_heat":false,"prize_won":null,"race_name":"Download The Tab App Today Pace","weight_kg":null,"carried_kg":null,"race_class":null,"runner_ref":null,"start_type":"Mobile","tote_place":null,"last_600m_s":null,"prize_total":null,"race_time_s":null,"scratched_at":null,"tabnz_weight":null,"result_status":"final","tabnz_horse_id":2347233,"barrier_position":"Fr4","positional_margin":null,"horse_ref":null},{"sp":null,"age":6,"box":null,"sex":"M","name":"Jolted Lover","jockey":"Scratched","margin":null,"number":4,"origin":"tabnz","status":"scratched","time_s":null,"sp_note":null,"trainer":"P D Symes","abnormal":null,"position":null,"tote_win":null,"dead_heat":false,"prize_won":null,"race_name":"Download The Tab App Today Pace","weight_kg":null,"carried_kg":null,"race_class":null,"runner_ref":null,"start_type":"Mobile","tote_place":null,"last_600m_s":null,"prize_total":null,"race_time_s":null,"scratched_at":"2026-10-02T06:02:38Z","tabnz_weight":null,"result_status":"final","tabnz_horse_id":1887761,"barrier_position":"Fr-","positional_margin":null,"horse_ref":null}],"field_status":"complete","field_source":"tabnz","field_note":null,"going":"Good","going_rating":null,"deductions":[{"win":0.0,"name":"Jolted Lover","place":0.0,"number":4,"scratched_at":"2026-10-04T01:05:51.647Z"}],"dividends":{"exotics":[{"code":"QN","type":"Quinella","amount":4.6,"product":"VIC","selection":"1,7"},{"code":"EX","type":"Exacta","amount":6.8,"product":"VIC","selection":"1,7"},{"code":"TF","type":"Trifecta","amount":39.4,"product":"VIC","selection":"1,7,3"},{"code":"FF","type":"First Four","amount":242.3,"product":"VIC","selection":"1,7,3,2"}],"straight":[{"name":"Wrong Answer","amount":2.2,"market":"WIN","number":1,"product":"FIXED","position":1},{"name":"Wrong Answer","amount":1.9,"market":"WIN","number":1,"product":"MIDDIV","position":1},{"name":"Wrong Answer","amount":1.18,"market":"PLC","number":1,"product":"FIXED","position":1},{"name":"Wrong Answer","amount":1.4,"market":"PLC","number":1,"product":"MIDDIV","position":1},{"name":"Rise Again","amount":1.55,"market":"PLC","number":7,"product":"FIXED","position":2},{"name":"Rise Again","amount":1.3,"market":"PLC","number":7,"product":"MIDDIV","position":2},{"name":"Rusty Mudshark","amount":2.0,"market":"PLC","number":3,"product":"FIXED","position":3},{"name":"Rusty Mudshark","amount":2.4,"market":"PLC","number":3,"product":"MIDDIV","position":3}],"multi_race":[],"straight_types":["FIXED","MIDDIV"]},"resulted_at":"2026-10-04T01:10:17.490670Z","market_closed_at":"2026-10-04T01:04:42.969861Z","inplay_at":null,"market_state_source":"pointsbetau"}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/results/coverage":{"get":{"tags":["Racing"],"summary":"What the results field coverage actually is","description":"Cost: 1 credit. The results store's own measurement of its FULL-FIELD coverage, per\nSydney day, category and venue — the aggregate answer to the question `field_status`\nanswers per race on /v1/racing/results.\n\n`runners: null` on its own can mean several\ndifferent things: a fill pass that has not run yet (`pending`), a pass that has been and\ngone while the collector keeps re-attempting the row (`overdue`), a row past its\ncollector's retry horizon so that no scheduled pass still selects it (`unavailable`), a\nracing code no collector exists for (`unsupported` — since 2026-10-04 only foreign races an\nAU book listed; AU harness and both NZ codes, 1,254 of the 5,577 stored results as at\n2026-09-10, are collected from TAB New Zealand from that day), and a race that did not run\n(`not_applicable`). Counting them separately is what lets you decide between polling,\nreconciling and giving up, without paging the results themselves.\n\nEvery count comes from the same function that sets `field_status` on each race, applied\nto the rows in the window at request time, so this summary and the races it summarises\ncan never disagree. Nothing holds a race in `pending` indefinitely: `pending` ends at the\nnormal pass, `overdue` ends at the collector's own candidate horizon.\n\nREAD THE PERCENTAGES CAREFULLY. `complete_pct` and `supported_complete_pct` are NULL, not\n0.0, when their denominator is 0 — an empty window, or one holding only codes we do not\ncollect. A `days=1` window is empty for the first hours of every Sydney day, and a monitor\nthat treated a fabricated 0.0 as real coverage would page every night. `collectable` is\npublished alongside them so 0-of-0 is never mistaken for 0-of-many.\n\nDays are AUSTRALIAN EASTERN calendar days (Australia/Sydney), the same day a meeting is\nadvertised under, and `to_day` is today in Sydney — a part day whose races are mostly\nstill `pending`, which is normal and not a gap. Read `by_day` for the day-by-day picture\nand `venues_incomplete` for the venue-days that are genuinely short.\n\n`archive_races_without_result` looks from the other side: AU and NZ races in the\nclosing-line archive, in this window and at least three hours old, that have no result row\nat all (`archive_races_in_window` is its denominator). They are not in `races`, which counts\nstored results only.\n\nSibling endpoint: /v1/racing/closing-lines/coverage does the same job for the price\narchive.","operationId":"results_coverage_v1_racing_results_coverage_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":30,"minimum":1,"description":"Sydney days to count, ending with today (a part day).","default":7,"title":"Days"},"description":"Sydney days to count, ending with today (a part day)."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResultsCoverageOut"},"example":{"as_of":"2026-09-10T03:40:12Z","by_category":[{"category":"greyhound","complete":220,"country":"AU","field_source":"fasttrack","not_applicable":0,"overdue":8,"pending":6,"races":742,"unavailable":0,"unsupported":508},{"category":"horse","complete":108,"country":"AU","field_source":"racing_australia","not_applicable":0,"overdue":21,"pending":2,"races":137,"unavailable":6,"unsupported":0},{"category":"harness","complete":0,"country":"AU","not_applicable":0,"overdue":0,"pending":0,"races":131,"unavailable":0,"unsupported":131}],"by_day":[{"category":"greyhound","complete":5,"country":"AU","day":"2026-09-10","field_source":"fasttrack","not_applicable":0,"overdue":0,"pending":1,"races":19,"unavailable":0,"unsupported":13},{"category":"horse","complete":42,"country":"AU","day":"2026-09-09","field_source":"racing_australia","not_applicable":0,"overdue":7,"pending":0,"races":49,"unavailable":0,"unsupported":0}],"collectable":879,"complete":806,"complete_pct":75.54,"coverage_from":"2026-08-15T05:53:00Z","days":7,"field_scope":"Full fields are collected for AU thoroughbred (Racing Australia, from the 2026-08-15 card, the first day of results) and AU greyhound (FastTrack for Victoria, the national results pages elsewhere, from 2026-09-13), NZ harness (TAB New Zealand, from the 2026-09-30 Sydney card), NZ thoroughbred (TAB New Zealand, from the 2026-09-30 card) and AU harness (TAB New Zealand, from the 2026-09-27 card). No pass reaches back beyond its own floor.","from_day":"2026-09-04","not_applicable":0,"overdue":52,"pending":21,"races":1067,"supported_complete_pct":91.7,"to_day":"2026-09-10","unavailable":0,"unsupported":188,"venues_incomplete":[{"category":"horse","complete":0,"country":"AU","day":"2026-09-09","not_applicable":0,"overdue":7,"pending":0,"races":7,"settled":false,"unavailable":0,"unsupported":0,"venue":"Randwick","venue_id":"randwick"}],"venues_incomplete_truncated":false}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/price-history":{"get":{"tags":["Racing"],"summary":"Racing price history / closing line","description":"Cost: 5 credits. Every price move for a race, per runner per bookmaker, with the opening\nand closing line.\n\nIdentify the race either by `race_id`, or by `venue` + `race_number` + `date` — use the latter\nfor past races, because race ids are only resolvable from the live feed, which holds the next\n~6 hours.\n\n**The series is change-only.** Each point is a price MOVE, not a sample: a gap means the price\nheld steady. `move_pct` is open→close, positive meaning the price drifted out.\n\n**`open_price` and `close_price` are the first and last CAPTURED prices for that book, not\nguaranteed market open and close.** Capture starts when a race enters the 60-minute window,\nand a book that does not quote that early gets its first price whenever it starts quoting:\nmeasured over 30,448 series on 2026-08-15, 44% of series began at the 60-minute baseline and\n56% began later. At the other end 63% of series have a final point inside 120s of the jump\nand 81% inside 300s, so the rest are not closing lines either. Read `open_secs_to_jump`,\n`close_secs_to_jump` and `open_is_baseline` on each bookmaker before using a series for\nclosing-line value, and drop the ones that do not reach the jump.\n\n**History is permanent.** Races inside the ~45-day live window are served from the live\nstore; older races come from the permanent archive, which is never purged. `history_from`\non the response is the true floor — capture began 2026-08-04, and the archive only\ndeepens from there. (Identify past races by `venue` + `race_number` + `date`; both stores\nresolve that key.)","operationId":"price_history_v1_racing_price_history_get","parameters":[{"name":"race_id","in":"query","required":false,"schema":{"type":"string","description":"Race id. If omitted, give venue + race_number + date.","title":"Race Id"},"description":"Race id. If omitted, give venue + race_number + date."},{"name":"venue","in":"query","required":false,"schema":{"type":"string","description":"Venue name, case-insensitive (with race_number + date)","title":"Venue"},"description":"Venue name, case-insensitive (with race_number + date)"},{"name":"race_number","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"title":"Race Number"}},{"name":"date","in":"query","required":false,"schema":{"type":"string","description":"Race date, YYYY-MM-DD (YYYYMMDD accepted): the UTC day of the race's start, or, when that finds nothing, the Australian Eastern meeting date, so the date /v1/racing/results and /v1/racing/closing-lines give a race resolves it here too. A date that resolves nothing is a free 404 naming the venue's races with history around it.","title":"Date"},"description":"Race date, YYYY-MM-DD (YYYYMMDD accepted): the UTC day of the race's start, or, when that finds nothing, the Australian Eastern meeting date, so the date /v1/racing/results and /v1/racing/closing-lines give a race resolves it here too. A date that resolves nothing is a free 404 naming the venue's races with history around it."},{"name":"bookmakers","in":"query","required":false,"schema":{"type":"string","description":"Comma-separated bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing.","title":"Bookmakers"},"description":"Comma-separated bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing."},{"name":"include_points","in":"query","required":false,"schema":{"type":"boolean","description":"False returns open/close/high/low only","default":true,"title":"Include Points"},"description":"False returns open/close/high/low only"},{"name":"max_points","in":"query","required":false,"schema":{"type":"integer","maximum":20000,"minimum":100,"description":"Cap on returned points; response flags truncation","default":5000,"title":"Max Points"},"description":"Cap on returned points; response flags truncation"},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceHistoryOut"},"example":{"category":"horse","country":"AU","history_from":"2026-08-04T09:22:00Z","points_returned":2481,"race_id":"race_123","race_number":2,"runners":[{"bookmakers":[{"close_price":7.0,"close_secs_to_jump":41,"high":7.0,"key":"sportsbet","low":6.0,"move_pct":7.69,"open_is_baseline":true,"open_price":6.5,"open_secs_to_jump":3595,"points":[{"captured_at":"2026-08-15T02:00:04Z","secs_to_jump":3595,"win_price":6.5}],"points_count":10}],"name":"Aeolian","number":4}],"start_time":"2026-08-15T03:00:00Z","truncated":false,"venue":"Caulfield"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"x-curl-query":"race_id=YOUR_RACE_ID"}},"/v1/racing/movers":{"get":{"tags":["Racing"],"summary":"Racing steamers and drifters","description":"Cost: 3 credits. Runners whose price has moved materially since the market opened,\nacross upcoming races.\n\n`move_pct` is **negative for a firming runner** (the price shortened — money has arrived)\nand positive for a drifter.\n\n`open_price` is the FIRST PRICE CAPTURED inside the 60-minute pre-race window, not a market\nopen. Capture begins when the race enters that window, so how close that first price is to a\ntrue 60-minute open depends on the book — and it is not a clean two-way split. Measured over\n13 days of snapshots, the share of series whose first capture sits at the 60-minute mark\nfalls into three groups. `sportsbet`, `betright` and `betr_au` are there on 96-98% of series.\n`tab`, `ladbrokes_au` and `neds` are there on about 55%, but that figure swings between 16%\nand 77% from one day to the next, so for these three it is not a property you can rely on\nper book. `playup` (6%), `unibet` and `pointsbetau` (3%), `tabtouch` (1%) and `palmerbet`\n(0%) rarely or never are: `palmerbet` has not once been captured earlier than 33 minutes out\nacross 23,641 series.\n\nDo not infer any of this from the book. Read `open_secs_to_jump` on each bookmaker row, which\nsays how far out that book's open actually was; `/v1/racing/price-history` additionally\nreturns `open_is_baseline` per series.\n\nA move is reported at the RUNNER level and only when at least `min_books` bookmakers have\n**two or more captured prices**, because a book seen only once has evidenced nothing: its\nopen equals its current by construction. Those books are still listed, with `points: 1` and\n`counted_in_consensus: false`, and still counted in `books_quoting` — but they do not enter\nthe consensus or the gate. Use `books_with_history` as the confirmation count.\n\n**Broad-market support versus one book moving: read the breadth counters.** `books_firming`,\n`books_drifting` and `books_unchanged` partition `books_quoting`, so they say how many books\nactually agreed with the headline move. A steam the market backs shows most of\n`books_with_history` moving the same way; a single-book move shows one, with the rest\nunchanged, and can still clear `min_move_pct` because the consensus averages over the few\nbooks that carry history. Measured live on 2026-09-01, Geelong R4 greyhound \"Got Immunity\"\nreported +102.8% drifting on 13 books quoting, of which 5 had history: 3 drifting, 1 firming,\n9 unchanged - the headline number came from a minority. Gate on the counters, and on\n`bookmakers[].move_pct` where you need the per-book evidence, rather than on `move_pct`\nalone. Raising `min_books` raises how many books must carry history, not how many must agree.","operationId":"racing_movers_v1_racing_movers_get","parameters":[{"name":"direction","in":"query","required":false,"schema":{"type":"string","description":"firming, drifting, or omit for both","title":"Direction"},"description":"firming, drifting, or omit for both"},{"name":"min_move_pct","in":"query","required":false,"schema":{"type":"number","maximum":90.0,"minimum":1.0,"description":"Minimum consensus move, percent","default":10.0,"title":"Min Move Pct"},"description":"Minimum consensus move, percent"},{"name":"min_books","in":"query","required":false,"schema":{"type":"integer","maximum":12,"minimum":1,"description":"Only report a move confirmed by at least this many books","default":3,"title":"Min Books"},"description":"Only report a move confirmed by at least this many books"},{"name":"categories","in":"query","required":false,"schema":{"type":"string","description":"horse,greyhound,harness or omit for all","title":"Categories"},"description":"horse,greyhound,harness or omit for all"},{"name":"category","in":"query","required":false,"schema":{"type":"string","description":"Alias of `categories`. Both spellings are accepted on every racing endpoint. An unknown value is a free 422 naming the valid ones.","title":"Category"},"description":"Alias of `categories`. Both spellings are accepted on every racing endpoint. An unknown value is a free 422 naming the valid ones."},{"name":"country","in":"query","required":false,"schema":{"type":"string","description":"ISO country codes, comma-separated (e.g. AU). Omit for every country. Pair with include_unresolved=true or you will silently lose races whose meeting is not confirmed yet.","title":"Country"},"description":"ISO country codes, comma-separated (e.g. AU). Omit for every country. Pair with include_unresolved=true or you will silently lose races whose meeting is not confirmed yet."},{"name":"include_unresolved","in":"query","required":false,"schema":{"type":"boolean","description":"Include races whose country is not resolved yet (country is null). OFF by default. Most of an Australian card is unresolved until the meeting is confirmed — measured 3 labelled AU against 21 unresolved in a live window — so country=AU alone can return far fewer races than are running, or none at all.","default":false,"title":"Include Unresolved"},"description":"Include races whose country is not resolved yet (country is null). OFF by default. Most of an Australian card is unresolved until the meeting is confirmed — measured 3 labelled AU against 21 unresolved in a live window — so country=AU alone can return far fewer races than are running, or none at all."},{"name":"max_mins_to_jump","in":"query","required":false,"schema":{"type":"integer","maximum":360,"minimum":5,"default":360,"title":"Max Mins To Jump"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/MoverOut"},"title":"Response Racing Movers V1 Racing Movers Get"},"example":[{"bookmakers":[{"counted_in_consensus":true,"current_price":9.0,"key":"betright","move_pct":-18.18,"open_price":11.0,"open_secs_to_jump":3597,"points":4}],"books_drifting":0,"books_firming":9,"books_quoting":10,"books_unchanged":1,"books_with_history":9,"category":"horse","country":"AU","current_price":9.17,"direction":"firming","mins_to_jump":3,"move_pct":-18.3,"number":10,"open_price":11.22,"race_number":8,"runner":"Magnaburma","start_time":"2026-08-15T08:52:00Z","venue":"Broome"}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/best-odds":{"get":{"tags":["Racing"],"summary":"Best racing price per runner across books","description":"Cost: 3 credits. The best win, place and tote price for every runner in the next races,\nwith the bookmaker offering it and a deep link to their race page.\n\n`market_percentage` is the sum of implied probabilities at the best available price. **Under\n100 means the best prices across books beat the field** — a cross-book arbitrage. Any single\nbook's market is always over 100.\n\n`price_spread_pct` per runner is how much better the best price is than the worst currently\nquoted: how much a punter loses by not shopping around.\n\n**Win and place coverage are counted separately.** `books_quoting_win` is the number of\nbookmakers with a WIN price on that runner and `books_quoting_place` the number with a\nPLACE price. They are not the same set — a book can quote the win and not the place on\nthe same runner — so a place bet sized off a win-market count is sized off the wrong\nnumber. `books_quoting` is retained unchanged for existing clients and equals\n`books_quoting_win`; read the explicit names in new code. `books_compared`, at race\nlevel, is the number of distinct books contributing any win price to the whole race.\n\n⚠️ `best_tote` is **not comparable to `best_win`**. A pre-race tote dividend is an estimate\nthat moves as the pool fills, and taking the highest across books and products is upward\nbiased by construction — the noisiest estimate wins. Greyhound pools are thinnest and swing\nhardest: measured 2026-08-15, greyhound tote-to-fixed ratios reached 8.8x on betright and\n9.9x on tabtouch, against averages near 1.0 for horse and harness. A tote figure far above\nthe fixed price is usually a thin pool, not an edge. Settle against a FINAL dividend, and\nread the `product` code — `PROV` is indicative, `SP`/`MIDDIV`/`RD+`/`BT+SP` are named\nproducts.\n\nThe comparison covers the Australian fixed-odds bookmakers, on New Zealand races as well\nas Australian ones. Not every book quotes New Zealand, so an NZ race is compared across\nfewer books than an AU race; the live per-country book counts are published at\nhttps://puntersedge.online/coverage-report. Betfair Exchange prices are\nwithheld pending a Betfair Exchange data licence, and non-AU reference books are excluded\nbecause an Australian punter cannot bet them — so `betfair_ex_au` never appears as a\n`best_win` bookmaker, and `market_percentage` is the fixed-odds market rather than an\nexchange-inclusive one.\n\n**Hong Kong** (`country=HK`, feature_requests #29, 2026-09-09). Sha Tin and Happy Valley\nare compared across whichever Australian books quote the meeting — win and place, the\nsame `best_win` / `best_place` shape as an Australian race. PRICES ONLY: no results, no\nform, no race conditions, no `runner_ref` — `horse_ref` alone is present, because it is\nderived from the runner's name and needs no Racing Australia read. Unlike the rest of\nthe foreign card this is a\nreal cross-book comparison rather than one book against itself, but it is a smaller panel\nthan an Australian race and only exists on the HKJC's nights — Happy Valley on Wednesday\nevenings, Sha Tin on Saturday or Sunday afternoons (AEST).\n\n**Scratched runners are never priced here.** They appear in `scratchings` only, and are left\nout of `market_percentage`.\n\n**Placeholder prices are kept out of the best line (since 2026-09-10).** A quote of $21 or\nmore that is at least 4x the median of three or more other books for the same runner is\ntreated as a placeholder, not a price: it is excluded from `best_win` / `best_place`,\n`market_percentage` and `price_spread_pct`, and listed on the runner as `suspect_win` /\n`suspect_place` (`bookmaker`, `price`, `consensus`) so nothing is hidden. A book that has\nnot priced a runner yet can publish $101 against a $9 field; before this that was the best price.\n\n**Every published price carries its own age.** `best_win`, `best_place` and `best_tote` each\ninclude `last_update`, `age_seconds`, `stale` and `refresh_tier`, because the book with the best price is not\nalways the book that answered most recently. `best_win` and `best_place` both carry the\noffering book's race-page `source_url`.\nOn Plus and above both also carry `betslip_url`, the runner-level betslip link of that\nsame book - null when the book has no such route, and absent on plans below Plus. See the\ntable below. Race level, `data_age_seconds` is the oldest of\nthose winning quotes and `stale_bookmakers` names any past its own `refresh_tier`\nthreshold (120s on `live`, 1800s on `card`; each published quote carries its own tier, and\na Betr quote 30-180 minutes from the jump is `card` on a `live` race — see\n/v1/racing/next-to-go for what the tiers mean). Responses are cached for 20s\nand say so via `cached` / `cache_age_seconds`; ages are recomputed at serve time, so a cached\npayload reports its real age rather than the age it had when it was stored.\n\n**Same Race Multi legs** (`best_top2`, `best_top3`, `best_top4`, since 2026-09-05).\nThe best price across books for the runner to finish in the first two, three or four,\nin the same shape as `best_place` (price, bookmaker, source_url, age fields) plus\n`books_quoting`, the number of books offering that leg. Present only when at least one\nbook quotes the leg: Ladbrokes, Neds and PointsBet carry all three, Sportsbet where its\nracecard offers them. Every published leg satisfied\n`win >= top2 >= top3 >= top4 > 1.0` at its source.\n\n**Runner-level betslip links** (`betslip_win_url`, `betslip_place_url`,\n`betslip_app_only`). Where `source_url` opens the book's race PAGE, these open the book's\nBETSLIP with this runner already selected. **Plus and above** (since 21 September 2026):\nthe betslip fields are served on Plus, Business, Platform and Scale, and a Standard or\nHobby key issued before that date keeps them - a key never loses what it was sold. On every\nother plan the fields are simply absent, exactly as on a book with no route; `source_url`,\nthe race-page link, is on every plan and is unaffected. Coverage, honestly:\n\n| book | opens in | preselects |\n|---|---|---|\n| `unibet` | web browser | runner **and** market (fixed win or fixed place) |\n| `tab` | web browser | runner only - the slip shows a win box and a place box, so `betslip_win_url` and `betslip_place_url` are deliberately the same URL |\n| `ladbrokes_au` | **native app only** (iOS/Android 8.69.0+) | runner and market |\n| `neds` | **native app only** | runner and market |\n\nEvery other bookmaker has no such route and carries none of these fields.\n`betslip_app_only` is `true` for Ladbrokes and Neds: in a desktop browser those URLs\nredirect to the app-download page, so send them to a phone with the app installed, or\nfall back to `source_url`. Measured 2026-09-03. The Ladbrokes pattern is vendor\ndocumented; the Neds one is **inferred** from it (same platform, same entrant ids) and\nhas not been tap-tested on a Neds install.\n\nThese routes are read from the bookmakers' own site code. **None of them is published or\nsupported by the bookmaker**, so any can change without notice. A daily automated check\nloads a live Unibet link and a live TAB link in a real browser and confirms the betslip\npopulates; when a book fails that check its betslip fields go **null** until it passes\nagain, so a broken route degrades to \"no link\" rather than sending a member to a dead\npage. Ladbrokes and Neds cannot be checked this way - a headless browser cannot open a\nnative app - and ship on the vendor's documentation instead.\n\nThe fields are **absent** on a book that has no link, exactly as `source_url` is; treat\nabsent and `null` alike as \"no betslip link\". Nothing about `source_url` changed.\n\nLike `source_url` these are plain URLs, and extra query parameters you append are passed\nthrough untouched. A link that opens a native app leaves the browser, so cookie-based\nattribution set by your own site does not travel with it.\n\n**Your own links** (`source_link` and `betslip_link` on every quote object, since\n2026-10-14, Plus and above). Store your bookmaker link templates once with\n`PUT /v1/link-templates` (for example your affiliate tracking link per bookmaker, with\n`{url}` where the bookmaker's URL goes) and each quote also carries a ready-made twin of\nits URLs, built through them: put the `*_link` value on your page. A twin is absent where\nthere is nothing to link, and an account with no stored templates sees none. The `*_url`\nfields are unchanged.","operationId":"racing_best_odds_v1_racing_best_odds_get","parameters":[{"name":"hours_ahead","in":"query","required":false,"schema":{"type":"integer","maximum":168,"minimum":1,"description":"How many hours ahead to scan for priced races, from now. Default 6, which is what this endpoint has always used. The ceiling is your plan horizon, returned as X-Horizon-Hours: 24 Free and Hobby, 48 Standard, 168 Plus and above. Raising it past about 12h returns races no book has opened yet -- the first price lands a median 10.1h before the jump on AU thoroughbreds -- so a wide window is for building tomorrow's board, not for finding more prices today.","default":6,"title":"Hours Ahead"},"description":"How many hours ahead to scan for priced races, from now. Default 6, which is what this endpoint has always used. The ceiling is your plan horizon, returned as X-Horizon-Hours: 24 Free and Hobby, 48 Standard, 168 Plus and above. Raising it past about 12h returns races no book has opened yet -- the first price lands a median 10.1h before the jump on AU thoroughbreds -- so a wide window is for building tomorrow's board, not for finding more prices today."},{"name":"date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Single-day alias: date=YYYY-MM-DD scans that Australian Eastern meeting day instead of a rolling hours_ahead window. Mutually exclusive with hours_ahead, bounded by the same X-Horizon-Hours ceiling, forward-looking only. num_races still caps the rows returned, so pair a date with num_races=150 for a full card.","title":"Date"},"description":"Single-day alias: date=YYYY-MM-DD scans that Australian Eastern meeting day instead of a rolling hours_ahead window. Mutually exclusive with hours_ahead, bounded by the same X-Horizon-Hours ceiling, forward-looking only. num_races still caps the rows returned, so pair a date with num_races=150 for a full card."},{"name":"num_races","in":"query","required":false,"schema":{"type":"integer","maximum":150,"minimum":1,"description":"Up to 150. Pass 150 to compare every race on the day's card in one call instead of paging.","default":10,"title":"Num Races"},"description":"Up to 150. Pass 150 to compare every race on the day's card in one call instead of paging."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":150,"minimum":1,"description":"Alias for `num_races`, accepted because it is the near-universal REST spelling and because /v1/racing/results already takes it — learning `limit` on one racing endpoint and being refused on the next is the inconsistency this closes. Same bounds and same meaning; when both are given, `limit` wins. Measured 2026-09-14: callers sent `limit` here 28 times and were refused every time.","title":"Limit"},"description":"Alias for `num_races`, accepted because it is the near-universal REST spelling and because /v1/racing/results already takes it — learning `limit` on one racing endpoint and being refused on the next is the inconsistency this closes. Same bounds and same meaning; when both are given, `limit` wins. Measured 2026-09-14: callers sent `limit` here 28 times and were refused every time."},{"name":"categories","in":"query","required":false,"schema":{"type":"string","description":"horse,greyhound,harness or omit for all","title":"Categories"},"description":"horse,greyhound,harness or omit for all"},{"name":"category","in":"query","required":false,"schema":{"type":"string","description":"Alias of `categories`. Both spellings are accepted on every racing endpoint. An unknown value is a free 422 naming the valid ones.","title":"Category"},"description":"Alias of `categories`. Both spellings are accepted on every racing endpoint. An unknown value is a free 422 naming the valid ones."},{"name":"venue","in":"query","required":false,"schema":{"type":"string","description":"Venue names, comma-separated and case-insensitive (e.g. Randwick or Randwick,Flemington). Matched exactly against the race's `venue` after case folding, so pass the venue as this API spells it — read `venue` off an unfiltered call rather than guessing. Omit for every venue. Same rule and same spelling as /v1/racing/next-to-go. This narrows the window the endpoint already serves; it does not look beyond it, so a venue with no race inside the window returns an empty list rather than an error. There is no race-number filter: `venue` selects the meeting and you read `race_number` off the races it returns.","title":"Venue"},"description":"Venue names, comma-separated and case-insensitive (e.g. Randwick or Randwick,Flemington). Matched exactly against the race's `venue` after case folding, so pass the venue as this API spells it — read `venue` off an unfiltered call rather than guessing. Omit for every venue. Same rule and same spelling as /v1/racing/next-to-go. This narrows the window the endpoint already serves; it does not look beyond it, so a venue with no race inside the window returns an empty list rather than an error. There is no race-number filter: `venue` selects the meeting and you read `race_number` off the races it returns."},{"name":"bookmakers","in":"query","required":false,"schema":{"type":"string","description":"Restrict the comparison to these bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing.","title":"Bookmakers"},"description":"Restrict the comparison to these bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing."},{"name":"country","in":"query","required":false,"schema":{"type":"string","description":"ISO country codes, comma-separated (e.g. AU). Omit for every country. Foreign races are quoted by a median of one book, so a cross-book comparison on them compares nothing — Hong Kong (HK) is the exception among foreign countries, carrying several Australian books on the HKJC race nights, win and place, prices only. Pair with include_unresolved=true or you will silently lose races whose meeting is not confirmed yet.","title":"Country"},"description":"ISO country codes, comma-separated (e.g. AU). Omit for every country. Foreign races are quoted by a median of one book, so a cross-book comparison on them compares nothing — Hong Kong (HK) is the exception among foreign countries, carrying several Australian books on the HKJC race nights, win and place, prices only. Pair with include_unresolved=true or you will silently lose races whose meeting is not confirmed yet."},{"name":"include_unresolved","in":"query","required":false,"schema":{"type":"boolean","description":"Include races whose country is not resolved yet (country is null). OFF by default. Most of an Australian card is unresolved until the meeting is confirmed — 58.8% of horse races in a measured window — so country=AU alone can return far fewer races than are running, or none at all. Pass true to get them, and read `country` per race to see which are still unresolved.","default":false,"title":"Include Unresolved"},"description":"Include races whose country is not resolved yet (country is null). OFF by default. Most of an Australian card is unresolved until the meeting is confirmed — 58.8% of horse races in a measured window — so country=AU alone can return far fewer races than are running, or none at all. Pass true to get them, and read `country` per race to see which are still unresolved."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RacingBestOddsOut"},"title":"Response Racing Best Odds V1 Racing Best Odds Get"},"example":[{"books_compared":11,"cache_age_seconds":0,"cached":false,"category":"horse","country":"AU","data_age_seconds":41,"freshest_age_seconds":3,"market_percentage":106.4,"race_id":"race_123","race_name":"TAB ROSEBUD","race_number":8,"refresh_tier":"live","runners":[{"barrier":3,"best_place":{"age_seconds":4,"betslip_url":"https://www.tab.com.au/racing/2026-09-03/REDCLIFFE/RED/R/4?runner=5","bookmaker":"tab","last_update":"2026-08-15T05:44:21Z","price":2.15,"source_url":"https://www.tab.com.au/racing/2026-09-03/REDCLIFFE/RED/R/4","stale":false},"best_tote":{"bookmaker":"betright","price":7.1,"product":"RD+"},"best_win":{"age_seconds":6,"bookmaker":"betright","last_update":"2026-08-15T05:44:19Z","price":6.5,"refresh_tier":"live","source_url":"https://...","stale":false},"books_quoting":11,"books_quoting_place":8,"books_quoting_win":11,"horse_ref":"pe:chillygirl","jockey":"J Mcdonald","name":"Chilly Girl","number":7,"price_spread_pct":18.2,"trainer":"C Waller"}],"scratchings":[],"stale":false,"stale_bookmakers":[],"start_time":"2026-08-15T05:45:00Z","track_condition":"Good (4)","venue":"Rosehill"}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/best-odds/{sport_key}":{"get":{"tags":["Best Odds"],"summary":"Best price per selection","description":"Cost: 3 credits. Returns the best available price per selection for upcoming events, including arb_exists and arb_profit_pct when the best prices create an arbitrage margin.\n\nAn unknown `sport_key` returns 404 and costs nothing.","operationId":"best_odds_v1_best_odds__sport_key__get","parameters":[{"name":"sport_key","in":"path","required":true,"schema":{"type":"string","title":"Sport Key"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/BestOddsEventOut"},"title":"Response Best Odds V1 Best Odds  Sport Key  Get"},"example":[{"id":"6b1608c5-a9b8-4235-9a12-6db9656fef74","sport_key":"nrl","home_team":"Cronulla Sharks","away_team":"Nth Queensland Cowboys","commence_time":"2026-09-09T02:00:00Z","selections":[{"name":"Cronulla Sharks","best_price":1.45,"best_bookmaker":"betright","all_prices":[{"bookmaker":"sportsbet","price":1.44},{"bookmaker":"ladbrokes_au","price":1.44},{"bookmaker":"betright","price":1.45},{"bookmaker":"tab","price":1.43},{"bookmaker":"pointsbetau","price":1.42}]},{"name":"Nth Queensland Cowboys","best_price":2.85,"best_bookmaker":"pointsbetau","all_prices":[{"bookmaker":"pointsbetau","price":2.85},{"bookmaker":"tab","price":2.85},{"bookmaker":"sportsbet","price":2.81},{"bookmaker":"ladbrokes_au","price":2.8},{"bookmaker":"betright","price":2.75}]}],"arb_exists":false,"arb_profit_pct":0.0,"data_age_seconds":41,"freshest_age_seconds":12,"stale":false,"stale_bookmakers":[]}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/usage":{"get":{"tags":["Account"],"summary":"Check API usage","description":"Free — 0 credits deducted. Everything you need to decide about your plan BEFORE the\nAPI refuses you: spend by endpoint, burn rate, the date you are projected to run out, and\nthe tier that would clear it (with a headless checkout you can POST).\n\nTwo windows, and every field says which one it is in its name:\n\n* ALL TIME — `usage_by_endpoint`, `recent_activity`, `all_time`, `all_time_by_endpoint`.\n  Every call this key has ever made. This is the original meaning of `usage_by_endpoint`\n  and `recent_activity` and it has not changed. Because it spans every billing period the\n  key has lived through, it will normally be LARGER than `credits_used` — that is not a\n  discrepancy, the two are measuring different things.\n* CURRENT BILLING PERIOD — `usage_by_endpoint_period`, `recent_activity_period`, plus\n  `credits_used` / `credits_remaining` / `period_start` / `next_reset_at`. The credits in\n  `usage_by_endpoint_period` sum to `credits_used`. Use these to reconcile a bill.\n\n`all_time_by_endpoint` is an exact alias of `usage_by_endpoint`, kept because it shipped.\nNothing in the all-time pair changed on 2026-08-18; if you saw those two numbers step down\nearlier that day, this restored them.","operationId":"usage_v1_usage_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv)$","description":"`csv` returns the per-endpoint breakdown as a spreadsheet instead of the JSON document. Same rows, same numbers, no row cap. Free, like the JSON.","default":"json","title":"Format"},"description":"`csv` returns the per-endpoint breakdown as a spreadsheet instead of the JSON document. Same rows, same numbers, no row cap. Free, like the JSON."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageOut"},"example":{"all_time":{"calls":310,"credits":402,"endpoints":1},"all_time_by_endpoint":[{"calls":310,"credits":402,"endpoint":"/v1/sports"}],"burn_rate_per_day":6.0,"credits_limit":1000,"credits_remaining":958,"credits_used":42,"next_reset_at":"2026-08-01T00:00:00","period_start":"2026-07-01T00:00:00","plan":"free","plans":{"free":{"credits_per_month":500,"price_aud":0}},"projected_month_end_credits":186,"recent_activity":[{"credits":1,"endpoint":"/v1/sports","ts":"2026-06-07T02:20:00"}],"recent_activity_period":[{"credits":1,"endpoint":"/v1/sports","ts":"2026-06-07T02:20:00"}],"reset_at":"2026-07-01T00:00:00","upgrade":{"checkout":{"body":{"email":"you@example.com","plan":"hobby"},"endpoint":"https://api.puntersedge.online/v1/signup","method":"POST"},"covers_projected_need":true,"credits_per_month":7500,"price_aud":9,"recommended_plan":"hobby","sized_for_credits_per_month":186,"url":"https://puntersedge.online/api/pricing?plan=hobby&utm_source=api_usage"},"usage_by_endpoint":[{"calls":310,"credits":402,"endpoint":"/v1/sports"}],"usage_by_endpoint_period":[{"calls":42,"credits":42,"endpoint":"/v1/sports"}]}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/billing/portal":{"get":{"tags":["Account"],"summary":"Self-service billing portal link","description":"Cost: 0 credits. Returns a short-lived Stripe billing portal URL for this key's\nsubscription: update the card, download invoices, see what the next charge will be.\n\nTHE API KEY IS THE IDENTITY. No website account or separate login is needed: the key\nthat holds the subscription opens its portal, so a customer who subscribed through the API\ncan change a failing card, pull invoices or cancel without contacting us.\n\nThe returned URL is a BEARER link: anyone holding it can manage the subscription until it\nexpires. It is deliberately never logged, and callers should not paste it anywhere.","operationId":"billing_portal_v1_billing_portal_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"url":"https://billing.stripe.com/p/session/live_YWNjdF8xUHZ2c3Bsb0N0Nnp0dGMx","expires_at":1757212800,"note":"Single-use and short-lived. Treat it like a password: anyone with this link can manage the subscription."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["Account"],"summary":"Billing portal link (console; confirms your password)","description":"Same reply as GET /v1/billing/portal. Cost: 0 credits.","operationId":"billing_portal_reauth_v1_billing_portal_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/_ReauthIn"},{"type":"null"}],"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"url":"https://billing.stripe.com/p/session/live_YWNjdF8xVGZGQWdDQ1dlUVYyOWVU_secret_example","expires_at":1790000000,"note":"Single-use and short-lived. Treat it like a password: anyone with this link can manage the subscription."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/billing/upgrade-link":{"get":{"tags":["Account"],"summary":"A signed link to your prorated upgrade page","description":"Cost: 0 credits. For the signed-in account page (2026-09-28): /v1/billing/upgrade\nidentifies its reader by X-API-Key or by an emailed HMAC token, and a browser navigating to a\npage sends neither — so this mints the same 30-minute token for the caller's own customer\nid and returns the page URL. Without a subscription there is nothing to prorate and the\nanswer is the ordinary pricing page. Like the portal URL, treat the link as a bearer\ncredential for the subscription.","operationId":"billing_upgrade_link_for_caller_v1_billing_upgrade_link_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"url":"https://api.puntersedge.online/v1/billing/upgrade?token=Y3VzX2V4YW1wbGV8MTc5MDAwMDAwMA.3b1f0c9e7d2a4b6c8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d","prorated":true,"expires_in":1800,"note":"Expires in 30 minutes. Anyone holding this link can change the plan on this subscription — open it, do not share it."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["Account"],"summary":"Prorated upgrade link (console; confirms your password)","description":"Same reply as GET /v1/billing/upgrade-link. Cost: 0 credits.","operationId":"billing_upgrade_link_reauth_v1_billing_upgrade_link_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/_ReauthIn"},{"type":"null"}],"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"url":"https://api.puntersedge.online/v1/billing/upgrade?token=Y3VzX2V4YW1wbGV8MTc5MDAwMDAwMA.3b1f0c9e7d2a4b6c8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d","prorated":true,"expires_in":1800,"note":"Expires in 30 minutes. Anyone holding this link can change the plan on this subscription — open it, do not share it."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/health":{"get":{"tags":["Account"],"summary":"Connector health status","description":"Requires auth. Shows last_ok, status, records_written per connector.","operationId":"connector_health_v1_health_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ConnectorHealthOut"},"title":"Response Connector Health V1 Health Get"},"example":[{"connector":"sportsbet","last_ok":"2026-09-07T00:49:09","last_poll":"2026-09-07T00:49:09","status":"ok","records_written":163},{"connector":"tab","last_ok":"2026-09-07T00:49:09","last_poll":"2026-09-07T00:49:09","status":"ok","records_written":349},{"connector":"ladbrokes_au","last_ok":"2026-09-07T00:49:09","last_poll":"2026-09-07T00:49:09","status":"ok","records_written":375}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/uptime":{"get":{"tags":["Account"],"summary":"Public uptime stats","description":"Public uptime stats — no auth required, 0 credits.\n\n**`uptime_30d_pct` is nullable — type `number | null`, not `number`.** It is `null`, with `checks: 0`, whenever the monitor has written no samples inside the window: the buffer file is absent, or every sample in it predates the 30-day cutoff. A missing measurement is never reported as availability.\n\nGuard before any arithmetic or `>=` comparison:\n\n```python\npct = r.get(\"uptime_30d_pct\")\nok = pct is not None and pct >= 99.9   # not: r[\"uptime_30d_pct\"] >= 99.9\n```\n\nThe no-measurement body in full:\n\n```json\n{\n  \"uptime_30d_pct\": null,\n  \"checks\": 0,\n  \"status\": \"no data yet\",\n  \"probe_target\": \"https://puntersedge.online/ping\",\n  \"measures\": \"puntersedge.online (website), not api.puntersedge.online\",\n  \"note\": \"...\"\n}\n```\n\n`checks == 0` tests the same condition without touching the nullable field.","operationId":"uptime_stats_v1_uptime_get","responses":{"200":{"description":"Uptime stats. `uptime_30d_pct` is null — NOT 100 and not absent — when the window holds no samples.","content":{"application/json":{"schema":{"properties":{"uptime_30d_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Percentage of successful probes in the window, or NULL when the window contains no samples at all. Null means unmeasured, not 100. Guard before arithmetic or a >= comparison.","examples":[99.99,null]},"checks":{"type":"integer","description":"Samples in the window. 0 is the non-null sentinel for the same condition that makes uptime_30d_pct null, and is always safe to compare."},"status":{"type":"string","description":"Present only in the no-measurement case: 'no data yet'."},"last_check":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"last_status":{"type":"string","enum":["ok","fail"]},"monitor_stale":{"type":"boolean","description":"True when the newest sample is over 15 minutes old."},"last_check_age_min":{"anyOf":[{"type":"number"},{"type":"null"}]},"window_first_check":{"type":"string","format":"date-time"},"window_span_days":{"type":"number","description":"Span actually observed, which equals 30 only if the probe never missed a sample."},"probe_target":{"type":"string"},"measures":{"type":"string"},"note":{"type":"string"}},"type":"object","required":["uptime_30d_pct","checks","probe_target","measures","note"]},"example":{"uptime_30d_pct":99.99,"checks":8640,"last_status":"ok","monitor_stale":false,"window_span_days":30.0,"probe_target":"https://puntersedge.online/ping"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/keys":{"post":{"tags":["Account"],"summary":"Create a new API key (admin only)","description":"Create a new API key. Admin only, raw key only (SEC-4: never a console session).","operationId":"create_key_v1_keys_post","parameters":[{"name":"label","in":"query","required":false,"schema":{"type":"string","default":"My Key","title":"Label"}},{"name":"plan","in":"query","required":false,"schema":{"type":"string","default":"free","title":"Plan"}},{"name":"email","in":"query","required":false,"schema":{"type":"string","title":"Email"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"key":"pe_4f9c2a7e1b8d3c6a5e0f7b2d9c4a1e8f","plan":"free","label":"My Key","credits_limit":500}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/keys/rotate":{"post":{"tags":["Account"],"summary":"Rotate your API key","description":"Issue a new API key. The old key keeps working for a short grace window so a\nmulti-process caller can roll over without an outage; pass immediate=true to kill it now.\nCost: 0 credits.","operationId":"rotate_key_v1_keys_rotate_post","parameters":[{"name":"immediate","in":"query","required":false,"schema":{"type":"boolean","description":"Revoke the old key NOW instead of after the grace window. Use this when rotating because the key leaked — a grace window keeps a compromised credential alive and is the wrong default for that case.","default":false,"title":"Immediate"},"description":"Revoke the old key NOW instead of after the grace window. Use this when rotating because the key leaked — a grace window keeps a compromised credential alive and is the wrong default for that case."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"new_key":"pe_4f9c2a7e1b8d3c6a5e0f7b2d9c4a1e8f","old_key_hint":"pe_9b21c0…","key_version":2,"rotated_at":"2026-09-07T00:52:10Z","previous_key_valid_until":"2026-09-08T00:52:10Z","message":"Key rotated. The previous key keeps working for 24h (until previous_key_valid_until) so you can roll over without downtime. Re-run with immediate=true if the old key may have leaked."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/keys/resend":{"post":{"tags":["Account"],"summary":"Email yourself a fresh API key","description":"Rotate this account's key and mail the new one to its own verified address.\n\nKeys are sha256-hashed on insert and cannot be read back, /v1/keys/rotate needs the key\nyou have lost, and the only regenerate UI is gated behind plan == \"members\" (the tips\nsubscription), which no API customer holds. So there was no self-serve recovery at all:\nto 2026-08-25, 22 distinct IPs reached /account against 45 that minted a key.\n\n⛔ ALWAYS 202, WHATEVER HAPPENS — unknown address, unverified account, rate-limited,\nmail failure. The response is byte-identical in every case. An endpoint that says\n\"no such account\" is an account-existence oracle for anyone with a list of emails, and\nthis one names a mailbox that receives credentials.\n\nThe key goes ONLY to the address already stored and verified on the row. Nothing the\ncaller sends is used as a destination, so submitting someone else's address mails that\nperson their own key and tells the requester nothing.\n\nCost: 0 credits. Unauthenticated by necessity — the credential is the thing that is lost.\n\n⚠️ KNOWN, ACCEPTED: response time is longer when the address does exist, because the send\nis awaited (see below). That leaks account existence to an attacker who can measure it.\nAccepted deliberately — the alternative is committing a rotation before knowing the mail\nwent, which strands a real customer with a dead key. A timing signal on account existence\nis worth less than a customer's working credential, and the per-IP cap bounds sampling.","operationId":"resend_key_v1_keys_resend_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyResendIn"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"status":"accepted","message":"If that address has a verified PuntersEdge API account, a new key is on its way to it. Your previous key keeps working for 24 hours, so anything running has time to roll over."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/keys/info":{"get":{"tags":["Account"],"summary":"API key metadata","description":"Returns metadata about the authenticated key including plan, credits, org, and last-used info.\nCredits are the POOL's when this is a team key (its own counter is never charged);\n`team` describes the pool on accounts that hold or may hold team keys.","operationId":"key_info_v1_keys_info_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"key_hint":"pe_4f9c2a…","plan":"plus","org_id":null,"org_name":null,"credits_used":1240,"credits_limit":20000,"credits_remaining":18760,"key_version":2,"last_used_at":"2026-09-07T00:51:58Z","last_used_ip":"203.0.113.42","ip_whitelist":[],"created_at":"2026-08-19T06:14:02Z","rotated_at":"2026-09-07T00:52:10Z"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/account/events":{"get":{"tags":["Account"],"summary":"Your account's change history","description":"Cost: 0 credits. Every recorded change to this account's plan, credits, keys and\nswitches, newest first: who made it (a billing event, you in the console or with your key,\nour support team, or a scheduled job), what it was, and the account's state before and after.\n\nA primary key sees its own history and its team keys'; a team key sees its own. Key\nmaterial, password and email are never part of a row. The IP whitelist appears as a count\nof rules, not the addresses.","operationId":"account_events_v1_account_events_get","parameters":[{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Only changes at or after this time (ISO 8601; UTC when no offset is given).","title":"Since"},"description":"Only changes at or after this time (ISO 8601; UTC when no offset is given)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Most rows returned, newest first.","default":50,"title":"Limit"},"description":"Most rows returned, newest first."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountEventsOut"},"example":{"available":true,"count":2,"events":[{"action":"plan_change","actor":"stripe","after":{"active":true,"credits_used":18210,"ip_whitelist":0,"overage_enabled":true,"plan":"pro_plus"},"api_key_id":"3f1c9a52-7d0e-4b8a-9c61-2e5f0d8b7a14","at":"2026-10-13T04:12:09.512331+00:00","before":{"active":true,"credits_used":18210,"ip_whitelist":0,"overage_enabled":false,"plan":"plus"},"id":1042,"request_id":"evt_1Q2w3E4r5T6y7U8i9O0p","source":"stripe:customer.subscription.updated"},{"action":"key_rotate","actor":"console","api_key_id":"3f1c9a52-7d0e-4b8a-9c61-2e5f0d8b7a14","at":"2026-10-02T22:40:51.004912+00:00","id":977,"note":"key_version 2","request_id":"6b0d8f3e-21c4-4a9e-b7d2-5e8c1a0f9b33","source":"POST /v1/keys/rotate"}]}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/keys/ip-whitelist":{"post":{"tags":["Account"],"summary":"Set IP whitelist for your key","description":"Cost: 0 credits. Restrict this key to specific IPs or CIDR ranges. Other plans get 403.\nPass an empty list to remove restrictions.","operationId":"set_ip_whitelist_v1_keys_ip_whitelist_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"description":"IPv4/IPv6 addresses or CIDR ranges. A bare address is stored as a /32 (or /128). Send an empty list to remove all restrictions.","examples":[["203.0.113.7/32","198.51.100.0/24"]],"title":"Cidrs"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"ip_whitelist":["203.0.113.0/24","198.51.100.7"],"message":"Whitelist updated — 2 rule(s) active."}}}},"403":{"description":"IP whitelisting is available on Business, Platform, Scale and Enterprise plans only."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/usage/analytics":{"get":{"tags":["Account"],"summary":"Usage breakdown by endpoint","description":"Cost: 0 credits. Credit usage grouped by endpoint.\n\n`period=billing` exists because the rolling window never lines up with what you are\nactually billed on: the credit counter is zeroed at 00:00 UTC on the 1st of each month,\nso on the 18th a 30-day rolling total spans two allowances\nand cannot be reconciled against credits_used. The default stays `rolling` so no existing\nclient changes behaviour.\n\n⚠️ `period_days` is an int under `period=rolling` (it echoes `days`, unchanged) and a\nfloat under `period=billing`, where it is the measured length of the current period\nrather than a requested window — e.g. 31.0 for a period that started on the 1st. The\nwidening only reaches callers who opt in by passing the new parameter; `period_start` and\n`period_end` are always returned so the window never has to be inferred from it.","operationId":"usage_analytics_v1_usage_analytics_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":90,"minimum":1,"description":"Rolling window length. Ignored when period=billing.","default":30,"title":"Days"},"description":"Rolling window length. Ignored when period=billing."},{"name":"period","in":"query","required":false,"schema":{"type":"string","pattern":"^(rolling|billing)$","description":"rolling = the last `days` days (default, unchanged). billing = the CURRENT billing period, so the totals here match credits_used and credits_remaining exactly.","default":"rolling","title":"Period"},"description":"rolling = the last `days` days (default, unchanged). billing = the CURRENT billing period, so the totals here match credits_used and credits_remaining exactly."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"period":"rolling","period_days":30,"period_start":"2026-08-08T00:52:10","period_end":"2026-09-07T00:52:10","credits_limit":20000,"credits_used_this_period":1240,"credits_remaining":18760,"total_calls":1102,"total_credits":1240,"by_endpoint":[{"endpoint":"/v1/racing/next-to-go","calls":611,"credits":611},{"endpoint":"/v1/racing/best-odds","calls":302,"credits":302},{"endpoint":"/v1/racing/price-history","calls":108,"credits":216},{"endpoint":"/v1/racing/results","calls":81,"credits":81}]}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/signup":{"post":{"tags":["Signup"],"summary":"Create API key or start paid checkout","description":"Sign up for an API key.\n- free: returns key immediately\n- paid: returns Stripe checkout URL, key created on webhook\n\nPlatform and Scale (`platform`, `unlimited`) are arranged with us rather than bought here:\nthose plans return 409 with a `sales_url` to https://puntersedge.online/contact/sales (a\nbrowser sending `Accept: text/html` is redirected there with 303).","operationId":"signup_v1_signup_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"status":"pending_verification","plan":"free","api_key":null,"message":"Almost there — check your inbox and click the verification link to activate your free API key."}}}},"409":{"description":"Platform and Scale are arranged with us; the body's `sales_url` is where to start."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/arb/sports":{"get":{"tags":["Arb & Value"],"summary":"Sports arb scanner","description":"Cost: 3 credits. Scans upcoming sports markets for true arbitrage and best-odds overlay opportunities, returning arb_pct, optimal stakes and per-selection price comparisons Every all_prices entry carries updated_at / age_seconds (when we last read that bookmaker), price_changed_at / unchanged_seconds (when its price last changed) and stale_suspect: true when a price has not moved for an hour while two or more other books moved away from it, in which case it is listed but never the best price.","operationId":"sports_arb_v1_arb_sports_get","parameters":[{"name":"sport_key","in":"query","required":false,"schema":{"type":"string","description":"Filter by sport e.g. afl, nrl","title":"Sport Key"},"description":"Filter by sport e.g. afl, nrl"},{"name":"maxAgeMinutes","in":"query","required":false,"schema":{"type":"integer","maximum":1440,"minimum":5,"description":"Exclude bookmaker markets older than this many minutes. Same bound and default as /v1/sports/{sport_key}/odds, so an arb can never be built on a price that endpoint refuses to show.","default":360,"title":"Maxageminutes"},"description":"Exclude bookmaker markets older than this many minutes. Same bound and default as /v1/sports/{sport_key}/odds, so an arb can never be built on a price that endpoint refuses to show."},{"name":"min_profit_pct","in":"query","required":false,"schema":{"type":"number","description":"Min guaranteed profit % (0 = show all value)","default":0.0,"title":"Min Profit Pct"},"description":"Min guaranteed profit % (0 = show all value)"},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SportsArbOut"},"title":"Response Sports Arb V1 Arb Sports Get"},"example":[{"event_id":"6b1608c5-a9b8-4235-9a12-6db9656fef74","sport_key":"nrl","home_team":"Cronulla Sharks","away_team":"Nth Queensland Cowboys","commence_time":"2026-09-09T02:00:00Z","is_arb":false,"arb_pct":0.0,"optimal_stakes":[],"max_overlay_pct":1.4,"selections":[{"name":"Cronulla Sharks","best_price":1.45,"best_bookmaker":"betright","avg_price":1.436,"overlay_pct":1.0,"all_prices":[{"bookmaker":"sportsbet","price":1.44},{"bookmaker":"ladbrokes_au","price":1.44},{"bookmaker":"betright","price":1.45},{"bookmaker":"tab","price":1.43},{"bookmaker":"pointsbetau","price":1.42}]},{"name":"Nth Queensland Cowboys","best_price":2.85,"best_bookmaker":"pointsbetau","avg_price":2.812,"overlay_pct":1.4,"all_prices":[{"bookmaker":"pointsbetau","price":2.85},{"bookmaker":"tab","price":2.85},{"bookmaker":"sportsbet","price":2.81},{"bookmaker":"ladbrokes_au","price":2.8},{"bookmaker":"betright","price":2.75}]}]}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/arb/best-prices":{"get":{"tags":["Arb & Value"],"summary":"Best prices comparison","description":"Cost: 2 credits. Returns the best available price per selection plus full bookmaker comparison, worst price, average price, spread and overlay percentage. Every all_prices entry carries updated_at / age_seconds (when we last read that bookmaker), price_changed_at / unchanged_seconds (when its price last changed) and stale_suspect: true when a price has not moved for an hour while two or more other books moved away from it, in which case it is listed but never the best price.","operationId":"best_prices_v1_arb_best_prices_get","parameters":[{"name":"maxAgeMinutes","in":"query","required":false,"schema":{"type":"integer","maximum":1440,"minimum":5,"description":"Exclude bookmaker markets older than this many minutes. Same bound and default as /v1/sports/{sport_key}/odds, so an arb can never be built on a price that endpoint refuses to show.","default":360,"title":"Maxageminutes"},"description":"Exclude bookmaker markets older than this many minutes. Same bound and default as /v1/sports/{sport_key}/odds, so an arb can never be built on a price that endpoint refuses to show."},{"name":"sport_key","in":"query","required":false,"schema":{"type":"string","default":"nrl","title":"Sport Key"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/BestPricesOut"},"title":"Response Best Prices V1 Arb Best Prices Get"},"example":[{"id":"6b1608c5-a9b8-4235-9a12-6db9656fef74","sport_key":"nrl","home_team":"Cronulla Sharks","away_team":"Nth Queensland Cowboys","commence_time":"2026-09-09T02:00:00Z","selections":[{"name":"Nth Queensland Cowboys","best_price":2.85,"best_bookmaker":"pointsbetau","worst_price":2.75,"avg_price":2.812,"price_spread":0.1,"overlay_pct":1.4,"all_prices":[{"bookmaker":"pointsbetau","price":2.85},{"bookmaker":"tab","price":2.85},{"bookmaker":"sportsbet","price":2.81},{"bookmaker":"ladbrokes_au","price":2.8},{"bookmaker":"betright","price":2.75}]},{"name":"Cronulla Sharks","best_price":1.45,"best_bookmaker":"betright","worst_price":1.42,"avg_price":1.436,"price_spread":0.03,"overlay_pct":1.0,"all_prices":[{"bookmaker":"sportsbet","price":1.44},{"bookmaker":"ladbrokes_au","price":1.44},{"bookmaker":"betright","price":1.45},{"bookmaker":"tab","price":1.43},{"bookmaker":"pointsbetau","price":1.42}]}]}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/arb/lines":{"get":{"tags":["Arb & Value"],"summary":"Spreads/totals line arb","description":"Cost: 3 credits. Line-matched arbitrage on spreads and totals: backs the two opposing sides\nat complementary lines across books. Returns the best complementary pair per event/market with a\nguaranteed-profit (is_arb) flag; is_middle marks overlapping lines where both sides can win.","operationId":"lines_arb_v1_arb_lines_get","parameters":[{"name":"sport_key","in":"query","required":false,"schema":{"type":"string","description":"Filter by sport e.g. afl, nrl","title":"Sport Key"},"description":"Filter by sport e.g. afl, nrl"},{"name":"maxAgeMinutes","in":"query","required":false,"schema":{"type":"integer","maximum":1440,"minimum":5,"description":"Exclude bookmaker markets older than this many minutes. Same bound and default as /v1/sports/{sport_key}/odds, so an arb can never be built on a price that endpoint refuses to show.","default":360,"title":"Maxageminutes"},"description":"Exclude bookmaker markets older than this many minutes. Same bound and default as /v1/sports/{sport_key}/odds, so an arb can never be built on a price that endpoint refuses to show."},{"name":"min_profit_pct","in":"query","required":false,"schema":{"type":"number","description":"Min guaranteed profit % (0 = also show the best line comparison when not an arb)","default":0.0,"title":"Min Profit Pct"},"description":"Min guaranteed profit % (0 = also show the best line comparison when not an arb)"},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/LinesArbOut"},"title":"Response Lines Arb V1 Arb Lines Get"},"example":[{"event_id":"6b1608c5-a9b8-4235-9a12-6db9656fef74","sport_key":"nrl","home_team":"Cronulla Sharks","away_team":"Nth Queensland Cowboys","commence_time":"2026-09-09T02:00:00Z","market_type":"totals","opportunities":[{"side_a":{"name":"Over","point":47.5,"price":2.05,"bookmaker":"pointsbetau"},"side_b":{"name":"Under","point":48.5,"price":2.02,"bookmaker":"tab"},"is_arb":true,"is_middle":true,"arb_pct":1.7,"optimal_stakes":[{"name":"Over","point":47.5,"stake":49.63,"bookmaker":"pointsbetau","profit_if_wins":1.74},{"name":"Under","point":48.5,"stake":50.37,"bookmaker":"tab","profit_if_wins":1.75}]}]}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/webhooks":{"post":{"tags":["Webhooks"],"summary":"Create Webhook","description":"Subscribe an HTTPS endpoint to signed event pushes.\n\nEvents that fire on their own: `race.odds_open` (a race's first bookmaker prices landed),\n`race.result` (a race's result went final, from 2026-10-13) and `arb.opportunity` (a\ncross-book arb opened). `odds.change` is the event POST /v1/webhooks/{id}/test sends and\nnothing else does. An unknown event name is a 422 that lists the events that fire, and\nnothing is created; `market.suspend`, withdrawn on 2026-10-13 because it never fired, is\nrefused the same way.\n\nEach delivery is a POST of the JSON payload with `X-PuntersEdge-Event` naming the event and\n`X-Webhook-Signature: sha256=<hex>`, the HMAC-SHA256 of the raw body keyed with the `secret`\nin this response. The secret is returned once, here (POST /v1/webhooks/{id}/rotate-secret\nissues a new one when the delivery queue is enabled).\n\nDelivery is at-least-once: the same event can arrive more than once, so deduplicate on `event_id` (in the body, and sent as the `X-PuntersEdge-Id` and `Idempotency-Key` headers). There is no ordering guarantee between events. Each delivery carries `X-PuntersEdge-Timestamp`, `X-PuntersEdge-Attempt` and two signatures: `X-Webhook-Signature: sha256=<hex>` (HMAC-SHA256 of the raw body with your secret) and `X-PuntersEdge-Signature: t=<unix seconds>,v1=<hex>` (HMAC-SHA256 of `<t>.<raw body>`; refuse a `t` more than 5 minutes from now to block replays). Any 2xx answer is success. A 5xx, 408, 429, timeout or connection failure is retried after 1 min, 5 min, 30 min, 2 h, 6 h (6 tries in all, about 8.5 hours), honouring `Retry-After` up to the next step; any other answer is final. After 10 consecutive undeliverable events, or a day of continuous failure, the subscription is paused and the owner is emailed; POST /v1/webhooks/{id}/resume restarts it with the same secret, and POST /v1/webhooks/{id}/replay re-sends what was missed. The delivery log returns the newest 50 rows unless `limit` says otherwise.","operationId":"create_webhook_v1_webhooks_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponse"},"example":{"id":"wh_7c1d2e9a4b3f","url":"https://example.com/hooks/puntersedge","events":["arb.opportunity","race.odds_open","race.result"],"min_edge_pct":1.5,"sports":["racing_horse","racing_greyhound","nrl"],"is_active":true,"created_at":"2026-09-07T00:52:10","last_delivery_at":null,"failure_count":0,"secret":"whsec_EXAMPLE_ONLY_shown_once_at_creation","delivery_entitled":true,"delivery_paused_reason":null}}}},"403":{"description":"Webhook subscriptions are available on Plus, Business, Platform, Scale and Enterprise plans only. Legacy Growth and Pro keys also qualify."},"409":{"description":"This key already has 5 subscriptions."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"get":{"tags":["Webhooks"],"summary":"List Webhooks","description":"Delivery is at-least-once: the same event can arrive more than once, so deduplicate on `event_id` (in the body, and sent as the `X-PuntersEdge-Id` and `Idempotency-Key` headers). There is no ordering guarantee between events. Each delivery carries `X-PuntersEdge-Timestamp`, `X-PuntersEdge-Attempt` and two signatures: `X-Webhook-Signature: sha256=<hex>` (HMAC-SHA256 of the raw body with your secret) and `X-PuntersEdge-Signature: t=<unix seconds>,v1=<hex>` (HMAC-SHA256 of `<t>.<raw body>`; refuse a `t` more than 5 minutes from now to block replays). Any 2xx answer is success. A 5xx, 408, 429, timeout or connection failure is retried after 1 min, 5 min, 30 min, 2 h, 6 h (6 tries in all, about 8.5 hours), honouring `Retry-After` up to the next step; any other answer is final. After 10 consecutive undeliverable events, or a day of continuous failure, the subscription is paused and the owner is emailed; POST /v1/webhooks/{id}/resume restarts it with the same secret, and POST /v1/webhooks/{id}/replay re-sends what was missed. The delivery log returns the newest 50 rows unless `limit` says otherwise.","operationId":"list_webhooks_v1_webhooks_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WebhookResponse"},"title":"Response List Webhooks V1 Webhooks Get"},"example":[{"id":"wh_7c1d2e9a4b3f","url":"https://example.com/hooks/puntersedge","events":["arb.opportunity","race.odds_open","race.result"],"min_edge_pct":1.5,"sports":["racing_horse","racing_greyhound","nrl"],"is_active":true,"created_at":"2026-09-07T00:52:10","last_delivery_at":"2026-09-07T00:57:15","failure_count":0,"secret":null,"delivery_entitled":true,"delivery_paused_reason":null}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/webhooks/{webhook_id}":{"delete":{"tags":["Webhooks"],"summary":"Delete Webhook","description":"Delivery is at-least-once: the same event can arrive more than once, so deduplicate on `event_id` (in the body, and sent as the `X-PuntersEdge-Id` and `Idempotency-Key` headers). There is no ordering guarantee between events. Each delivery carries `X-PuntersEdge-Timestamp`, `X-PuntersEdge-Attempt` and two signatures: `X-Webhook-Signature: sha256=<hex>` (HMAC-SHA256 of the raw body with your secret) and `X-PuntersEdge-Signature: t=<unix seconds>,v1=<hex>` (HMAC-SHA256 of `<t>.<raw body>`; refuse a `t` more than 5 minutes from now to block replays). Any 2xx answer is success. A 5xx, 408, 429, timeout or connection failure is retried after 1 min, 5 min, 30 min, 2 h, 6 h (6 tries in all, about 8.5 hours), honouring `Retry-After` up to the next step; any other answer is final. After 10 consecutive undeliverable events, or a day of continuous failure, the subscription is paused and the owner is emailed; POST /v1/webhooks/{id}/resume restarts it with the same secret, and POST /v1/webhooks/{id}/replay re-sends what was missed. The delivery log returns the newest 50 rows unless `limit` says otherwise.","operationId":"delete_webhook_v1_webhooks__webhook_id__delete","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"deleted":true,"id":"wh_7c1d2e9a4b3f"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/webhooks/{webhook_id}/deliveries":{"get":{"tags":["Webhooks"],"summary":"List Webhook Deliveries","description":"Your subscription's delivery log, newest first.\n\nWith no parameters this is the newest 50 rows, exactly as it has always been. `since`,\n`status`, `limit` (up to 200) and `cursor` page through older rows: when a page is full the\nresponse carries an `X-Next-Cursor` header to pass back as `cursor`. `include=attempts` adds\nan `attempts` list to each row (time, status code, error and latency of every POST), filled\nfor rows written by the delivery queue.\n\nDelivery is at-least-once: the same event can arrive more than once, so deduplicate on `event_id` (in the body, and sent as the `X-PuntersEdge-Id` and `Idempotency-Key` headers). There is no ordering guarantee between events. Each delivery carries `X-PuntersEdge-Timestamp`, `X-PuntersEdge-Attempt` and two signatures: `X-Webhook-Signature: sha256=<hex>` (HMAC-SHA256 of the raw body with your secret) and `X-PuntersEdge-Signature: t=<unix seconds>,v1=<hex>` (HMAC-SHA256 of `<t>.<raw body>`; refuse a `t` more than 5 minutes from now to block replays). Any 2xx answer is success. A 5xx, 408, 429, timeout or connection failure is retried after 1 min, 5 min, 30 min, 2 h, 6 h (6 tries in all, about 8.5 hours), honouring `Retry-After` up to the next step; any other answer is final. After 10 consecutive undeliverable events, or a day of continuous failure, the subscription is paused and the owner is emailed; POST /v1/webhooks/{id}/resume restarts it with the same secret, and POST /v1/webhooks/{id}/replay re-sends what was missed. The delivery log returns the newest 50 rows unless `limit` says otherwise.","operationId":"list_webhook_deliveries_v1_webhooks__webhook_id__deliveries_get","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only rows recorded at or after this ISO-8601 time, e.g. 2026-10-13T06:00:00Z.","title":"Since"},"description":"Only rows recorded at or after this ISO-8601 time, e.g. 2026-10-13T06:00:00Z."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(delivered|failed|pending)$"},{"type":"null"}],"description":"Only rows with this status.","title":"Status"},"description":"Only rows with this status."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Rows per page, newest first (max 200).","default":50,"title":"Limit"},"description":"Rows per page, newest first (max 200)."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `X-Next-Cursor` response header of the previous page, to read the next one.","title":"Cursor"},"description":"The `X-Next-Cursor` response header of the previous page, to read the next one."},{"name":"include","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^attempts$"},{"type":"null"}],"description":"`attempts` adds each row's per-attempt detail.","title":"Include"},"description":"`attempts` adds each row's per-attempt detail."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDeliveryResponse"},"title":"Response List Webhook Deliveries V1 Webhooks  Webhook Id  Deliveries Get"},"example":[{"id":"dl_9c4b7e2a1f6d","webhook_id":"wh_7c1d2e9a4b3f","event_type":"race.result","payload":{"event":"race.result","race_id":"6761176a-6f6e-4ce6-bf81-6a1bf81aa72e","result_id":"f59b8244-3734-46f1-88de-f362e3c76197","venue":"Bairnsdale","race_number":8,"start_time":"2026-10-12T06:55:00Z","country":"AU","category":"horse","sport":"racing_horse","status":"final","placings":[{"position":1,"name":"Torn","number":10,"horse_ref":"pe:torn"},{"position":2,"name":"Spirit Angel","number":17,"horse_ref":"pe:spiritangel"},{"position":3,"name":"Great Prosperity","number":4,"horse_ref":"pe:greatprosperity"},{"position":4,"name":"Rich King","number":3,"horse_ref":"pe:richking"}],"dividends":{"straight":[{"position":1,"number":10,"name":"Torn","market":"WIN","product":"FIXED","amount":7.0},{"position":1,"number":10,"name":"Torn","market":"WIN","product":"RD+","amount":8.98},{"position":1,"number":10,"name":"Torn","market":"WIN","product":"BT+SP","amount":8.6}],"exotics":[{"type":"Quinella","code":"QN","selection":"10,17","product":"VIC","amount":30.9},{"type":"Exacta","code":"EX","selection":"10,17","product":"VIC","amount":77.2}],"multi_race":[{"code":"RC-QUADDIE","type":"Quaddie","product":"VIC","selection":"4,1,1,10","amount":2140.4,"legs":[5,6,7,8]}],"straight_types":["FIXED","RD+","BT+SP","MIDDIV"]},"source":"betright","sent_at":"2026-10-12T07:35:00.912345Z"},"status":"delivered","attempt_count":1,"status_code":200,"error":null,"delivered_at":"2026-10-12T07:35:01.208114"},{"id":"dl_5e2c8a1f9b3d","webhook_id":"wh_7c1d2e9a4b3f","event_type":"arb.opportunity","payload":{"sport":"nrl","event_name":"Melbourne Storm v Penrith Panthers","edge_pct":2.97,"legs":[{"bookmaker":"pointsbetau","selection":"Melbourne Storm","odds":2.1},{"bookmaker":"sportsbet","selection":"Penrith Panthers","odds":2.02}]},"status":"delivered","attempt_count":1,"status_code":200,"error":null,"delivered_at":"2026-09-07T00:57:15.331905"},{"id":"dl_1a7f3c0e2d9b","webhook_id":"wh_7c1d2e9a4b3f","event_type":"odds.change","payload":{"event":"odds.change","test":true,"sent_at":"2026-09-07T00:53:02.118240Z","message":"PuntersEdge webhook test"},"status":"delivered","attempt_count":1,"status_code":200,"error":null,"delivered_at":"2026-09-07T00:53:02.410551"}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/webhooks/{webhook_id}/test":{"post":{"tags":["Webhooks"],"summary":"Test Webhook","description":"Send one signed `odds.change` test event to the subscription now and report the result.\n\nDelivery is at-least-once: the same event can arrive more than once, so deduplicate on `event_id` (in the body, and sent as the `X-PuntersEdge-Id` and `Idempotency-Key` headers). There is no ordering guarantee between events. Each delivery carries `X-PuntersEdge-Timestamp`, `X-PuntersEdge-Attempt` and two signatures: `X-Webhook-Signature: sha256=<hex>` (HMAC-SHA256 of the raw body with your secret) and `X-PuntersEdge-Signature: t=<unix seconds>,v1=<hex>` (HMAC-SHA256 of `<t>.<raw body>`; refuse a `t` more than 5 minutes from now to block replays). Any 2xx answer is success. A 5xx, 408, 429, timeout or connection failure is retried after 1 min, 5 min, 30 min, 2 h, 6 h (6 tries in all, about 8.5 hours), honouring `Retry-After` up to the next step; any other answer is final. After 10 consecutive undeliverable events, or a day of continuous failure, the subscription is paused and the owner is emailed; POST /v1/webhooks/{id}/resume restarts it with the same secret, and POST /v1/webhooks/{id}/replay re-sends what was missed. The delivery log returns the newest 50 rows unless `limit` says otherwise.","operationId":"test_webhook_v1_webhooks__webhook_id__test_post","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"delivered":true,"status_code":200,"error":null,"delivery_id":"dl_1a7f3c0e2d9b","webhook_active":true,"event_id":"0b7e3f52-9c41-4d8a-a6e2-5f1c9d3b7a40","latency_ms":[212]}}}},"403":{"description":"Webhook subscriptions are available on Plus, Business, Platform, Scale and Enterprise plans only. Legacy Growth and Pro keys also qualify."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/webhooks/{webhook_id}/replay":{"post":{"tags":["Webhooks"],"summary":"Replay Webhook","description":"Queue past events for this subscription again, with their original `event_id`.\n\nRe-sent deliveries carry `X-PuntersEdge-Replay: true`, restart the retry schedule, and keep\nthe `event_id` they first had, so a receiver that deduplicates on it ignores what it already\nprocessed. At most 1,000 events per call and one call per key per minute; an event already\nwaiting to be sent is not queued twice.\n\nDelivery is at-least-once: the same event can arrive more than once, so deduplicate on `event_id` (in the body, and sent as the `X-PuntersEdge-Id` and `Idempotency-Key` headers). There is no ordering guarantee between events. Each delivery carries `X-PuntersEdge-Timestamp`, `X-PuntersEdge-Attempt` and two signatures: `X-Webhook-Signature: sha256=<hex>` (HMAC-SHA256 of the raw body with your secret) and `X-PuntersEdge-Signature: t=<unix seconds>,v1=<hex>` (HMAC-SHA256 of `<t>.<raw body>`; refuse a `t` more than 5 minutes from now to block replays). Any 2xx answer is success. A 5xx, 408, 429, timeout or connection failure is retried after 1 min, 5 min, 30 min, 2 h, 6 h (6 tries in all, about 8.5 hours), honouring `Retry-After` up to the next step; any other answer is final. After 10 consecutive undeliverable events, or a day of continuous failure, the subscription is paused and the owner is emailed; POST /v1/webhooks/{id}/resume restarts it with the same secret, and POST /v1/webhooks/{id}/replay re-sends what was missed. The delivery log returns the newest 50 rows unless `limit` says otherwise.","operationId":"replay_webhook_v1_webhooks__webhook_id__replay_post","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}},{"name":"since","in":"query","required":true,"schema":{"type":"string","description":"Replay events first queued at or after this ISO-8601 time.","title":"Since"},"description":"Replay events first queued at or after this ISO-8601 time."},{"name":"status","in":"query","required":false,"schema":{"type":"string","pattern":"^(dead|failed|all)$","description":"`dead`: retries exhausted or refused. `failed`: dead plus events held while the subscription was paused. `all`: those plus events already delivered.","default":"dead","title":"Status"},"description":"`dead`: retries exhausted or refused. `failed`: dead plus events held while the subscription was paused. `all`: those plus events already delivered."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookReplayResponse"},"example":{"id":"wh_7c1d2e9a4b3f","requeued":37,"since":"2026-10-12T06:00:00Z","status":"failed","max_per_call":1000}}}},"403":{"description":"Webhook subscriptions are available on Plus, Business, Platform, Scale and Enterprise plans only. Legacy Growth and Pro keys also qualify."},"429":{"description":"One replay per key per minute."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/webhooks/{webhook_id}/resume":{"post":{"tags":["Webhooks"],"summary":"Resume Webhook","description":"Restart deliveries to a paused (or deactivated) subscription, keeping its secret.\n\nClears the pause, resets the failure count and queues one `odds.change` probe event so you\ncan see the endpoint answer. Events that were held while paused are not re-sent by this;\nuse POST /v1/webhooks/{id}/replay with `status=failed` for those.\n\nDelivery is at-least-once: the same event can arrive more than once, so deduplicate on `event_id` (in the body, and sent as the `X-PuntersEdge-Id` and `Idempotency-Key` headers). There is no ordering guarantee between events. Each delivery carries `X-PuntersEdge-Timestamp`, `X-PuntersEdge-Attempt` and two signatures: `X-Webhook-Signature: sha256=<hex>` (HMAC-SHA256 of the raw body with your secret) and `X-PuntersEdge-Signature: t=<unix seconds>,v1=<hex>` (HMAC-SHA256 of `<t>.<raw body>`; refuse a `t` more than 5 minutes from now to block replays). Any 2xx answer is success. A 5xx, 408, 429, timeout or connection failure is retried after 1 min, 5 min, 30 min, 2 h, 6 h (6 tries in all, about 8.5 hours), honouring `Retry-After` up to the next step; any other answer is final. After 10 consecutive undeliverable events, or a day of continuous failure, the subscription is paused and the owner is emailed; POST /v1/webhooks/{id}/resume restarts it with the same secret, and POST /v1/webhooks/{id}/replay re-sends what was missed. The delivery log returns the newest 50 rows unless `limit` says otherwise.","operationId":"resume_webhook_v1_webhooks__webhook_id__resume_post","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResumeResponse"},"example":{"id":"wh_7c1d2e9a4b3f","resumed":true,"probe_event_id":"5d0c8e1a-2f47-4b9e-9a63-c1e7f4b2d805"}}}},"403":{"description":"Webhook subscriptions are available on Plus, Business, Platform, Scale and Enterprise plans only. Legacy Growth and Pro keys also qualify."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/webhooks/{webhook_id}/rotate-secret":{"post":{"tags":["Webhooks"],"summary":"Rotate Webhook Secret","description":"Issue a new signing secret. The old one keeps working for 24 hours.\n\nThe new secret is returned once, here. For the next 24 hours every delivery is signed with\nboth: the new secret in `X-Webhook-Signature` / `X-PuntersEdge-Signature`, the previous one\nin `X-Webhook-Signature-Previous` / `X-PuntersEdge-Signature-Previous`, so you can deploy the\nnew secret to your receiver without missing a delivery.\n\nDelivery is at-least-once: the same event can arrive more than once, so deduplicate on `event_id` (in the body, and sent as the `X-PuntersEdge-Id` and `Idempotency-Key` headers). There is no ordering guarantee between events. Each delivery carries `X-PuntersEdge-Timestamp`, `X-PuntersEdge-Attempt` and two signatures: `X-Webhook-Signature: sha256=<hex>` (HMAC-SHA256 of the raw body with your secret) and `X-PuntersEdge-Signature: t=<unix seconds>,v1=<hex>` (HMAC-SHA256 of `<t>.<raw body>`; refuse a `t` more than 5 minutes from now to block replays). Any 2xx answer is success. A 5xx, 408, 429, timeout or connection failure is retried after 1 min, 5 min, 30 min, 2 h, 6 h (6 tries in all, about 8.5 hours), honouring `Retry-After` up to the next step; any other answer is final. After 10 consecutive undeliverable events, or a day of continuous failure, the subscription is paused and the owner is emailed; POST /v1/webhooks/{id}/resume restarts it with the same secret, and POST /v1/webhooks/{id}/replay re-sends what was missed. The delivery log returns the newest 50 rows unless `limit` says otherwise.","operationId":"rotate_webhook_secret_v1_webhooks__webhook_id__rotate_secret_post","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookRotateResponse"},"example":{"id":"wh_7c1d2e9a4b3f","secret":"whsec_EXAMPLE_ONLY_shown_once_at_creation","previous_secret_expires_at":"2026-10-14T06:00:00+00:00"}}}},"403":{"description":"Webhook subscriptions are available on Plus, Business, Platform, Scale and Enterprise plans only. Legacy Growth and Pro keys also qualify."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/sports/{sport_key}/odds/history":{"get":{"tags":["Sports"],"summary":"Historical odds snapshots","description":"Cost: 5 credits. Paid tiers only — 403 on Free and Hobby.\n\nSnapshots of one market, newest first, 500 per page (`offset`), each about ten minutes\napart per bookmaker and only while the fixture has not started — so the last snapshot\nbefore `commence_time` is the closing price, and /v1/sports/{sport_key}/closing-lines\nreturns that row per book with the consensus already worked out.\n\n`home_point`/`away_point` are the line the prices are attached to: each side's handicap\non the spreads market (-6.5 / +6.5) and the total on the totals market, where Over is\nstored as the home side and Under as the away side. Lines exist from 2026-09-29; rows\nrecorded before that carry prices only. AFL and AFLW history is kept permanently; every\nother sport is a rolling 90-day window.\n\nAn unknown `sport_key`, or a date range that runs backwards, returns an error and costs\nnothing.","operationId":"get_odds_history_v1_sports__sport_key__odds_history_get","parameters":[{"name":"sport_key","in":"path","required":true,"schema":{"type":"string","title":"Sport Key"}},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date/time; defaults to 7 days ago","title":"From"},"description":"ISO date/time; defaults to 7 days ago"},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date/time; defaults to now","title":"To"},"description":"ISO date/time; defaults to now"},{"name":"bookmakers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing.","title":"Bookmakers"},"description":"Comma-separated bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing."},{"name":"market","in":"query","required":false,"schema":{"type":"string","description":"Market type: h2h (default), spreads or totals.","default":"h2h","title":"Market"},"description":"Market type: h2h (default), spreads or totals."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":[{"event_id":"4b84fde7-7054-40db-a37f-ae05c9705549","home_team":"Souths","away_team":"Newcastle","sport_key":"nrl","snapshots":[{"recorded_at":"2026-09-07T00:43:24Z","bookmaker":"tab","home_price":1.4,"draw_price":null,"away_price":3.0,"home_point":null,"away_point":null},{"recorded_at":"2026-09-07T00:43:24Z","bookmaker":"ladbrokes_au","home_price":1.4,"draw_price":null,"away_price":3.0,"home_point":null,"away_point":null},{"recorded_at":"2026-09-06T22:13:09Z","bookmaker":"tab","home_price":1.42,"draw_price":null,"away_price":2.9,"home_point":null,"away_point":null}]},{"event_id":"ad025e01-04fa-4d79-ae7f-fe756d1a55e7","home_team":"Philadelphia Eagles","away_team":"Los Angeles Rams","sport_key":"nfl","snapshots":[{"recorded_at":"2026-09-29T04:24:46.464555Z","bookmaker":"sportsbet","home_price":1.93,"draw_price":null,"away_price":1.87,"home_point":46.5,"away_point":46.5},{"recorded_at":"2026-09-29T04:14:29.916138Z","bookmaker":"sportsbet","home_price":1.93,"draw_price":null,"away_price":1.87,"home_point":46.5,"away_point":46.5}]}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/sports/{sport_key}/odds/movements":{"get":{"tags":["Sports"],"summary":"Price movement feed","description":"Cost: 5 credits. Returns detected home/draw/away price movements from historical snapshots.\n\nPaid tiers only — 403 on Free and Hobby. Same gate as the history endpoint above; the 403\nbody names the plans that include it.\n\nAn unknown `sport_key` returns 404 and costs nothing.","operationId":"get_odds_movements_v1_sports__sport_key__odds_movements_get","parameters":[{"name":"sport_key","in":"path","required":true,"schema":{"type":"string","title":"Sport Key"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date/time; defaults to 24 hours ago","title":"Since"},"description":"ISO date/time; defaults to 24 hours ago"},{"name":"bookmakers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing.","title":"Bookmakers"},"description":"Comma-separated bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing."},{"name":"market","in":"query","required":false,"schema":{"type":"string","default":"h2h","title":"Market"}},{"name":"min_delta","in":"query","required":false,"schema":{"type":"number","minimum":0.0,"default":0.01,"title":"Min Delta"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":[{"event_id":"4b84fde7-7054-40db-a37f-ae05c9705549","sport_key":"nrl","bookmaker":"tab","market_type":"h2h","home_team":"Souths","away_team":"Newcastle","commence_time":"2026-09-09T02:05:00Z","from_recorded_at":"2026-09-06T22:13:09Z","to_recorded_at":"2026-09-07T00:43:24Z","changes":{"home_price":{"from":1.42,"to":1.4,"delta":-0.02},"away_price":{"from":2.9,"to":3.0,"delta":0.1}}}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/sports/{sport_key}/closing-lines":{"get":{"tags":["Sports"],"summary":"Closing price and line per bookmaker, with the consensus","description":"Cost: 5 credits. Paid tiers only — 403 on Free and Hobby.\n\nOne row per match that started in the range. For each bookmaker: the last head-to-head\nprice, handicap line and total we recorded before the start — the closing snapshot, taken\nwithin about ten minutes of the first bounce. Then a consensus across those books: the\nmedian closing price on head-to-head, and on the line and total markets the most common\nclosing line with the median price at that exact line.\n\n`home_point`/`away_point` are the handicap on each side (-6.5 / +6.5) on the spreads\nmarket and the total on both sides of the totals market, where Over is the home side and\nUnder the away side. The lines exist from 2026-09-29; rows recorded before that carry\nprices only. AFL and AFLW price history is kept permanently, so their closes accumulate\nseason on season; every other sport is a rolling 90-day window. Betfair is never included.\nAn unknown `sport_key`, a backwards range or a range over 92 days is an error and costs\nnothing.","operationId":"get_sports_closing_lines_v1_sports__sport_key__closing_lines_get","parameters":[{"name":"sport_key","in":"path","required":true,"schema":{"type":"string","title":"Sport Key"}},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date/time. Matches that START on or after this; defaults to 7 days ago.","title":"From"},"description":"ISO date/time. Matches that START on or after this; defaults to 7 days ago."},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date/time. Matches that start on or before this; defaults to now. At most 92 days after `from` — page by month for a season.","title":"To"},"description":"ISO date/time. Matches that start on or before this; defaults to now. At most 92 days after `from` — page by month for a season."},{"name":"bookmakers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys.","title":"Bookmakers"},"description":"Comma-separated bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":[{"event_id":"5b0d2e0c-2c6d-4f2a-9a6e-1d2f3c4b5a69","sport_key":"aflw","home_team":"Carlton (W)","away_team":"Hawthorn (W)","commence_time":"2026-10-01T09:15:00Z","books":{"tab":{"h2h":{"home_price":1.6,"draw_price":null,"away_price":2.3,"home_point":null,"away_point":null,"recorded_at":"2026-10-01T09:06:12Z"},"spreads":{"home_price":1.9,"draw_price":null,"away_price":1.9,"home_point":-6.5,"away_point":6.5,"recorded_at":"2026-10-01T09:06:12Z"}},"sportsbet":{"h2h":{"home_price":1.62,"draw_price":null,"away_price":2.25,"home_point":null,"away_point":null,"recorded_at":"2026-10-01T09:06:12Z"},"spreads":{"home_price":1.88,"draw_price":null,"away_price":1.88,"home_point":-6.5,"away_point":6.5,"recorded_at":"2026-10-01T09:06:12Z"}},"pointsbetau":{"h2h":{"home_price":1.65,"draw_price":null,"away_price":2.2,"home_point":null,"away_point":null,"recorded_at":"2026-10-01T09:06:12Z"},"spreads":{"home_price":1.88,"draw_price":null,"away_price":1.88,"home_point":-7.5,"away_point":7.5,"recorded_at":"2026-10-01T09:06:12Z"},"totals":{"home_price":1.88,"draw_price":null,"away_price":1.88,"home_point":88.5,"away_point":88.5,"recorded_at":"2026-10-01T09:06:12Z"}}},"consensus":{"h2h":{"books":3,"home_price":1.62,"draw_price":null,"away_price":2.25},"spreads":{"books":3,"home_point":-6.5,"away_point":6.5,"home_price":1.89,"away_price":1.89,"books_on_line":2},"totals":{"books":1,"home_point":88.5,"away_point":88.5,"home_price":1.88,"away_price":1.88,"books_on_line":1}}}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/health/connectors":{"get":{"tags":["Health"],"summary":"Per-connector freshness","description":"Cost: 1 credit. Per-connector last-success age + a stale flag, so a stalled feed is\nvisible.\n\n`sports` breaks that down PER SPORT, because connector-level health cannot see a book\nthat has stopped covering one competition while it keeps writing others: a book can be\ncurrent to the minute on NRL and AFL while its rows for another sport are days old.","operationId":"connectors_v1_health_connectors_get","parameters":[{"name":"stale_after_s","in":"query","required":false,"schema":{"type":"number","description":"age (s) past which a connector is 'stale'","default":180.0,"title":"Stale After S"},"description":"age (s) past which a connector is 'stale'"},{"name":"coverage_stale_after_s","in":"query","required":false,"schema":{"type":"number","description":"age (s) past which a book is treated as no longer COVERING a sport. Defaults to 6h, the same window /v1/sports/{sport_key}/odds will serve — past it the book is effectively absent for that sport.","default":21600.0,"title":"Coverage Stale After S"},"description":"age (s) past which a book is treated as no longer COVERING a sport. Defaults to 6h, the same window /v1/sports/{sport_key}/odds will serve — past it the book is effectively absent for that sport."},{"name":"include_sports","in":"query","required":false,"schema":{"type":"boolean","description":"include the per-sport coverage breakdown","default":true,"title":"Include Sports"},"description":"include the per-sport coverage breakdown"},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Connectors V1 Health Connectors Get"},"example":{"now":"2026-09-07T00:49:16Z","stale_after_s":180.0,"any_stale":false,"any_degraded":false,"connectors":[{"connector":"sportsbet","last_ok_age_s":6.0,"last_poll_age_s":6.0,"polling":true,"stale":false,"records_written":163,"last_msg":"OK — 163 records","degraded":false,"sports":[{"sport_key":"nrl","markets":48,"newest_age_s":6.0,"covering":true},{"sport_key":"afl","markets":27,"newest_age_s":6.0,"covering":true}],"sports_not_covering":[]},{"connector":"tab","last_ok_age_s":11.2,"last_poll_age_s":11.2,"polling":true,"stale":false,"records_written":349,"last_msg":"OK — 349 records","degraded":false,"sports":[{"sport_key":"nrl","markets":48,"newest_age_s":11.2,"covering":true},{"sport_key":"afl","markets":27,"newest_age_s":11.2,"covering":true}],"sports_not_covering":[]},{"connector":"ladbrokes_au","last_ok_age_s":10.6,"last_poll_age_s":10.6,"polling":true,"stale":false,"records_written":375,"last_msg":"OK — 375 records","degraded":false,"sports":[{"sport_key":"nrl","markets":48,"newest_age_s":10.6,"covering":true},{"sport_key":"afl","markets":27,"newest_age_s":10.6,"covering":true}],"sports_not_covering":[]}],"coverage_stale_after_s":21600.0,"not_covering":[]}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/demo/racing/next-to-go":{"get":{"tags":["Sandbox (no key)"],"summary":"Sample next-to-go races (no key required)","description":"Public teaser: next 3 races, up to 5 runners each, best 3 bookmaker prices. No API key. Cached 30s, 30 req/min per IP.","operationId":"demo_racing_v1_demo_racing_next_to_go_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"demo":true,"note":"Free sandbox sample (truncated). Get a free API key for full data: https://puntersedge.online/api?utm_source=demo_api&utm_medium=sandbox","signup_url":"https://puntersedge.online/api?utm_source=demo_api&utm_medium=sandbox","shape":"Sandbox teaser: 3 races, 5 runners, best 3 prices, wrapped in this envelope. GET /v1/racing/next-to-go returns a bare array of full race objects. race_id matches, so anything you build here extends to /v1/racing/price-history, /results and /closing-lines.","races":[{"race_id":"bec4195f-dc6a-4a78-9d17-ae79e7954c71","venue":"Healesville","race_number":1,"category":"greyhound","start_time":"2026-09-07T00:51:00Z","country":"AU","runners":[{"name":"Mrs. Puddleduck","number":1,"bookmakers":[{"key":"betdeluxe","win_price":4.4,"source_url":"https://www.betdeluxe.com.au/racing/Greyhound/AUS/Healesville/789853/1/11268767"},{"key":"neds","win_price":4.0,"source_url":"https://www.neds.com.au/racing/healesville/8165c88a-3151-4a66-a7b7-57bbb09119c7"},{"key":"sportsbet","win_price":4.0,"source_url":"https://www.sportsbet.com.au/greyhound-racing/australia-nz/healesville/race-1-10896567"}]},{"name":"Sweet Chiquita","number":3,"bookmakers":[{"key":"tab","win_price":2.6,"source_url":"https://www.tab.com.au/racing/2026-09-07/HEALESVILLE/HSV/G/1"},{"key":"betr_au","win_price":2.45,"source_url":"https://www.betr.com.au/racing/Australian-Greyhound/Healesville/Race-1/91974333/win"},{"key":"unibet","win_price":2.4,"source_url":"https://www.unibet.com.au/racing#/event/202609070000.G.AUS.healesville.1"}]}]}],"cached":false}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/demo/best-odds":{"get":{"tags":["Sandbox (no key)"],"summary":"Sample best-odds + arb detection (no key required)","description":"Public teaser: best price per selection + arb flag for up to 3 upcoming events. No API key. Cached 45s, 30 req/min per IP.","operationId":"demo_best_odds_v1_demo_best_odds_get","parameters":[{"name":"sport","in":"query","required":false,"schema":{"type":"string","description":"Optional sport_key; auto-picks a live sport if omitted","title":"Sport"},"description":"Optional sport_key; auto-picks a live sport if omitted"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"demo":true,"note":"Free sandbox sample (truncated). Get a free API key for full data: https://puntersedge.online/api?utm_source=demo_api&utm_medium=sandbox","signup_url":"https://puntersedge.online/api?utm_source=demo_api&utm_medium=sandbox","sport":"nrl","events":[{"home_team":"Cronulla Sharks","away_team":"Nth Queensland Cowboys","commence_time":"2026-09-09T02:00:00Z","selections":[{"name":"Cronulla Sharks","best_price":1.44,"best_bookmaker":"sportsbet"},{"name":"Nth Queensland Cowboys","best_price":2.81,"best_bookmaker":"sportsbet"}],"arb_exists":false,"arb_profit_pct":0.0},{"home_team":"South Sydney Rabbitohs","away_team":"Newcastle Knights","commence_time":"2026-09-09T02:05:00Z","selections":[{"name":"South Sydney Rabbitohs","best_price":1.4,"best_bookmaker":"palmerbet"},{"name":"Newcastle Knights","best_price":2.97,"best_bookmaker":"palmerbet"}],"arb_exists":false,"arb_profit_pct":0.0}],"cached":false}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/demo/book-sport":{"get":{"tags":["Sandbox (no key)"],"summary":"Live book+sport snapshot (no key; powers SEO page differentiation)","description":"Public, book-specific live odds for one sport. Truncated to a few events/races. Cached 90s.","operationId":"demo_book_sport_v1_demo_book_sport_get","parameters":[{"name":"book","in":"query","required":true,"schema":{"type":"string","description":"SANDBOX bookmaker slug e.g. sportsbet. Slugs, not API bookmaker keys -- the real keys are betr_au, ladbrokes_au, pointsbetau, playup. Use GET /v1/racing/next-to-go for the real ones.","title":"Book"},"description":"SANDBOX bookmaker slug e.g. sportsbet. Slugs, not API bookmaker keys -- the real keys are betr_au, ladbrokes_au, pointsbetau, playup. Use GET /v1/racing/next-to-go for the real ones."},{"name":"sport","in":"query","required":true,"schema":{"type":"string","description":"SANDBOX sport slug e.g. afl or horse-racing. These are this endpoint's own URL vocabulary, NOT API sport_keys -- the keyed API has no 'horse-racing' sport_key at all (racing lives under /v1/racing/*).","title":"Sport"},"description":"SANDBOX sport slug e.g. afl or horse-racing. These are this endpoint's own URL vocabulary, NOT API sport_keys -- the keyed API has no 'horse-racing' sport_key at all (racing lives under /v1/racing/*)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"demo":true,"book":"sportsbet","sport":"nrl","type":"sports","items":[{"home_team":"Cronulla Sharks","away_team":"Nth Queensland Cowboys","commence_time":"2026-09-09T02:00:00Z","prices":[{"name":"Cronulla Sharks","price":1.44},{"name":"Nth Queensland Cowboys","price":2.81}]},{"home_team":"South Sydney Rabbitohs","away_team":"Newcastle Knights","commence_time":"2026-09-09T02:05:00Z","prices":[{"name":"South Sydney Rabbitohs","price":1.4},{"name":"Newcastle Knights","price":2.97}]}],"has_data":true,"cached":false}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/data-quality/summary":{"get":{"tags":["Data Quality"],"summary":"Data quality summary","description":"Cost: 1 credit. Summary of connector freshness, canonical mappings and recent audit status.","operationId":"data_quality_summary_v1_data_quality_summary_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"status":"ok","canonical_events":1843,"source_mappings":9211,"recent_market_quality":{"ok":612,"warn":41,"stale":9},"connectors":[{"connector":"sportsbet","last_ok":"2026-09-07T00:49:09Z","age_seconds":6,"records_written":163,"message":"OK — 163 records"},{"connector":"tab","last_ok":"2026-09-07T00:49:09Z","age_seconds":11,"records_written":349,"message":"OK — 349 records"},{"connector":"ladbrokes_au","last_ok":"2026-09-07T00:49:09Z","age_seconds":10,"records_written":375,"message":"OK — 375 records"}],"latest_audit":{"id":"2a9d3416-3cc9-41b2-a38c-9e65cca2c531","sport_key":"nba","status":"ok","api_rows":69,"source_rows":67,"exact_matches":67,"diff_rows":0,"max_delta":0.0,"finished_at":"2026-09-07T00:45:04Z"}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/data-quality/audit/latest":{"get":{"tags":["Data Quality"],"summary":"Latest bookmaker audits","operationId":"latest_audits_v1_data_quality_audit_latest_get","parameters":[{"name":"sport_key","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sport Key"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":[{"id":"2a9d3416-3cc9-41b2-a38c-9e65cca2c531","sport_key":"nba","market_type":"h2h","status":"ok","api_rows":69,"source_rows":67,"common_rows":67,"exact_matches":67,"diff_rows":0,"missing_api":0,"missing_source":0,"max_delta":0.0,"started_at":"2026-09-07T00:45:04Z","finished_at":"2026-09-07T00:45:04Z"},{"id":"ceb59980-4699-47d4-b2e8-971f288dd441","sport_key":"cricket_test","market_type":"h2h","status":"ok","api_rows":1,"source_rows":0,"common_rows":0,"exact_matches":0,"diff_rows":0,"missing_api":0,"missing_source":0,"max_delta":0.0,"started_at":"2026-09-07T00:45:04Z","finished_at":"2026-09-07T00:45:04Z"}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/data-quality/audit/{run_id}/results":{"get":{"tags":["Data Quality"],"summary":"Audit result detail","operationId":"audit_results_v1_data_quality_audit__run_id__results_get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":[{"bookmaker_key":"tab","event_key":["penrith","wests tigers","2026-09-06"],"status":"outlier","delta":0.457,"message":"wests tigers 3.8 vs consensus 7.0 (45.7%)","api":[{"selection":"wests tigers","price":3.8,"consensus":7.0,"dev_pct":45.7}],"source":{"penrith":1.22,"wests tigers":3.8}},{"bookmaker_key":"pointsbetau","event_key":["penrith","wests tigers","2026-09-06"],"status":"outlier","delta":0.2,"message":"wests tigers 5.6 vs consensus 7.0 (20.0%)","api":[{"selection":"wests tigers","price":5.6,"consensus":7.0,"dev_pct":20.0}],"source":{"penrith":1.14,"wests tigers":5.6}}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/closing-lines":{"get":{"tags":["Racing Archive"],"summary":"Permanent closing-line and result archive","description":"Cost: 5 credits (JSON) / 20 credits (CSV). The permanent closing-line archive: one row\nper race, runner and bookmaker, carrying the last price seen before the jump, the first\nprice seen after the market opened, and the finishing position where one is known.\n\n**Plans.** Plus and above read the whole archive back to its first race; `window_days`\ncomes back null for them. Standard reads a rolling 90-day window: `window_days` is 90,\nthe response carries `X-Archive-Window-Days`, and a range entirely older than the window\nis a free 403 that says so. Free and Hobby get a free 403 that carries a `sample`: five\nof this endpoint's rows, for one recent race. Standard keys issued before\n2026-09-20 keep the whole archive they were sold; the `window_days` field tells you\nwhich case yours is. Legacy `starter`, `pro` and `growth` keys keep their access but\ncannot be bought.\n\n**This is not the same data as `/v1/racing/price-history`.** That endpoint reads the live\n45-day snapshot store and returns every price move for one race. This one reads a separate,\npermanent table and returns one collapsed row per series across any date range. Price\nhistory disappears at 45 days; the archive does not.\n\n**Read the flags before you model on this.** Measured 2026-08-18 over 348,889 archived\nseries:\n\n* `is_closing_line` — only 85.5% of series have their final observation within 300s of the\n  jump. The rest stopped being quoted early and their close is a last-seen price. Filtered\n  in by default.\n* `open_is_baseline` — only 42.5% of series start at the 60-minute window entry. The rest\n  joined mid-window, so their \"open\" is not a market open.\n* `finish_position` **NULL does not mean the runner lost.** Before 2026-10-04 harness and NZ\n  results named the placegetters only, so most runners in a resulted race there have no\n  published position; AU harness and NZ results carry the full field from 2026-10-04,\n  and GB and IE thoroughbred results from 2026-10-06.\n  AU thoroughbred and greyhound results carry the whole field, and there every finisher has\n  its position (5th, 6th, …) — applied back to 2026-09-14 on 2026-09-29; before that only\n  1-4 were stored. Check `result_status`: NULL there means the race has no result at all.\n* `venue_split_suspect` / `name_fragment_suspect` — the archive's record of its own known\n  contamination. Excluded by default.\n* `duplicate_suspect` / `duplicate_of_race_id` — the race appears to duplicate another race\n  record (the same race filed twice); the second names the other race_id. These keys, and\n  `meeting_date_local`, appear on a row once the archive carries them; an absent key means\n  not assessed, not false.\n* `scratched` — the result marked this runner scratched, yet the row holds a price: four\n  books (Betfair, Sportsbet, Ladbrokes, Neds) keep quoting a scratched runner and the\n  archive kept their last price as its \"close\". Excluded by default since 2026-09-28\n  (`include_scratched=true` to see them). Only AU thoroughbred and greyhound results name\n  their scratchings, so on harness and NZ rows false means not known, never ran.\n\n**Recommended filter for modelling.** Keep a row when `is_closing_line AND NOT scratched AND\nNOT venue_split_suspect AND NOT name_fragment_suspect AND NOT duplicate_suspect` (treat an\nabsent `duplicate_suspect` as false). For GB/IE racing, key a race on `race_id` or on\n`(venue, meeting_date_local, race_number)`, never on `meeting_date_aet`: a British or Irish\nevening card falls on the next Australian Eastern date. Every row's fields are typed in the\n`ClosingLineRow` schema.\n\n**Three columns appended LAST on 2026-09-29, after `scratched`** — every earlier column\nkeeps its position:\n\n* `open_place_price` / `close_place_price` — the book's PLACE price at the first and the\n  last observation of the series. NULL on rows archived before 2026-09-29 (the capture\n  began storing place that day) and at books that quote no place price (Betfair, and any\n  book that prices win only). The place price is recorded at the moments the WIN price\n  moved, not at every place move, so a close_place_price can be older than the book's\n  final place price by up to the time since its last win move.\n* `scratched_at` — when the runner was scratched, as the scratching book stamped it on the\n  live board, captured at the jump from 2026-09-29. NULL for a runner that ran, for a\n  scratching the board carried without a time, and for every race before that date.\n\n**Six Top-N columns appended LAST on 2026-10-05, after `scratched_at`:**\n`open_top2_price`, `open_top3_price`, `open_top4_price`, `close_top2_price`,\n`close_top3_price`, `close_top4_price` — the book's Same Race Multi Top 2 / Top 3 / Top 4\nprice at the first and the last observation of the series. Only **sportsbet, ladbrokes_au,\nneds and pointsbetau** publish these legs, so they are NULL at every other book; NULL on\nevery race before 2026-09-29, when the capture began holding them. Like the place price,\nthey are recorded at the moments the WIN price moved, not at every Top-N move.\n\n**Five consensus columns appended LAST on 2026-10-05, after `close_top4_price`:**\n`consensus_close_prob`, `consensus_close_price`, `books_in_consensus_close`,\n`consensus_open_price`, `books_in_consensus_open` — the market's margin-free probability\nfor the runner at the close, the price it corresponds to, and how many books it rests on,\nplus the same price at the 60-minute open. The arithmetic is GET /v1/racing/consensus's\ndefault (power method): each included book's implied probabilities raised to the exponent\nthat makes them sum to one, the median across books per runner, the field scaled to one.\nThe archive is one method throughout — the rows written under the multiplicative default\non 5 October 2026 were recomputed when power became the default on 6 October. A book is included at the close when its\nrow is a genuine closing line (`is_closing_line`), it prices the complete field, it is not\nwithheld from customers and the runner was not scratched; placeholder quotes leave on the\nbest-odds rule. The open figure counts only series that began at the baseline\n(`open_is_baseline`), so it rests on fewer books and is NULL where fewer than two qualify.\nThe value is the same on every bookmaker row of a runner, like `finish_position`, and it is\nrecomputed by the hourly archiver whenever a row changes. It describes the market, not the\noutcome: it is what the panel implied, with margin removed, not a probability of winning.\n\n**Result coverage is thin and forward-growing.** The results feed began on 2026-08-15 and\nis AU/NZ only, so on 2026-08-18 only 5.3% of archived races carry a result. `resulted_rows`\non the response is the measured count for your actual selection, not a marketing figure.\n\n**`runner_ref` joins a row to everything about its race; `horse_ref` joins a HORSE across\nmeetings.** `runner_ref` (the column after `venue_site`, since 2026-09-05) is the registry\nid stored on the row — `ra:<horsecode>` for thoroughbreds, `grv:<dogId>` for greyhounds —\nthe same value `/v1/racing/results` carries on every `runners[]` entry and\n`/v1/racing/markets` carries for the race, so an archive row joins to its result and its\nmarket rows without matching names. On a THOROUGHBRED it is the code Racing Australia\npublished on race day; Racing Australia issues a new code for the same horse each day, so\nit changes from meeting to meeting and a season keyed on it splits into one group per\nstart — `horse_ref` is the key for that. On greyhounds `grv:`/`grsa:` are per-dog registry\nids and do persist. Filled from Racing Australia acceptances for AU thoroughbreds and from\nthe result itself for every code once it lands, so it can be null on a row whose race has\nnot resulted yet, on harness (no registry feed), and on rows archived before the\nidentifiers existed (late August 2026). Appended after `venue_site` so positional CSV\nreaders keep working.\n\n**`horse_ref`** (the column after `runner_ref`, added 2026-09-21) is the horse identity:\n`pe:<name>`, the registered name folded to letters and digits with any trailing country\nparenthetical removed. It is DERIVED at serve time from `runner_name`, not stored, so every row the\narchive has ever held carries it from today — no backfill, and nothing already exported\nmoved: it is appended after `runner_ref`, exactly as `runner_ref` was appended after\n`venue_site`. It is the same value on `/v1/racing/results` runners and placings, the live\nboard, `/v1/racing/horses/form`, `/v1/racing/horses/runs` and the form backfill, so a\nhorse's whole price history across meetings is one `GROUP BY horse_ref`. Null on\ngreyhound and harness rows, and on any row whose `runner_name` is missing.\n\nBetfair Exchange rows are withheld from customer responses pending a data licence, so\n`close_lay_price` — which only an exchange quotes — is NULL for customers.\n\n**Dates.** `from`/`to` bound the range. `date=YYYY-MM-DD` is a convenience alias for a\nsingle day and is exactly `from=YYYY-MM-DD&to=YYYY-MM-DD`; it exists because\n`/v1/racing/results` and `/v1/racing/acceptances` take `date` and callers reasonably\nexpect the same spelling here. `/v1/racing/events` does NOT — it is forward-looking\nand takes `hours_ahead`.\nPassing `date` together with `from` or `to` is a free 422 rather than a silent precedence\nrule.\n\nAn unknown bookmaker key, an unknown parameter, a reversed date range or a bad date is an\nerror and costs nothing.","operationId":"closing_lines_v1_racing_closing_lines_get","parameters":[{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date/time on the race start. Defaults to the archive floor.","title":"From"},"description":"ISO date/time on the race start. Defaults to the archive floor."},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date/time. Defaults to now.","title":"To"},"description":"ISO date/time. Defaults to now."},{"name":"date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Single-day alias: date=YYYY-MM-DD is exactly from=YYYY-MM-DD&to=YYYY-MM-DD. Mutually exclusive with from/to. Also accepted by /v1/racing/results. NOT by /v1/racing/events, which is forward-looking and takes hours_ahead.","title":"Date"},"description":"Single-day alias: date=YYYY-MM-DD is exactly from=YYYY-MM-DD&to=YYYY-MM-DD. Mutually exclusive with from/to. Also accepted by /v1/racing/results. NOT by /v1/racing/events, which is forward-looking and takes hours_ahead."},{"name":"venue","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Venue name, case-insensitive, exact match.","title":"Venue"},"description":"Venue name, case-insensitive, exact match."},{"name":"bookmakers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing.","title":"Bookmakers"},"description":"Comma-separated bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing."},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"horse, harness or greyhound — comma-separated for more than one. `categories=` is accepted as an alias (2026-09-24); an unknown value is a free 422.","title":"Category"},"description":"horse, harness or greyhound — comma-separated for more than one. `categories=` is accepted as an alias (2026-09-24); an unknown value is a free 422."},{"name":"categories","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Alias of `category`. Comma-separated horse, harness, greyhound. Both spellings are accepted on every racing endpoint.","title":"Categories"},"description":"Alias of `category`. Comma-separated horse, harness, greyhound. Both spellings are accepted on every racing endpoint."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter country code, case-insensitive; comma-separated for more than one, e.g. AU, or GB,IE for British and Irish racing.","title":"Country"},"description":"Two-letter country code, case-insensitive; comma-separated for more than one, e.g. AU, or GB,IE for British and Irish racing."},{"name":"race_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Single race, joinable to /v1/racing/price-history while that race is still inside the 45-day snapshot window.","title":"Race Id"},"description":"Single race, joinable to /v1/racing/price-history while that race is still inside the 45-day snapshot window."},{"name":"closing_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only rows whose last observation was within 300s of the jump. True by default because 14.5% of series are NOT closing lines and silently mixing them in is how a CLV study goes wrong.","default":true,"title":"Closing Only"},"description":"Only rows whose last observation was within 300s of the jump. True by default because 14.5% of series are NOT closing lines and silently mixing them in is how a CLV study goes wrong."},{"name":"resulted_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only rows from races that have a result.","default":false,"title":"Resulted Only"},"description":"Only rows from races that have a result."},{"name":"include_flagged","in":"query","required":false,"schema":{"type":"boolean","description":"Include rows flagged venue_split_suspect or name_fragment_suspect. False by default; set true if you want the contaminated rows and intend to handle them.","default":false,"title":"Include Flagged"},"description":"Include rows flagged venue_split_suspect or name_fragment_suspect. False by default; set true if you want the contaminated rows and intend to handle them."},{"name":"include_scratched","in":"query","required":false,"schema":{"type":"boolean","description":"Include runners the result marked scratched. False by default: four books keep pricing a scratched runner, so the archive holds a \"close\" for a runner that never started (rows carry scratched=true). Only AU thoroughbred and greyhound results name their scratchings, so on harness and NZ rows scratched=false means not known, never ran.","default":false,"title":"Include Scratched"},"description":"Include runners the result marked scratched. False by default: four books keep pricing a scratched runner, so the archive holds a \"close\" for a runner that never started (rows carry scratched=true). Only AU thoroughbred and greyhound results name their scratchings, so on harness and NZ rows scratched=false means not known, never ran."},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv)$","description":"csv streams a flat table with a stable column order — the format a modeller actually wants.","default":"json","title":"Format"},"description":"csv streams a flat table with a stable column order — the format a modeller actually wants."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50000,"minimum":1,"default":500,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClosingLinesOut"},"example":{"rows_returned":117,"total_rows":117,"limit":500,"offset":0,"archive_from":"2026-08-04T09:22:00Z","window_days":null,"resulted_rows":117,"rows":[{"race_id":"81db7342-2e44-482a-bf62-5d5166d764f5","start_time":"2026-09-29T03:30:00Z","meeting_date_aet":"2026-09-29","venue":"Tatura","race_number":1,"category":"horse","country":"AU","runner_key":"theshyster","runner_name":"The Shyster","runner_number":8,"bookmaker_key":"betgold","open_win_price":2.45,"open_secs_to_jump":3597,"open_is_baseline":true,"close_win_price":2.3,"close_lay_price":null,"close_secs_to_jump":80,"is_closing_line":true,"points_observed":6,"finish_position":1,"result_status":"final","venue_split_suspect":false,"venue_split_peer_race_id":null,"name_fragment_suspect":false,"venue_id":"tatura","venue_site":"tatura","runner_ref":"ra:NDIzMjAzNDI2NDA","horse_ref":"pe:theshyster","scratched":false,"open_place_price":1.3,"close_place_price":1.26,"scratched_at":null,"open_top2_price":null,"open_top3_price":null,"open_top4_price":null,"close_top2_price":null,"close_top3_price":null,"close_top4_price":null,"consensus_close_prob":0.388705,"consensus_close_price":2.573,"books_in_consensus_close":14,"consensus_open_price":2.754,"books_in_consensus_open":13}]}},"text/csv":{"schema":{"type":"string","example":"race_id,start_time,meeting_date_aet,venue,..."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"x-curl-query":"from=2026-08-10&to=2026-08-17&category=horse&format=csv"}},"/v1/racing/closing-lines/coverage":{"get":{"tags":["Racing Archive"],"summary":"What the closing-line archive actually holds","description":"Cost: 1 credit. The archive's own measurements of itself — depth, date floor, how much\nof it is a genuine closing line, how much has a result, and how many rows carry a\ncontamination flag.\n\nResults are collected for Australian and New Zealand racing from `results_from` onward,\nand for British and Irish thoroughbreds from 2026-10-06 (Betway's settlement feed: every\nfinisher Betway ranks; on Irish races it ranks the first four first and the rest within\nabout 25 minutes of the off). British and Irish races\nare closed by the UK-licensed books\n(`ladbrokes_uk`, `williamhill`, `betway_uk`) and the Australian books that quote them;\nraces in every other country carry closing prices but never a finish position. So\n`resulted_races_pct` is the share of the WHOLE archive with a result, while\n`settleable_races` / `settleable_resulted_pct` count only AU and NZ races that started\nafter results collection began — the figure to read if you intend to settle against\nthe archive. `by_category` carries `resulted_races` per code for the same reason.\n\n`finish_position_pct` is the finish-position fill rate: of the rows whose race has a\nresult (runners marked scratched left out), the share that carry a position. AU\nthoroughbred and greyhound results publish every finisher's position, and AU harness\nand NZ results carry the full field from 2026-10-04, so all of them run near 100% on\nraces from then on; before 2026-10-04 harness and NZ results named the placegetters\nonly, which holds their whole-archive figure near the placed share of the field.\n`by_category` carries it per code.\n\n`consensus_close_pct` (from 2026-10-05) is the share of genuine closing lines that carry\n`consensus_close_prob`, the market's margin-free probability stored at the close. Rows\nwithout one belong to races where fewer than two complete-field books closed.\n\n**Per book and per join, over a recent window (`days`, default 30).** `by_bookmaker` gives\neach book's `closing_pct` and `baseline_pct`: the closing-line rate varies widely between\nbooks, so the archive-wide `is_closing_line_pct` hides the books whose series often stop\nearly. `races_without_result` counts AU and NZ archive races in the window with no result\nat all, and `results_without_race` counts AU and NZ results with no archive race; their\ndenominators are `settleable_races_in_window` and `results_in_window`, and `definitions`\nstates each count in one line. Races younger than three hours are left out of both gaps.\n\nThis exists so nobody has to take a marketing number on trust before paying for a bulk\npull. Every figure is computed from the table, refreshed at most every ten minutes. Open to every key, Free\nincluded, so you can check the archive before paying for it; the numbers describe the\nwhole archive. Reading the rows themselves (/v1/racing/closing-lines) is a paid endpoint.","operationId":"closing_lines_coverage_v1_racing_closing_lines_coverage_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":90,"minimum":1,"description":"Days the per-bookmaker rates and the two result-join gaps are measured over, ending now. Every other figure covers the whole archive.","default":30,"title":"Days"},"description":"Days the per-bookmaker rates and the two result-join gaps are measured over, ending now. Every other figure covers the whole archive."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArchiveCoverageOut"},"example":{"archive_from":"2026-08-04T09:22:00Z","archive_to":"2026-09-29T04:10:00Z","rows":1474236,"races":40953,"bookmakers":14,"is_closing_line_pct":84.34,"open_is_baseline_pct":62.13,"resulted_races_pct":23.82,"results_from":"2026-08-15T05:53:00Z","settleable_races":10296,"settleable_resulted_pct":94.69,"results_scope":"Results are collected for Australian and New Zealand racing only, from results_from onward. Races in other countries carry closing prices but no finish position. settleable_races counts AU and NZ races that started after results_from.","venue_split_suspect_rows":13323,"name_fragment_suspect_rows":387,"scratched_rows":9058,"finish_position_pct":61.29,"by_category":[{"category":"greyhound","rows":654868,"races":15309,"resulted_races":5570,"finish_position_pct":71.51},{"category":"horse","rows":514720,"races":19466,"resulted_races":2214,"finish_position_pct":57.53},{"category":"harness","rows":304648,"races":6178,"resulted_races":1965,"finish_position_pct":43.14}],"window_days":30,"window_from":"2026-09-13T09:30:00Z","by_bookmaker":[{"bookmaker_key":"pointsbetau","rows":167700,"closing_pct":91.4,"baseline_pct":54.9},{"bookmaker_key":"sportsbet","rows":54544,"closing_pct":69.0,"baseline_pct":98.5},{"bookmaker_key":"palmerbet","rows":57538,"closing_pct":54.4,"baseline_pct":92.1}],"settleable_races_in_window":6695,"races_without_result":189,"results_in_window":6607,"results_without_race":101,"definitions":{"races_without_result":"AU and NZ archive races that started in the window, at least 3 hours ago, with no result row for their race_id.","results_without_race":"AU and NZ results that started in the window, at least 3 hours ago, whose race_id has no archive row at any served book (or no race_id).","closing_pct":"Per bookmaker: share of that book's archive rows in the window with is_closing_line (last observation within 300 seconds of the jump).","baseline_pct":"Per bookmaker: share of that book's archive rows in the window with open_is_baseline (series began at the 60-minute window entry)."}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/price-paths":{"get":{"tags":["Racing Archive"],"summary":"Bulk export of the permanent price-movement archive","description":"Cost: 5 credits (JSON) / 20 credits (CSV). Every OBSERVED PRICE POINT in the permanent\nmovement archive: one row per race, runner, bookmaker and captured price, from market open\nto the last pre-jump quote.\n\n**This is the bulk, downloadable form of `/v1/racing/price-history`.** That endpoint\nanswers for one race; this one exports a date range as a flat table (CSV loads straight\ninto pandas — `pd.read_csv(url)` with your key in the header; `format=parquet` returns the\nsame table typed, `pd.read_parquet(BytesIO(r.content))`). The archive is permanent:\nnothing here ages out, and coverage runs from `archive_from` (capture began 2026-08-04)\nforward, forever.\n\n**The series is change-only.** Each row is a price MOVE, not a fixed-interval sample — a\ngap between two points means the price held. To build T-snapshots (T-60/30/15/5/2), take\nthe last row at or before each mark per (race_id, runner_key, bookmaker_key).\n\n**Window guard.** Points run ~9x the series rows, so a request without `race_id` is capped\nat a 7-day `from`/`to` window per call — page a season week by week. A reversed or\nover-wide range is a free 422.\n\n**`runner_ref`** (the column after `venue_site`, since 2026-09-05) is the registry id\nstored on the row — `ra:<horsecode>` for thoroughbreds, `grv:<dogId>` for greyhounds — identical to the\n`runner_ref` on `/v1/racing/results` `runners[]` entries, on `/v1/racing/closing-lines` and\n`/v1/racing/markets`, and on the live board on race day, so every point in this export\njoins to its result and to the race's other rows on one key.\nNull where no registry id is known: harness, rows archived before late August 2026, and\na race whose result has not landed and whose acceptance list we did not hold.\nOn a thoroughbred it is the code Racing Australia published on race day, not the horse:\nRacing Australia issues a new code for the same horse each day, so it changes from meeting\nto meeting. A greyhound's does not — `grv:`/`grsa:` are per-dog registry ids.\n\n**`horse_ref`** (the column after `runner_ref`, added 2026-09-21) is the horse identity\nthis export was missing: `pe:<name>`, the registered name folded to letters and digits (trailing country\nparenthetical removed). Derived at serve time from `runner_name` rather than stored, so\nevery point ever archived carries it from today, and appended after `runner_ref` so no\npositional reader moves. Group a horse's whole price history on it — across meetings,\ntracks and seasons — and use `runner_ref` to join the rows about one race. Null on\ngreyhound and harness rows.\n\n**`place_price`** (LAST column, added 2026-09-29) is the book's PLACE price at that point.\nNULL on every point archived before 2026-09-29 and at books that quote no place price. A\npoint is written when the WIN (or exchange lay) price moves — a place-only move does not\ncreate one — so it is the place price at each win move, not a separate place series.\n\n**`top2_price`, `top3_price`, `top4_price`** (LAST three columns, added 2026-10-05) are the\nbook's Same Race Multi Top 2 / Top 3 / Top 4 price at that point, on the same win-move\ncadence. Only sportsbet, ladbrokes_au, neds and pointsbetau publish them — NULL at every\nother book — and NULL on every point before 2026-09-29.\n\n**Plans.** Plus and above, and Standard keys issued before 2026-09-20. Any other key gets\na free 403 that carries a `sample`: the last five moves in one book's price for the\nwinner of one recent race, as rows of this export.\n\nBetfair Exchange rows are withheld pending a data licence, so `lay_price` — which only an\nexchange quotes — is NULL for customers. BSP and traded volume are absent for the same\nreason; when a licence lands they will arrive as new columns, not a changed contract.","operationId":"price_paths_v1_racing_price_paths_get","parameters":[{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date/time on the race start.","title":"From"},"description":"ISO date/time on the race start."},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date/time.","title":"To"},"description":"ISO date/time."},{"name":"date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Single-day alias: date=YYYY-MM-DD is exactly from=YYYY-MM-DD&to=YYYY-MM-DD. Mutually exclusive with from/to.","title":"Date"},"description":"Single-day alias: date=YYYY-MM-DD is exactly from=YYYY-MM-DD&to=YYYY-MM-DD. Mutually exclusive with from/to."},{"name":"venue","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Venue name, case-insensitive, exact match.","title":"Venue"},"description":"Venue name, case-insensitive, exact match."},{"name":"bookmakers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys.","title":"Bookmakers"},"description":"Comma-separated bookmaker keys, case-insensitive. An unrecognised key is a free 422 naming the valid keys."},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"horse, harness or greyhound — comma-separated for more than one. `categories=` is accepted as an alias (2026-09-24); an unknown value is a free 422.","title":"Category"},"description":"horse, harness or greyhound — comma-separated for more than one. `categories=` is accepted as an alias (2026-09-24); an unknown value is a free 422."},{"name":"categories","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Alias of `category`. Comma-separated horse, harness, greyhound. Both spellings are accepted on every racing endpoint.","title":"Categories"},"description":"Alias of `category`. Comma-separated horse, harness, greyhound. Both spellings are accepted on every racing endpoint."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter country code, case-insensitive; comma-separated for more than one, e.g. AU, or GB,IE for British and Irish racing.","title":"Country"},"description":"Two-letter country code, case-insensitive; comma-separated for more than one, e.g. AU, or GB,IE for British and Irish racing."},{"name":"race_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Single race. With race_id the window guard does not apply.","title":"Race Id"},"description":"Single race. With race_id the window guard does not apply."},{"name":"include_flagged","in":"query","required":false,"schema":{"type":"boolean","description":"Include rows flagged venue_split_suspect or name_fragment_suspect. False by default.","default":false,"title":"Include Flagged"},"description":"Include rows flagged venue_split_suspect or name_fragment_suspect. False by default."},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|parquet)$","description":"csv streams a flat table with a stable column order — the bulk download this endpoint exists for. parquet returns the same table, same column order, as one Apache Parquet file with typed columns (UTC timestamps, a date, float64 prices) — pd.read_parquet(BytesIO(r.content)). Same credit cost and row cap as csv.","default":"json","title":"Format"},"description":"csv streams a flat table with a stable column order — the bulk download this endpoint exists for. parquet returns the same table, same column order, as one Apache Parquet file with typed columns (UTC timestamps, a date, float64 prices) — pd.read_parquet(BytesIO(r.content)). Same credit cost and row cap as csv."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200000,"minimum":1,"default":1000,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PricePathsOut"},"example":{"rows_returned":969,"total_points":969,"limit":1000,"offset":0,"archive_from":"2026-08-04T09:22:00Z","rows":[{"race_id":"81db7342-2e44-482a-bf62-5d5166d764f5","start_time":"2026-09-29T03:30:00Z","meeting_date_aet":"2026-09-29","venue":"Tatura","race_number":1,"category":"horse","country":"AU","runner_key":"theshyster","runner_name":"The Shyster","runner_number":8,"bookmaker_key":"betgold","captured_at":"2026-09-29T02:30:03Z","secs_to_jump":3597,"win_price":2.45,"lay_price":null,"venue_id":"tatura","venue_site":"tatura","runner_ref":"ra:NDIzMjAzNDI2NDA","horse_ref":"pe:theshyster","scratched":false,"place_price":1.3,"top2_price":null,"top3_price":null,"top4_price":null},{"race_id":"81db7342-2e44-482a-bf62-5d5166d764f5","start_time":"2026-09-29T03:30:00Z","meeting_date_aet":"2026-09-29","venue":"Tatura","race_number":1,"category":"horse","country":"AU","runner_key":"theshyster","runner_name":"The Shyster","runner_number":8,"bookmaker_key":"betgold","captured_at":"2026-09-29T03:26:49Z","secs_to_jump":191,"win_price":2.35,"lay_price":null,"venue_id":"tatura","venue_site":"tatura","runner_ref":"ra:NDIzMjAzNDI2NDA","horse_ref":"pe:theshyster","scratched":false,"place_price":1.26,"top2_price":null,"top3_price":null,"top4_price":null},{"race_id":"81db7342-2e44-482a-bf62-5d5166d764f5","start_time":"2026-09-29T03:30:00Z","meeting_date_aet":"2026-09-29","venue":"Tatura","race_number":1,"category":"horse","country":"AU","runner_key":"theshyster","runner_name":"The Shyster","runner_number":8,"bookmaker_key":"betgold","captured_at":"2026-09-29T03:28:40Z","secs_to_jump":80,"win_price":2.3,"lay_price":null,"venue_id":"tatura","venue_site":"tatura","runner_ref":"ra:NDIzMjAzNDI2NDA","horse_ref":"pe:theshyster","scratched":false,"place_price":1.26,"top2_price":null,"top3_price":null,"top4_price":null}]}},"text/csv":{"schema":{"type":"string","example":"race_id,start_time,meeting_date_aet,venue,..."}},"application/vnd.apache.parquet":{"schema":{"type":"string","format":"binary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"x-curl-query":"date=2026-08-15&category=horse&format=csv"}},"/v1/racing/greyhounds/form":{"get":{"tags":["Racing"],"summary":"Greyhound form history for one dog, from our own results","description":"Cost: 3 credits. Every run we hold for one greyhound, newest first, from our own collection.\n\nSOURCE. Not a third-party form feed: every run comes off the official public results pages we\nread ourselves — Victorian tracks from FastTrack (`source: fasttrack`, `runner_ref: grv:<id>`)\nand every other state from the national results pages (`source: grsa`, `runner_ref:\ngrsa:<id>`). `origin` says which half of the store a run is in: `results` = a race we also\nsettled and serve on /v1/racing/results, `history` = a race our history collector read back\noff the same page for a date our settled store does not cover, whose `result_id` is a\nsynthetic `hist:…` and joins to nothing. `source.coverage_from` is the oldest run actually\nheld and `source.results_from` the date the settled half begins, so `runs_total` is the dog's\nrecord over a stated window and not its career. Since 2026-10-13 `runs_total` counts STARTS\n(`status: ran`), the same figure as /v1/racing/greyhounds/stats `overall.starts`, and\n`held_rows_total` counts every held row, scratchings and unused reserves included -- the rows\n`runs` is drawn from. Before then `runs_total` counted every row.\n\nIDENTITY. Pass a name and the runs are grouped on its letters and digits (\"Ando's Mac\" and\n\"ANDOS MAC\" are one query; the whole name must match). Our two collectors number dogs in\ndifferent registries, so a dog that races in Victoria and interstate has two refs; the name\njoins them and `runner_refs` lists every id seen. Pass a ref and you get that ref's runs —\nplus, when the registry map has proved the two refs are one dog (one grv id, one grsa id,\ntrainers that agree or do not contradict), that dog's runs in BOTH registries. A name\nmatching nothing is a 404 and costs nothing.\n\nREADING A RUN. `position` is the finishing place and is null when the dog did not run (`status`\nsays `scratched` or `reserve`) or did not complete the course. `time_s` is the dog's own time\nand `win_time_s` the winner's in the same race. `sp` is the official starting price as\npublished with the result; null means unknown, never zero. `margin` is the source's own text:\nlengths behind the winner, and on Victorian tracks the winner's row carries its winning margin.\n`field_size` is how many dogs ran.\n\nTHE MEASURED COLUMNS, AND WHY THEY ARE OFTEN NULL. `first_split_s`, `second_split_s`,\n`weight_kg`, `pir` and `grade` are printed by the source pages and have been carried through\nsince 2026-09-21. Each is null wherever that page printed nothing, and the gaps are\nstructural, not random: FastTrack publishes one split and no second; PIR comes from\nFastTrack's own column on Victorian tracks and, since 2026-09-29, from the national page's In\nRace Positions card, which only some tracks print (see `pir`); Western Australian meetings\nmostly print no weight; a rug 9/10 reserve that never started carries no weight, split or PIR\nat all. Sampled on 2026-09-21 over\n46 freshly read race pages, a grade appeared on 46/46 races and 362/362 runner rows, a first\nsplit on 288/362 and a weight on 295/362. ⚠️ A STORED FIELD IS NEVER REWRITTEN, so a run\nsettled before 2026-09-21 carries none of the five however well its page printed them — read\nthem per run, never as a property of the dog.","operationId":"greyhound_form_v1_racing_greyhounds_form_get","parameters":[{"name":"dog","in":"query","required":true,"schema":{"type":"string","description":"Dog name (matched on letters and digits, case-insensitive, whole name), or a runner_ref like grv:1055038365 / grsa:441535 to restrict to one registry id.","title":"Dog"},"description":"Dog name (matched on letters and digits, case-insensitive, whole name), or a runner_ref like grv:1055038365 / grsa:441535 to restrict to one registry id."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Most recent runs to return.","default":20,"title":"Limit"},"description":"Most recent runs to return."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GreyhoundFormOut"},"example":{"dog":"Wally's Boy","name_key":"wallysboy","runner_refs":["grsa:334352"],"runs_total":13,"held_rows_total":14,"runs":[{"date":"2026-09-28","start_time":"2026-09-28T12:52:00Z","venue":"Q Straight","race_number":14,"distance_m":300,"race_name":"Gorski Truck & Trailer Pathways H","track_condition":"Good","box":8,"number":8,"position":5,"status":"ran","margin":null,"time_s":null,"first_split_s":null,"second_split_s":null,"weight_kg":null,"pir":"4","grade":"P5","win_time_s":null,"sp":null,"dead_heat":false,"trainer":"George Kadniak","field_size":8,"runner_ref":"grsa:334352","source":"grsa","origin":"results","result_id":"07d60362-c799-4216-b433-8eddf8c20e25"},{"date":"2026-09-24","start_time":"2026-09-24T03:37:00Z","venue":"Q Straight","race_number":8,"distance_m":350,"race_name":"Gap Greyhounds Great Pets 5Th Grade Ht2","track_condition":null,"box":8,"number":8,"position":6,"status":"ran","margin":"10.25","time_s":19.64,"first_split_s":4.12,"second_split_s":9.2,"weight_kg":29.9,"pir":null,"grade":"5","win_time_s":18.92,"sp":71.0,"dead_heat":false,"trainer":"George Kadniak","field_size":8,"runner_ref":"grsa:334352","source":"grsa","origin":"results","result_id":"4b0896ff-5680-4002-8a03-4aae093e2470"}],"source":{"feed":"puntersedge","region":"AU","sport":"greyhound","coverage_from":"2026-06-23","results_from":"2026-09-13","coverage_to":"2026-09-29","collectors":["fasttrack","grsa"],"refreshed_hourly":true}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/greyhounds/stats":{"get":{"tags":["Racing"],"summary":"Greyhound record by track, distance, box or grade, from our own results","description":"Cost: 3 credits. One greyhound's record from our own results, split by track, distance,\nbox, grade, or any combination — plus the same figures over every run, in `overall`.\n\nSOURCE. The same store as /v1/racing/greyhounds/form: the official results pages we read\nourselves, Victorian tracks via FastTrack and every other state via the national results\npages, rebuilt hourly, in two halves (`origin` on the form endpoint says which). These are\nfigures over `source.coverage_from`..`source.coverage_to` only, not a career.\n\nGROUPING. `by=track,distance` returns one row per track-and-distance pair the dog has started\nat; `by=box` one row per box; `by=grade` one row per grade. The dimensions you did not request\ncome back null on each row. Rows are ordered by starts, most first.\n\nGRADE (a dimension since 2026-09-29). Measured 2026-09-28 21:07 UTC: 6,217 of the 6,217 runs\nin the last seven days carry a grade (100.0%). It is the grade in the SOURCE'S OWN WORDS —\n\"5\", \"M\", \"4/5\" on the national pages, a race type such as \"Grade 5\" or \"Mixed 4/5\" on\nFastTrack — and the two vocabularies are NOT mapped onto each other, so a dog that raced in\nVictoria and interstate shows a FastTrack row and a national row for what may be the same\nclass. Compare grade rows within one source. Older runs are covered too: history runs have\ncarried a grade from the start, and after the 2026-09-29 re-read of every settled race since\n2026-09-13 (whose pages were stored before the grade was collected) 100.0% of `ran` entries\ncarry one on every Sydney day from 2026-09-13 to 2026-09-28. A run without a grade would\ngroup under null rather than be dropped.\n\nTHE NUMBERS. `starts` counts runs with status `ran`; scratchings and unused reserves are\nexcluded from every figure, so `overall.starts` equals `runs_total` on the form endpoint (both\ncount starts since 2026-10-13) and is lower than its `held_rows_total`. `wins` is place 1, `places` a finish in the first three, both percentages over\n`starts`. `avg_time_s` and `best_time_s` are over the runs with a recorded time; `avg_sp` over\nthe runs that carry an official starting price; `avg_first_split_s` over the runs whose page\nprinted a first sectional, which is FEWER than `starts` — some tracks print none, so it is\nnot an average over the group you might assume. On a small group these describe a handful of\nruns, so read `starts` alongside them. A misspelt `by` is a 422 and costs nothing; a name\nmatching nothing is a free 404.","operationId":"greyhound_stats_v1_racing_greyhounds_stats_get","parameters":[{"name":"dog","in":"query","required":true,"schema":{"type":"string","description":"Dog name or runner_ref, resolved exactly as on /v1/racing/greyhounds/form.","title":"Dog"},"description":"Dog name or runner_ref, resolved exactly as on /v1/racing/greyhounds/form."},{"name":"by","in":"query","required":false,"schema":{"type":"string","description":"Dimensions to group by: track, distance, box, grade. Comma-separate to cross them, e.g. track,distance or grade,track.","default":"track","title":"By"},"description":"Dimensions to group by: track, distance, box, grade. Comma-separate to cross them, e.g. track,distance or grade,track."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GreyhoundStatsOut"},"example":{"by":["track","grade"],"dog":"Tommy Blue","name_key":"tommyblue","overall":{"avg_first_split_s":6.71,"avg_sp":1.33,"avg_time_s":30.14,"best_time_s":30.14,"last_start":"2026-09-19","place_pct":100.0,"places":1,"starts":1,"win_pct":100.0,"wins":1},"rows":[{"avg_first_split_s":6.71,"avg_sp":1.33,"avg_time_s":30.14,"best_time_s":30.14,"grade":"M","last_start":"2026-09-19","place_pct":100.0,"places":1,"starts":1,"track":"Dubbo","win_pct":100.0,"wins":1}],"runner_refs":["grsa:441535"],"source":{"collectors":["fasttrack","grsa"],"coverage_from":"2026-06-23","coverage_to":"2026-09-20","feed":"puntersedge","refreshed_hourly":true,"region":"AU","results_from":"2026-09-13","sport":"greyhound"}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/greyhounds/field":{"get":{"tags":["Racing"],"summary":"Every dog in one greyhound race with its recent runs, from our own results","description":"Cost: 3 credits. Every dog in one greyhound race with its recent runs from our own results, in\none call: the field's form, not one dog's. Australian races and, from 12 October 2026, British\nand Irish ones.\n\n**Naming the race.** `race_id` from /v1/racing/next-to-go, /events or /results is exact. Failing\nthat, `venue` + `meeting_date` + `race_number`: the AET meeting date for an Australian race, the\nLondon date for a British or Irish one, the track matched by words there (\"Towcester\" finds\n\"Betway Towcester\"). Only greyhound races are matched, so a track that races dogs and trots on\nthe same day is not ambiguous (from 13 October 2026); two greyhound races sharing those three\nanswer 409 `race_ambiguous` and cost nothing.\n\n**The field.** While the race is open the runners come from the live card (`card_source: live`):\nrug number, trap, trainer and the card's form figures as the books publish them -- PointsBet on\nevery card, William Hill on the British and Irish ones -- with a dog the card has since scratched\nkept in the field and marked `scratched: true`. Once the race has been run and the live card\npurged, the field is the settled result's (`card_source: results`).\n\n**The runs.** Under each dog, its newest `runs` runs (default 10, at most 20) and a starts, wins\nand places summary over every run held, ONLY RUNS BEFORE THIS RACE'S START (`form_as_at`), so a\nrace already run answers with what was knowable at the jump and never with its own result. On an\nAustralian race the runs are the same store /v1/racing/greyhounds/form reads -- our own collected\nresults and the history behind them, with times, splits, weight, PIR and grade where the source\nprinted them. On a British or Irish race they are our own GB/IE results from PointsBet's settled\nrace card since 12 October 2026: the paid places carry a `position` (three on a six-dog field),\nevery other starter reads `ran` with `position` null -- not placed, which covers a dog that did\nnot finish, and `source.note` says so on every British or Irish answer -- and there are no times,\nsplits or grades;\n`source.region` reads `GB-IE` and `source.coverage_from` is that floor, so most dogs hold few or\nno runs yet -- British dogs race about weekly, and the record grows with every meeting we settle.\n`runs_total` counts STARTS (`status: ran`), the same figure as `stats.starts`, and\n`held_rows_total` every held row, scratchings and unused reserves included, which is what `runs`\nis drawn from; before 2026-10-13 `runs_total` counted every row.\n\n**Identity.** Dogs are grouped on `name_key`, the name's letters and digits, the same fold the\none-dog routes use; pass it as `dog` there for the full record. A race we cannot find is a free\n404, a race we hold no field for a free 404, an unknown query parameter a free 422. Greyhounds\nonly; thoroughbreds have /v1/racing/form. Cached 10 minutes; the Australian store refreshes\nhourly, the British and Irish runs are read live.","operationId":"greyhound_field_v1_racing_greyhounds_field_get","parameters":[{"name":"race_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"description":"The race_id from /v1/racing/next-to-go, /events or /results. The primary form; use venue + meeting_date + race_number only when you do not hold it.","title":"Race Id"},"description":"The race_id from /v1/racing/next-to-go, /events or /results. The primary form; use venue + meeting_date + race_number only when you do not hold it."},{"name":"venue","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"description":"Track name as the feeds spell it (case-insensitive; a British or Irish track is matched by words). Needs meeting_date and race_number.","title":"Venue"},"description":"Track name as the feeds spell it (case-insensitive; a British or Irish track is matched by words). Needs meeting_date and race_number."},{"name":"meeting_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Meeting date, YYYY-MM-DD: the AET date for an Australian race, the London date for a British or Irish one.","title":"Meeting Date"},"description":"Meeting date, YYYY-MM-DD: the AET date for an Australian race, the London date for a British or Irish one."},{"name":"race_number","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":30,"minimum":1},{"type":"null"}],"title":"Race Number"}},{"name":"runs","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"description":"Most recent runs to return per dog. The stats cover every held run regardless.","default":10,"title":"Runs"},"description":"Most recent runs to return per dog. The stats cover every held run regardless."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GreyhoundFieldOut"},"example":{"card_source":"live","distance_m":516,"form_as_at":"2026-09-19T07:22:00Z","meeting_date":"2026-09-19","race_id":"9b4e2b2e-1d7a-4b6a-9f1c-0f3c2a6e7d10","race_name":"Dubbo Grade 5","race_number":2,"race_status":"open","runners":[{"box":2,"form":"1","held_rows_total":1,"name":"Tommy Blue","name_key":"tommyblue","number":2,"runs":[{"box":2,"date":"2026-09-19","dead_heat":false,"distance_m":516,"field_size":5,"first_split_s":6.71,"grade":"M","number":2,"origin":"results","position":1,"race_name":"Dubbo Maiden","race_number":1,"result_id":"res_9f1c…","runner_ref":"grsa:441535","second_split_s":18.02,"source":"grsa","sp":1.33,"start_time":"2026-09-19T07:02:00Z","status":"ran","time_s":30.14,"trainer":"Raymond Tyquin","venue":"Dubbo","weight_kg":29.8,"win_time_s":30.14}],"runs_total":1,"scratched":false,"stats":{"place_pct":100.0,"places":1,"starts":1,"win_pct":100.0,"wins":1},"trainer":"Raymond Tyquin"}],"runners_total":1,"runners_with_runs":1,"runs_per_runner":10,"source":{"collectors":["fasttrack","grsa"],"coverage_from":"2026-06-23","coverage_to":"2026-09-20","feed":"puntersedge","refreshed_hourly":true,"region":"AU","results_from":"2026-09-13","sport":"greyhound"},"start_time":"2026-09-19T07:22:00Z","venue":"Dubbo"}}}},"404":{"description":"No race matched, or we hold no field for it. Nothing was charged."},"409":{"description":"Two greyhound races share that venue, date and number (a venue split). Nothing was charged."},"422":{"description":"The race was not named. Nothing was charged."},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/horses/form":{"get":{"tags":["Racing"],"summary":"Thoroughbred form, career record and profile for one horse","description":"Cost: 3 credits. One AU thoroughbred's recent form, career record and profile,\nparsed from Racing Australia's public horse page — the registry's own data.\n\nSOURCE AND FRESHNESS. The horse's page is read on demand and the parsed result stored\n(`source.fetched_at` dates it), so the first query for a horse pays a page-read to RA\n(typically under two seconds) and later reads are instant. A horse's form only\nchanges when it races or trials, so the stored copy is re-read from RA only when our\nown results archive shows the horse has started since it was read, or when the copy\nis more than seven days old (a barrier trial is invisible to results, so it surfaces\nwithin a week at worst). Inside six hours it is served without even that check. A\nhorse collected once, by you or by anyone, is therefore served from our store without\nanother RA read for as long as it stays unraced. When RA cannot be reached and a\nstored copy exists, that copy is served\nwith `source.stale: true` rather than an error. When RA is refusing reads (its bot\nwall) and no stored copy exists, the answer is 503 with a `Retry-After` header and\nfresh reads pause for that long; stored horses keep serving throughout. Only a 200\nis billed — a 404, 502 or 503 costs nothing.\n\nFRESH READS ARE CAPPED PER KEY AND PACED (2026-10-13). A fresh read is one that has to go\nto Racing Australia because we hold no current copy of the horse's page. Each API key may\nmake at most 30 fresh reads in any rolling 60 seconds, and fresh reads from every caller\ntogether leave at most one every 2.5 seconds (bursts of three); a read whose turn is more\nthan 20 seconds away is not held open. Past either limit nothing is sent to Racing\nAustralia and the answer is the one given while RA is refusing reads: a horse we hold is\nserved from the store, otherwise its runs in our own results where we have them, otherwise\na 503 with `Retry-After`, unbilled, whose detail says which limit it was. Why, measured to\n2026-10-12: over the previous 14 days Racing Australia's refusals closed fresh reads for\nevery caller 94 times, each refused read prolongs a closure, and one key alone sent 44\ncalls in two minutes.\n\nRESOLVING `horse`. Pass a name, or the `pe:` horse_ref that every thoroughbred surface\nnow publishes — they resolve identically, because the horse_ref is the folded name. An\n`ra:` code from `runner_ref` is accepted too, but prefer the name or horse_ref: Racing\nAustralia issues a new code for the same horse each day. Names resolve against\nhorses seen on our own feeds: acceptances roughly three days ahead, plus every archived\nresult runner (from 2026-09-01) — and, failing both, our own run store and stored form\npages, so a horse that last raced months ago still resolves (2026-10-03). Where we hold\nruns but no readable RA page, those runs are the answer (`source.feed`\n'puntersedge_results'). A name we have never seen returns 404 even when RA knows the\nhorse — there is deliberately no name-search against RA itself.\n\nONE NAME, ONE HORSE. A name resolves to the horse, and the most recently seen of its Racing\nAustralia codes is read — a horse that has raced more than once holds several codes,\nbecause Racing Australia issues a new one each day. `ambiguous` is always false and\n`candidates` always empty; both stay on the response for compatibility. Two different\nhorses sharing one folded name cannot be told apart here; the AU register does not allow\ntwo ACTIVE horses to share one (a name is released about 17 years after its horse retires).\n\n`horse_ref` ON THE RESPONSE is that identity, `pe:<name>`, and it is the same value on\n/v1/racing/results runners and placings, the live board, the closing-line and price-path\narchives, /v1/racing/horses/runs and the backfill. `runner_ref` is the code the page was\nread under; it changes from day to day. Join a horse on `horse_ref`; join the rows about\none race on `runner_ref`.\n\nTHE STORE IS KEYED ON THE HORSE. A page collected under ANY of the horse's codes — by you,\nby another customer, or by the paced backfill — answers every later lookup of that horse.\n\nREADING A RUN. Runs come newest first: the page's recent-form table, typically the\nlast ten starts plus barrier trials (`trial: true` — RA publishes those with $0\nprize, 0kg weights and no market). `race_class` is RA's official class string\n('MDN-SW', 'BM64', 'CTRY MDN'), the first place race class appears anywhere on this\nAPI. `prices` is the published price path oldest-quote-to-SP and `sp` its last\nentry. `margin_l` is lengths behind the winner — on the horse's own win, its winning\nmargin. `pir` is position in running at the 800m and 400m marks where published.\n`placegetters` names the two nearest finishers with their own ra: codes, so one\nhorse's page walks to its rivals'. Track names are RA's compressed codes ('W FM',\n'RAND') served as published. `trainer` on a run is the trainer OF THAT DAY, joined\nfrom our own results archive — populated for runs from 2026-09-01 onward and NULL\nbefore, because RA's form table names only the current trainer (`profile.trainer`);\nthe archive floor moves back nowhere, so the populated share grows with every\nmeeting.\n\nTHE CAREER BLOCK. RA's own summaries, not derived from the runs list: overall\nstarts/wins/seconds/thirds, prizemoney, and per-dimension splits (first_up,\nsecond_up, firm/good/soft/heavy/synthetic; track/distance splits appear when RA\nrenders them). These cover the whole career even where the runs list shows only\nrecent starts.\n\nJOINING TO LIVE DATA. `runner_ref` here, on /v1/racing/results runners and on enriched\nlive runners is the same identifier FOR ONE MEETING, so a pre-race model row joins to that\nday's official result and to the form read for it. Across meetings, join on `horse_ref`\ninstead — it is on all of those surfaces too and it does not change. `jockey_code` and\n`meet_code` are RA's own identifiers for the rider and the meeting, and those do persist.\n\nCached 10 minutes per query on top of the six-hour page store.","operationId":"horse_form_v1_racing_horses_form_get","parameters":[{"name":"horse","in":"query","required":true,"schema":{"type":"string","description":"Horse name, the `pe:` horse_ref published on every thoroughbred surface ('pe:custo'), or the ra: entry code from runner_ref on results/live runners. Names match case- and punctuation-insensitively on the whole name, and a name and its horse_ref resolve identically.","title":"Horse"},"description":"Horse name, the `pe:` horse_ref published on every thoroughbred surface ('pe:custo'), or the ra: entry code from runner_ref on results/live runners. Names match case- and punctuation-insensitively on the whole name, and a name and its horse_ref resolve identically."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HorseFormOut"},"example":{"ambiguous":false,"candidates":[],"career":{"prizemoney":4875,"seconds":0,"splits":{"first_up":{"seconds":0,"starts":2,"thirds":1,"wins":0},"good":{"seconds":0,"starts":2,"thirds":1,"wins":0}},"starts":3,"thirds":1,"wins":0},"horse_name":"Zweigelt","horse_ref":"pe:zweigelt","profile":{"age_sex":"4yo Bay Gelding","colours":"Lime, Purple And Gold Braces And Armbands","dam":"MEDGRINDA","dob":"2022-11-01","last_gear_change":"20-Aug-2026, Blinkers, First Time","sire":"VINO ROSSO (USA)","trainer":"Matthew Dunn","trainer_location":"Murwillumbah"},"runner_ref":"ra:MTMwNDY2ODI0NA","runs":[{"barrier":9,"date":"2026-08-18","distance_m":1430,"field_size":9,"going":"Soft","going_rating":7,"jockey":"Andrew Mallyon","jockey_code":"MTQ5MjQ5OTIw","margin_l":0.76,"meet_code":"MTI2NzM2MDQwMA","pir":[{"at_m":800,"position":7},{"at_m":400,"position":7}],"placegetters":[{"name":"Sunset Trail","position":1,"runner_ref":"ra:MTMwMDQ4MDk2MA","weight_kg":58.0}],"position":4,"prices":[7.5,4.0,4.8,5.0],"prize_total":27000,"prize_won":1300,"race_class":"MDN","race_number":4,"result_raw":"4th of 9","sectional_distance_m":600,"sectional_time_s":37.16,"sp":5.0,"time_s":86.82,"track":"GRAF","trial":false,"weight_kg":58.5}],"source":{"feed":"racing_australia","fetched_at":"2026-09-01T11:20:00+00:00","region":"AU","sport":"thoroughbred","stale":false}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/login/request":{"post":{"tags":["Account"],"summary":"Email yourself a one-time login link for the key console","description":"Always 202 with an identical body. Cost: 0 credits. Unauthenticated by necessity.","operationId":"login_request_v1_login_request_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequestIn"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"status":"accepted","message":"If that address has a verified PuntersEdge API account, a login link is on its way to it. The link works once and expires in 15 minutes."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/login/exchange":{"post":{"tags":["Account"],"summary":"Redeem a login link for a console session","operationId":"login_exchange_v1_login_exchange_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginExchangeIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"session_token":"pes_9d1c4b7a2f6e3081c5a497b2de60f3a1","expires_in":2592000,"email":"example@example.com","plan":"racing","message":"Signed in. Present the session_token in the X-API-Key header exactly like a key; it is tied to this account and expires in 30 days."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/logout":{"post":{"tags":["Account"],"summary":"End a console session","operationId":"logout_v1_logout_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"204":{"description":"Successful Response","content":{"application/json":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/keys/reveal":{"post":{"tags":["Account"],"summary":"Issue a fresh key and return it (signed-in console only)","operationId":"keys_reveal_v1_keys_reveal_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/RevealIn"},{"type":"null"}],"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"api_key":"pe_4f9c2a7e1b8d3c6a5e0f7b2d9c4a1e8f","key_version":3,"previous_key_valid_until":"2026-09-26T00:15:00Z","emailed":true,"message":"New key issued. Your previous key keeps working for 24 hours so anything running has time to roll over. A copy has been emailed to you."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/login":{"post":{"tags":["Account"],"summary":"Log in with your email and password","description":"Cost: 0 credits. Returns the same 30-day console session that the emailed link mints;\npresent `session_token` in the X-API-Key header exactly like a key. The failure reply is\nidentical whether the address is unknown, has no password yet, or the password is wrong.","operationId":"login_password_v1_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordLoginIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"session_token":"pes_3f9c1b7a2d4e5f60718293a4b5c6d7e8f9a0b1c2d3e4f5a6","expires_in":2592000,"email":"example@example.com","plan":"plus","password_set":true,"message":"Signed in. Present the session_token in the X-API-Key header exactly like a key; it expires in 30 days."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/password/forgot":{"post":{"tags":["Account"],"summary":"Email yourself a one-time password reset link","description":"Always 202 with an identical body — the same existence-oracle discipline as\n/v1/login/request. Cost: 0 credits. Unauthenticated by necessity. Works for an account that\nhas never set a password: the link is how an existing customer sets their first one.","operationId":"password_forgot_v1_password_forgot_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordIn"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"status":"accepted","message":"If that address has a verified PuntersEdge API account, a password reset link is on its way to it. The link works once and expires in 30 minutes."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/password/reset":{"post":{"tags":["Account"],"summary":"Choose a new password from a reset link","description":"Consumes the token (single use), sets the password, signs out every other device and\nreturns a fresh session so the page can land the customer straight in their account.","operationId":"password_reset_v1_password_reset_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"session_token":"pes_3f9c1b7a2d4e5f60718293a4b5c6d7e8f9a0b1c2d3e4f5a6","expires_in":2592000,"email":"example@example.com","plan":"plus","password_set":true,"message":"Password saved and you are signed in on this device. Any other signed-in devices were signed out."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/account/password":{"post":{"tags":["Account"],"summary":"Set or change your account password","description":"Cost: 0 credits. Works with a signed-in console session or the raw key. Setting a FIRST\npassword needs nothing else — the caller already proved the account by holding the key or by\nredeeming an emailed link. Changing an existing one requires `current_password`; forgotten,\nthe reset link from the login page is the path. Other devices are signed out; the session\nmaking this call (if any) stays signed in.","operationId":"account_set_password_v1_account_password_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPasswordIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"password_set":true,"changed":false,"other_sessions_signed_out":0,"message":"Password set. You can now log in at https://puntersedge.online/login with your email."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/account":{"get":{"tags":["Account"],"summary":"Your account: email, plan, password, billing and consent status","description":"Cost: 0 credits. What the account page needs in one call. `email` is the caller's own\naddress in full — it is their key or session asking. `billing_attached` says whether\n/v1/billing/portal has a subscription to open; `password_set` whether /v1/login works yet.","operationId":"account_me_v1_account_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"email":"example@example.com","email_masked":"ex***@example.com","plan":"plus","plan_display":"Plus","password_set":true,"password_set_at":"2026-09-28T04:12:09Z","billing_attached":true,"is_team_key":false,"team_keys_allowed":1,"created_at":"2026-08-14T09:30:11Z","key_version":1,"auth_via":"session","terms_accepted_at":"2026-08-14T09:30:11Z","marketing_opt_in":true,"marketing_changed_at":"2026-08-14T09:30:11Z","email_suppressed":false,"suppression_reason":null,"login_url":"https://puntersedge.online/login","account_url":"https://puntersedge.online/account"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/account/marketing":{"post":{"tags":["Account"],"summary":"Turn promotional emails on or off for your account","description":"Cost: 0 credits. Appends a consent record — the account page's toggle.\nTurning it ON from a signed-in account also lifts an `unsubscribed` suppression on the\naddress (never a bounce or a provider block): the person is asking, in their own account, to\nhear from us again. Messages about the account and key are unaffected either way.","operationId":"account_marketing_v1_account_marketing_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketingIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"terms_accepted_at":"2026-08-14T09:30:11Z","terms_source":"signup:free","marketing_opt_in":false,"marketing_changed_at":"2026-09-28T04:40:02Z","marketing_source":"console","email_suppressed":false,"suppression_reason":null,"message":"You will not receive promotional emails. Messages about your account and key still arrive."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/logout/all":{"post":{"tags":["Account"],"summary":"Sign out of every device","description":"Revokes every console session on the account, including this one (2026-09-28). The API\nkey itself is untouched — rotate it from the console if it is the key you suspect.","operationId":"logout_all_v1_logout_all_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"204":{"description":"Successful Response","content":{"application/json":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/horses/backfill":{"post":{"tags":["Racing"],"summary":"Queue a batch of horses for paced form collection","description":"Cost: free to submit. 3 credits per horse when it is delivered.\n\nHand us a list of horses and we collect their Racing Australia form at a pace RA\ntolerates, then hold it for you. This is the supported way to backfill a model:\nlooping /v1/racing/horses/form yourself will trip RA's bot protection long before a\nfew thousand horses, and the 503s that follow are shared with every other customer.\n\nNames resolve the same way they do on the form endpoint — against acceptances (about\nthree days ahead) and every archived result runner — and so do the `pe:` horse_refs\npublished on results, the live board, the archives and /v1/racing/horses/runs, which is\nthe spelling to submit if you are working from those. A horse we have never seen comes\nstraight back in `not_found` rather than being queued, and costs nothing. `ambiguous` is\nalways empty: a name resolves to one horse (several Racing Australia codes under one name\nare the same horse's codes from different days, not several horses).\n\nHorses we already hold a fresh copy of skip the queue and are ready at once — matched on\nthe HORSE, so a page collected under any of its codes counts and no read of Racing\nAustralia is spent re-fetching it. A horse already queued for you is not queued twice.\n\nPoll GET /v1/racing/horses/backfill/{job_id} for progress and collect from\n/results as rows complete — you do not have to wait for the whole batch.","operationId":"submit_backfill_v1_racing_horses_backfill_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackfillSubmitIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackfillSubmitOut"},"example":{"job_id":"bf_20260907_3a9c1e","label":"Saturday metro fields","queued":38,"already_stored":6,"duplicates":0,"not_found":["Fabulos Fiano"],"ambiguous":[],"estimated_ready":"2026-09-07T01:35:00Z"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/horses/backfill/{job_id}":{"get":{"tags":["Racing"],"summary":"Progress of a backfill job","description":"Cost: free. Poll as often as your rate limit allows.\n\n`credits_blocked` is the one field worth watching: the worker bills 3 credits per\nhorse as it delivers it, and when your balance drops below that it stops taking rows\nfor your key and leaves them queued. Nothing is lost — collection resumes when your\ncredits reset on the 1st, or immediately if you upgrade.\n\n`upstream` says whether Racing Australia is answering form reads at all. `state: walled`\nmeans RA is serving its bot-protection challenge to every fresh read, so NO horse can be\ncollected — by us or by anyone — until that stops; `walled_since` and `walled_hours` say\nhow long it has been going on, which is the difference between a queue that is slow and a\nqueue that is stopped. Your rows stay queued and unbilled throughout and collection\nresumes on its own; there is nothing to re-submit. While it lasts,\nGET /v1/racing/horses/runs answers from our own settled results instead and needs no\nRacing Australia read at all. The block is additive: every other field, and all billing\nand queue behaviour, is exactly as it was before 2026-09-21.","operationId":"backfill_status_v1_racing_horses_backfill__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackfillStatusOut"},"example":{"job_id":"bf_20260907_3a9c1e","submitted_at":"2026-09-07T00:52:10Z","total":38,"done":21,"queued":17,"failed":0,"credits_charged":63,"credits_blocked":false,"queue_position":0,"estimated_ready":"2026-09-07T01:35:00Z","ready_to_collect":21,"upstream":{"state":"walled","walled_since":"2026-09-19T15:00:00Z","walled_hours":41.2,"note":"Racing Australia is serving its bot-protection challenge to every fresh form read, so no new horse can be collected until it stops; your queued horses are untouched and unbilled, collection resumes on its own with no action from you, and for what we already hold on these horses today use GET /v1/racing/horses/runs."}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"tags":["Racing"],"summary":"Cancel the horses a job has not collected yet","description":"Cost: free. Drops this job's queued rows. Delivered horses stay collectable, and\nnothing already billed is re-billed.","operationId":"cancel_backfill_v1_racing_horses_backfill__job_id__delete","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Cancel Backfill V1 Racing Horses Backfill  Job Id  Delete"},"example":{"job_id":"bf_20260907_3a9c1e","cancelled":17}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/horses/backfill/{job_id}/results":{"get":{"tags":["Racing"],"summary":"Collect the form of horses this job has delivered","description":"Cost: free. The 3 credits per horse were charged when it was delivered, and\nre-reading data you have already paid for costs nothing.\n\nEach row is the same payload /v1/racing/horses/form returns for that horse — profile,\ncareer, runs — so anything written against the form endpoint reads this unchanged.\nRows appear as they complete: collect early and often rather than waiting for the\nwhole batch.\n\n`horse_ref` (`pe:<name>`, added 2026-09-21) is the horse this row is about, and it is the\nsame value on /v1/racing/results runners and placings, the live board, the closing-line\nand price-path archives, /v1/racing/horses/form and /v1/racing/horses/runs — so a\ncollected batch joins straight onto your price rows without matching names. `runner_ref`\nbeside it is Racing Australia's code for the entry we read, which changes from meeting to\nmeeting and does not identify the horse.","operationId":"backfill_results_v1_racing_horses_backfill__job_id__results_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":25,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Backfill Results V1 Racing Horses Backfill  Job Id  Results Get"},"example":{"job_id":"bf_20260907_3a9c1e","rows_returned":1,"total_done":21,"limit":50,"offset":0,"rows":[{"input":"Vomo Island","runner_ref":"ra:NzkyMzc2NTE1MA","horse_ref":"pe:vomoisland","horse_name":"Vomo Island","delivered_at":"2026-09-07T00:58:41Z","fetched_at":"2026-09-07T00:58:39Z","available":true,"form":{"horse_name":"Vomo Island","horse_ref":"pe:vomoisland","runner_ref":"ra:NzkyMzc2NTE1MA","ambiguous":false,"candidates":[],"profile":{"age_sex":"5yo Bay Gelding","trainer":"John Lugg","trainer_location":"Kalgoorlie"},"career":{"starts":24,"wins":5,"seconds":4,"thirds":3,"prizemoney":118450},"runs":[{"date":"2026-09-06","track":"KALG","race_number":8,"trial":false,"position":1,"field_size":14,"distance_m":1200,"going":"Good","going_rating":4,"jockey":"Austin Galati","weight_kg":57.0,"barrier":3,"sp":4.6,"prices":[5.0,4.8,4.6],"margin_l":0.0,"pir":[],"placegetters":[]}],"source":{"feed":"racing_australia","region":"AU","sport":"thoroughbred","fetched_at":"2026-09-07T00:58:39+00:00","stale":false}}}]}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/horses/runs":{"get":{"tags":["Racing"],"summary":"One thoroughbred's runs in our own settled results","description":"Cost: 3 credits. Every run we hold for one AU thoroughbred, newest first, out of our own\nsettled results.\n\nTHIS IS NOT A CAREER RECORD. Each run here is a race WE collected and settled: the same rows\n/v1/racing/results serves, flattened one-per-horse. The store begins at the first complete card\non `source.coverage_from` and `source.coverage_to` is the latest race in it, so `runs_total` is\nthis horse's record SINCE THAT DATE, not its lifetime. The same run never changes once written,\nso the record only grows — build a longer history forward from here.\n\nWHY YOU WOULD USE IT INSTEAD OF /v1/racing/horses/form. That endpoint is Racing Australia's own\nform page: deeper (career splits, sectionals, position-in-running, the price path to SP) but\ndependent on RA serving us the page. Since 2026-09-21 RA's edge answers every fresh form read\nwith a human-verification challenge, so horses we have not already stored cannot be read at\nall, and we do not automate that challenge. This endpoint has no such dependency — it reads\nonly what we already hold — and it is the honest answer to \"what do you have on this horse\ntoday\". Use both: form for depth when RA lets us in, runs for availability always.\n\nRESOLVING `horse` — PASS THE NAME, OR ITS `horse_ref`. A name is folded to letters and digits\n(the trailing country parenthetical stripped first) and must match in full — the same rule\n/v1/racing/horses/form applies, so one name resolves identically on both. `pe:custo`, the\n`horse_ref` published on every thoroughbred runner, placing, live-board entry and archive row,\nis the same fold already done and resolves to exactly the same runs. A Racing Australia\nhorsecode (`ra:MjgwNjM0ODg2NDA`, or the bare code) is accepted too, but it finds one run\nrather than a horse: Racing Australia issues a new code for the same horse each day, so a code\nfilter returns the one run that carries it. `runner_refs` lists every code published under the\nname; more than one is normal. The other side of grouping on a name is\nthat a registered name reused by two horses returns the union of both. A horse we hold no run\nfor is a 404 and costs nothing; an unknown query parameter is a 422 and costs nothing.\n\nREADING A RUN. `position` is the finishing place, null when the horse did not start (`status`\nis `scratched`) or started and did not finish — `abnormal` is then RA's own reason cell,\nverbatim and from no closed list. `sp` is the official starting price and `sp_note` RA's\nfavouritism marker; null `sp` means unknown, never zero. `margin` is RA's own text in lengths.\n`time_s` is the WINNER's time only: RA prints one time per race, in its race header, and no\nper-runner time in the results table, so since 29 September 2026 a position-1 run carries the\nrace time and every other run's `time_s` is null. Every run of a race also carries the race-level\n`race_time_s` and `last_600m_s` (RA's header, seconds) and `prize_total`, and each placed run its\n`prize_won` off RA's prize line (dead heats share the places they tie for; 0 outside the paid\nplaces); all four are null on runs stored before that date until the meeting is re-read, and\nbefore 29 September no run carried any time (0 of 17,344 stored runs, measured 2026-09-28).\n`trainer` and `jockey` are the ones of that day, which is\nwhat the result published — `profile.trainer` on the form endpoint is the CURRENT trainer.\n`field_size` counts the horses that ran. `weight_kg` is the allotted weight and `carried_kg`\nthe weight actually carried when RA printed one (a claim under it, or an overweight rider over it); `race_class` is RA's own leading\nconditions term ('Maiden', 'BenchMark 64'). All three are kept since 28 September 2026. On\n`origin: tabtouch` runs, and on runs from 10 August to 27 September 2026 whose result lacked\nthem, `race_class` and `weight_kg` come from TABtouch's race page instead: its own class wording\n('BM58 HCP', 'MDN-SW') and its printed weight, with `carried_kg` null. `result_id` joins back to /v1/racing/results, except on `origin: history` and `origin: tabtouch` runs, whose synthetic 'hist:...' id joins to nothing.\n\n`stats` is computed over every held run, not just the `limit` returned, and counts only starts\n(`status: ran`). `runs_total` is that same count of starts; `held_rows_total` is every held row,\nscratchings included, and `runs` is the newest `limit` of those rows -- compare `len(runs)` with\n`held_rows_total` to know whether you have them all. Before 2026-10-13 `runs_total` counted the\nscratchings too.\n\nCached 10 minutes per query; the store itself refreshes hourly at :59.","operationId":"horse_runs_v1_racing_horses_runs_get","parameters":[{"name":"horse","in":"query","required":true,"schema":{"type":"string","description":"Horse NAME (matched on letters and digits, case- and punctuation-insensitive, whole name), or the `horse_ref` any thoroughbred surface publishes ('pe:custo') — either gathers the whole record, and they resolve identically. A Racing Australia horsecode from `runner_ref` ('ra:MjgwNjM0ODg2NDA' or the bare code) is accepted too, but it pins one results entry, usually one meeting's runs, not the horse.","title":"Horse"},"description":"Horse NAME (matched on letters and digits, case- and punctuation-insensitive, whole name), or the `horse_ref` any thoroughbred surface publishes ('pe:custo') — either gathers the whole record, and they resolve identically. A Racing Australia horsecode from `runner_ref` ('ra:MjgwNjM0ODg2NDA' or the bare code) is accepted too, but it pins one results entry, usually one meeting's runs, not the horse."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Most recent runs to return.","default":20,"title":"Limit"},"description":"Most recent runs to return."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HorseRunsOut"},"example":{"horse":"Belle Reine","name_key":"bellereine","horse_ref":"pe:bellereine","runner_refs":["ra:Mjk3NDcwNzQ4OTY","ra:NDIzMjQxMjA5OTI"],"runs_total":1,"held_rows_total":3,"runs":[{"date":"2026-09-29","start_time":"2026-09-29T03:35:00Z","venue":"Grafton","race_number":2,"distance_m":1006,"race_name":"Book A Christmas Party @ Crjc Maiden","track_condition":"Soft (6)","number":12,"box":11,"position":5,"status":"ran","abnormal":null,"margin":"3.02L","time_s":null,"sp":91.0,"sp_note":null,"dead_heat":false,"trainer":"Ethan Ensby","jockey":"Ms Violet Soulsby","field_size":12,"weight_kg":55.0,"carried_kg":53.0,"race_class":"Maiden","race_time_s":59.27,"last_600m_s":35.84,"prize_total":27000,"prize_won":825.0,"runner_ref":"ra:NDIzMjQxMjA5OTI","horse_ref":"pe:bellereine","result_id":"d01a811c-0b45-41c9-86c8-35b06d46e07f","origin":"results"},{"date":"2026-09-21","start_time":"2026-09-21T04:50:00Z","venue":"Grafton","race_number":3,"distance_m":1190,"race_name":"BOOK A CHRISTMAS PARTY @ CRJC MAIDEN HANDICAP","track_condition":"Soft (5)","number":10,"box":null,"position":null,"status":"scratched","abnormal":null,"margin":null,"time_s":null,"sp":null,"sp_note":null,"dead_heat":false,"trainer":"Ethan Ensby","jockey":null,"field_size":10,"weight_kg":null,"carried_kg":null,"race_class":null,"race_time_s":null,"last_600m_s":null,"prize_total":null,"prize_won":null,"runner_ref":"ra:Mjk3NDcwNzQ4OTY","horse_ref":"pe:bellereine","result_id":"3ca2049e-4339-45fd-bb36-3c34a52aad71","origin":"results"},{"date":"2026-09-21","start_time":"2026-09-21T04:10:00Z","venue":"Grafton","race_number":2,"distance_m":1015,"race_name":"CRJC AGM 25 NOVEMBER COUNTRY BOOSTED MAIDEN PLATE","track_condition":"Soft (5)","number":17,"box":null,"position":null,"status":"scratched","abnormal":null,"margin":null,"time_s":null,"sp":null,"sp_note":null,"dead_heat":false,"trainer":"Ethan Ensby","jockey":null,"field_size":12,"weight_kg":null,"carried_kg":null,"race_class":null,"race_time_s":null,"last_600m_s":null,"prize_total":null,"prize_won":null,"runner_ref":"ra:Mjk3NDcwNzQ4OTY","horse_ref":"pe:bellereine","result_id":"09d4a64e-3c05-498c-abb5-dd95f0bb812f","origin":"results"}],"stats":{"starts":1,"wins":0,"places":0,"win_pct":0.0,"place_pct":0.0},"source":{"feed":"puntersedge","region":"AU","sport":"thoroughbred","coverage_from":"2026-08-09","coverage_to":"2026-09-29","collectors":["racing_australia"],"refreshed_hourly":true}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/form":{"get":{"tags":["Racing"],"summary":"Every runner in one race with its recent runs, from our own results","description":"Cost: 3 credits. Every runner in one race with its recent runs from our own settled AU\nthoroughbred results, in one call: the field's form, not one horse's.\n\n**Naming the race.** `race_id` from /v1/racing/next-to-go, /events, /acceptances or /results is\nexact. Failing that, `venue` + `meeting_date` (the AET meeting date) + `race_number`. Only\nthoroughbred races are matched, so a track that races gallops beside dogs or trots on the same day\nis not ambiguous (from 13 October 2026); two thoroughbred races sharing those three answer 409\n`race_ambiguous` and cost nothing. A race a bookmaker has not\nframed yet has no race_id (every AU book frames a race 16 hours before its jump), so the evening\nbefore a meeting name it by venue + meeting_date + race_number exactly as /v1/racing/acceptances\nlists it: the answer comes from Racing Australia's acceptance card (`card_source: acceptances`,\n`race_id` null), and the whole of tomorrow's card can be read the night before.\n\n**The field.** While the race is open the runners come from the live card (`card_source: live`):\nsaddlecloth, barrier, jockey, trainer and allotted weight as the books and Racing Australia's\nacceptances publish them, with a horse the card has since scratched kept in the field and marked\n`scratched: true`. Once the race has been run and the live card purged, the field is the settled\nresult's (`card_source: results`). Until then Racing Australia's acceptance card is folded in\n(`acceptances_folded`): every accepted runner, emergencies aside, is in the field even before the\nbooks list it, named as RA names it, with RA's `last10` and `last_start_date`.\n\n**The runs.** Under each runner, its newest `runs` runs (default 10, at most 20) out of the same\nstore /v1/racing/horses/runs reads -- our own collected results plus the history collected behind\nthem -- and a starts, wins and places summary over every run held. Each run carries date, venue,\nrace, distance, class, track condition, saddlecloth, barrier, position, margin, official SP,\nweight, jockey, trainer and field size, and since 29 September 2026 the race's winning time and\nlast-600m time, its total prize money and the place money this runner won (the winner's\n`time_s` is the race time; RA publishes no other runner's time). ONLY RUNS BEFORE THIS RACE'S START are included\n(`form_as_at`), so a race already run answers with what was knowable at the jump and never with\nits own result. A runner with no run held is still in the field, with `runs: []` and\n`runs_total: 0`; `source.coverage_from` is the floor of the store, and nothing earlier exists here.\n`runs_total` counts STARTS (`status: ran`), the same figure as `stats.starts`, and\n`held_rows_total` counts every held row, scratchings included, which is what `runs` is drawn\nfrom: a horse scratched before its debut reads `runs_total: 0` with its scratchings in `runs`.\nBefore 2026-10-13 `runs_total` counted the scratchings too.\n\n**Identity.** Runners are gathered on `horse_ref` (`pe:<name>`), the fold every thoroughbred\nsurface publishes. `runner_ref` is the Racing Australia code the card carries for this runner,\non race day the join to /v1/racing/next-to-go, /v1/racing/acceptances and /v1/racing/results;\nit is not a horse identity, because Racing Australia issues a new code for the same horse each day.\n\n**British and Irish races (from 2026-10-12).** The same call answers a GB or IE thoroughbred race.\nThe field is the UK bookmakers' card as we hold it -- William Hill's and Betway's rows carry the\nstall draw (`barrier`), rider, trainer, weight in kg, the form figures (`last10`), the horse's\n`age` and, from William Hill, the Racing Post `rating` -- and, once run, Betway's settled result.\nNo acceptance card exists for these races, so `acceptances_folded` is always false. The runs are\nour own GB/IE results from Betway's settlement feed since 6 October 2026: position, SP and field\nsize, no times or prize money, and a starter Betway never ranked once the result was final reads\n`abnormal: did not finish`; `source.region` reads `GB-IE`, `source.coverage_from` is that\nfloor, and most horses hold few or no runs yet -- the record grows with every meeting we settle.\n`meeting_date` and each run's `date` are the London calendar date, and when you name a GB/IE race\nby venue + meeting_date + race_number, meeting_date is the London date and the course is matched\nby words (\"Kempton\" finds \"Kempton Park\").\n\nNot a career record: this is what we hold, honestly bounded, and it grows with every meeting we\ncollect. For Racing Australia's own form page (career splits, sectionals, price paths) use\n/v1/racing/horses/form, one horse at a time. A race we cannot find is a free 404, a race we hold\nno field for a free 404, an unknown query parameter a free 422. Thoroughbreds only; greyhounds\nhave /v1/racing/greyhounds/form. Cached 10 minutes; the AU store refreshes hourly, the GB/IE runs\nare read live.","operationId":"racing_form_v1_racing_form_get","parameters":[{"name":"race_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"description":"The race_id from /v1/racing/next-to-go, /events, /acceptances or /results. The primary form; use venue + meeting_date + race_number only when you do not hold it.","title":"Race Id"},"description":"The race_id from /v1/racing/next-to-go, /events, /acceptances or /results. The primary form; use venue + meeting_date + race_number only when you do not hold it."},{"name":"venue","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"description":"Venue name as the feeds spell it (case-insensitive). Needs meeting_date and race_number.","title":"Venue"},"description":"Venue name as the feeds spell it (case-insensitive). Needs meeting_date and race_number."},{"name":"meeting_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"AET meeting date, YYYY-MM-DD.","title":"Meeting Date"},"description":"AET meeting date, YYYY-MM-DD."},{"name":"race_number","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":20,"minimum":1},{"type":"null"}],"title":"Race Number"}},{"name":"runs","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"description":"Most recent runs to return per runner. The stats cover every held run regardless.","default":10,"title":"Runs"},"description":"Most recent runs to return per runner. The stats cover every held run regardless."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldFormOut"},"example":{"race_id":"ca43a1a8-f58e-491e-bc88-5d556c7bf1f7","venue":"Grafton","race_number":2,"start_time":"2026-09-29T03:35:00Z","meeting_date":"2026-09-29","race_name":"Book A Christmas Party @ Crjc Maiden","distance_m":1006,"track_condition":"Soft (6)","race_status":"final","card_source":"results","form_as_at":"2026-09-29T03:35:00Z","runs_per_runner":10,"runners_total":13,"runners_with_runs":10,"runners":[{"number":1,"name":"Dream Invader","horse_ref":"pe:dreaminvader","runner_ref":"ra:NDI0MTMzNzM3MTI","barrier":5,"jockey":"Ben Looker","trainer":"Allan Kehoe","weight_kg":59.0,"scratched":false,"runs_total":1,"held_rows_total":1,"stats":{"starts":1,"wins":0,"places":1,"win_pct":0.0,"place_pct":100.0},"runs":[{"date":"2026-09-13","start_time":"2026-09-13T04:30:00Z","venue":"Coffs Harbour","race_number":3,"distance_m":1014,"race_name":"MORSE LEGACY SUPER MAIDEN HANDICAP","track_condition":"Soft (5)","number":1,"box":5,"position":3,"status":"ran","abnormal":null,"margin":"1.8L","time_s":null,"sp":13.0,"sp_note":null,"dead_heat":false,"trainer":"Allan Kehoe","jockey":"Ms Olivia Chambers","field_size":10,"weight_kg":null,"carried_kg":null,"race_class":null,"race_time_s":null,"last_600m_s":null,"prize_total":null,"prize_won":null,"runner_ref":"ra:MTc4OTQ0ODYyNzI","horse_ref":"pe:dreaminvader","result_id":"e5566b7e-eb0a-46a9-aefc-637a9a447d99","origin":"results"}]},{"number":2,"name":"Exo Murano","horse_ref":"pe:exomurano","runner_ref":"ra:NDIxOTYxMDY2NDA","barrier":8,"jockey":"Archie McColm","trainer":"Stephen & Jordan Lee","weight_kg":59.0,"scratched":false,"runs_total":1,"held_rows_total":2,"stats":{"starts":1,"wins":0,"places":0,"win_pct":0.0,"place_pct":0.0},"runs":[{"date":"2026-09-21","start_time":"2026-09-21T04:10:00Z","venue":"Grafton","race_number":2,"distance_m":1015,"race_name":"CRJC AGM 25 NOVEMBER COUNTRY BOOSTED MAIDEN PLATE","track_condition":"Soft (5)","number":13,"box":null,"position":null,"status":"scratched","abnormal":null,"margin":null,"time_s":null,"sp":null,"sp_note":null,"dead_heat":false,"trainer":"Stephen & Jordan Lee","jockey":null,"field_size":12,"weight_kg":null,"carried_kg":null,"race_class":null,"race_time_s":null,"last_600m_s":null,"prize_total":null,"prize_won":null,"runner_ref":"ra:Mjk2NTcxMDEzMjA","horse_ref":"pe:exomurano","result_id":"09d4a64e-3c05-498c-abb5-dd95f0bb812f","origin":"results"},{"date":"2026-09-10","start_time":"2026-09-10T04:35:00Z","venue":"Lismore","race_number":3,"distance_m":1110,"race_name":"LISMORE FLOOR COVERINGS CG&E MAIDEN PLT","track_condition":"Soft (5)","number":18,"box":2,"position":6,"status":"ran","abnormal":null,"margin":"7.98L","time_s":null,"sp":51.0,"sp_note":null,"dead_heat":false,"trainer":"Stephen & Jordan Lee","jockey":"Ms Siabh Rigley","field_size":10,"weight_kg":null,"carried_kg":null,"race_class":null,"race_time_s":null,"last_600m_s":null,"prize_total":null,"prize_won":null,"runner_ref":"ra:MTM1MzM5OTQ2NTA","horse_ref":"pe:exomurano","result_id":"1e4e3591-2f0f-41d6-b0f9-8caea3a92dad","origin":"results"}]}],"source":{"feed":"puntersedge","region":"AU","sport":"thoroughbred","coverage_from":"2026-08-09","coverage_to":"2026-09-29","collectors":["racing_australia"],"refreshed_hourly":true}}}}},"404":{"description":"No race matched, or we hold no field for it. Nothing was charged."},"409":{"description":"Two thoroughbred races share that venue, date and number (a venue split). Nothing was charged."},"422":{"description":"The race was not named. Nothing was charged."},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/jockeys/stats":{"get":{"tags":["Racing"],"summary":"Jockey premiership leaderboards and lookup","description":"Cost: 2 credits. Jockey premiership leaderboards — 1st-5th finishes, prize\n    money, strike rate and starts — from Racing Australia's own published tables.\n\n    WHAT A LEADERBOARD IS. RA publishes each premiership as a TOP-20 table, one per\n    state and scope, and that is exactly what is held: the national table (`state=AUS`)\n    and the eight state tables, each split all/metro/provincial/country/picnic, for the\n    running season and the previous one (RA seasons run 1 Aug - 31 Jul). There is no\n    page two at the source, so a jockey outside every top-20 has no row here — an empty\n    `rows` for a name means \"outside the published top 20\", never \"inactive\". For\n    per-horse jockey identity on individual runs, /v1/racing/horses/form carries it\n    with a stable code on every run.\n\n    LOOKUP SHAPES. With no `name`, the response is one table: `state` (default AUS) x\n    `scope` (default all) for the chosen `season`. With `name`, the search widens to\n    every state and scope for that season by default — a rider prominent only in one\n    state's country scope is found without knowing which — and `state`/`scope`, if\n    given, narrow it back. Name matching is case-insensitive on the whole name or any\n    part of it.\n\n    JOINS. `ra_code` is the code inside RA's own engagement links, stored with padding\n    stripped — the same identity namespace as `jockey_code` on horse-form runs and\n    `trainer_code` on acceptance rows, so premiership standing joins to per-run data on\n    code, not name spelling.\n\n    HOW FRESH ANY GIVEN TABLE IS. There are 180 of these tables and Racing Australia\n    will not serve them all in one sitting, so they are refreshed in paced passes\n    overnight Australian time: the national tables (`state=AUS`) daily, and the state\n    tables on a rolling refresh that takes a few nights to come round. Nothing is\n    guessed or interpolated in between — a table is exactly as RA last published it.\n    Read the age off the response rather than assuming it: `period_to` on the running\n    season is the day RA rendered that table, so it dates every row, and\n    `source.scraped_at` gives the newest scrape among the rows returned. A state table\n    added to the store for the first time simply appears; until then its `rows` are\n    empty, the same shape as a name that is outside every top 20.\n\n    `season=previous` serves the completed season, useful as the longer baseline of the\n    two. It changes only on a late protest, so it is refreshed about weekly.\n\n    Cached 1 hour.","operationId":"jockey_stats_v1_racing_jockeys_stats_get","parameters":[{"name":"name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Find one jockey by (partial) name across every state and scope.","title":"Name"},"description":"Find one jockey by (partial) name across every state and scope."},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"AUS (national) or a state. Default AUS, unless `name` widens the search.","title":"State"},"description":"AUS (national) or a state. Default AUS, unless `name` widens the search."},{"name":"scope","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"all, metro, provincial, country, picnic. Default all, unless `name` widens the search.","title":"Scope"},"description":"all, metro, provincial, country, picnic. Default all, unless `name` widens the search."},{"name":"season","in":"query","required":false,"schema":{"type":"string","description":"current or previous RA season (1 Aug - 31 Jul).","default":"current","title":"Season"},"description":"current or previous RA season (1 Aug - 31 Jul)."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PremiershipOut"},"example":{"role":"jockey","season_start_year":2026,"state":"AUS","scope":"all","name":null,"rows":[{"state":"AUS","scope":"all","rank":1,"name":"William Pike","ra_code":"OTI5MTgwMDAw","firsts":24.0,"seconds":13.0,"thirds":7.0,"fourths":8.0,"fifths":8.0,"prize_money":1115623.0,"strike_rate":26.7,"starts":90,"period_from":"2026-08-01","period_to":"2026-09-07"},{"state":"AUS","scope":"all","rank":2,"name":"Zac Lloyd","ra_code":"MzY0MzEwNjc3NTA","firsts":22.0,"seconds":13.0,"thirds":7.0,"fourths":8.0,"fifths":6.0,"prize_money":1772645.0,"strike_rate":30.6,"starts":72,"period_from":"2026-08-01","period_to":"2026-09-07"}],"source":{"feed":"racing-australia-premierships","region":"AU","sport":"thoroughbred","scraped_at":"2026-09-06T19:40:44+00:00","top_n":20}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/trainers/stats":{"get":{"tags":["Racing"],"summary":"Trainer premiership leaderboards and lookup","description":"Cost: 2 credits. Trainer premiership leaderboards — 1st-5th finishes, prize\n    money, strike rate and starts — from Racing Australia's own published tables.\n\n    WHAT A LEADERBOARD IS. RA publishes each premiership as a TOP-20 table, one per\n    state and scope, and that is exactly what is held: the national table (`state=AUS`)\n    and the eight state tables, each split all/metro/provincial/country/picnic, for the\n    running season and the previous one (RA seasons run 1 Aug - 31 Jul). There is no\n    page two at the source, so a trainer outside every top-20 has no row here — an empty\n    `rows` for a name means \"outside the published top 20\", never \"inactive\". For\n    per-horse trainer identity on individual runs, /v1/racing/horses/form carries it\n    with a stable code on every run.\n\n    LOOKUP SHAPES. With no `name`, the response is one table: `state` (default AUS) x\n    `scope` (default all) for the chosen `season`. With `name`, the search widens to\n    every state and scope for that season by default — a rider prominent only in one\n    state's country scope is found without knowing which — and `state`/`scope`, if\n    given, narrow it back. Name matching is case-insensitive on the whole name or any\n    part of it.\n\n    JOINS. `ra_code` is the code inside RA's own engagement links, stored with padding\n    stripped — the same identity namespace as `jockey_code` on horse-form runs and\n    `trainer_code` on acceptance rows, so premiership standing joins to per-run data on\n    code, not name spelling.\n\n    HOW FRESH ANY GIVEN TABLE IS. There are 180 of these tables and Racing Australia\n    will not serve them all in one sitting, so they are refreshed in paced passes\n    overnight Australian time: the national tables (`state=AUS`) daily, and the state\n    tables on a rolling refresh that takes a few nights to come round. Nothing is\n    guessed or interpolated in between — a table is exactly as RA last published it.\n    Read the age off the response rather than assuming it: `period_to` on the running\n    season is the day RA rendered that table, so it dates every row, and\n    `source.scraped_at` gives the newest scrape among the rows returned. A state table\n    added to the store for the first time simply appears; until then its `rows` are\n    empty, the same shape as a name that is outside every top 20.\n\n    `season=previous` serves the completed season, useful as the longer baseline of the\n    two. It changes only on a late protest, so it is refreshed about weekly.\n\n    Cached 1 hour.","operationId":"trainer_stats_v1_racing_trainers_stats_get","parameters":[{"name":"name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Find one trainer by (partial) name across every state and scope.","title":"Name"},"description":"Find one trainer by (partial) name across every state and scope."},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"AUS (national) or a state. Default AUS, unless `name` widens the search.","title":"State"},"description":"AUS (national) or a state. Default AUS, unless `name` widens the search."},{"name":"scope","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"all, metro, provincial, country, picnic. Default all, unless `name` widens the search.","title":"Scope"},"description":"all, metro, provincial, country, picnic. Default all, unless `name` widens the search."},{"name":"season","in":"query","required":false,"schema":{"type":"string","description":"current or previous RA season (1 Aug - 31 Jul).","default":"current","title":"Season"},"description":"current or previous RA season (1 Aug - 31 Jul)."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PremiershipOut"},"example":{"role":"trainer","rows":[{"fifths":14.0,"firsts":17.0,"fourths":22.0,"name":"Chris Waller","period_from":"2026-08-01","period_to":"2026-09-02","prize_money":2437350.0,"ra_code":"Njg0MjM2MA","rank":1,"scope":"all","seconds":23.5,"starts":147,"state":"NSW","strike_rate":11.6,"thirds":20.0}],"scope":"all","season_start_year":2026,"source":{"feed":"racing-australia-premierships","region":"AU","scraped_at":"2026-09-02T07:10:00+00:00","sport":"thoroughbred","top_n":20},"state":"NSW"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/changes":{"get":{"tags":["Racing"],"summary":"Races and prices that changed since a timestamp","description":"Cost: 2 credits. Everything that moved on the live racing board since `since` — one\npoll instead of re-downloading the whole card to diff it yourself. Never cached; serving\na cached delta would hand you changes you already have and hide the ones you do not.\n\n**Poll contract.** Call with a recent `since`, then send the `server_time` from each\nresponse as the `since` of the next call. `server_time` is deliberately set back 30\nseconds from the server clock: ingest stamps a price change in Python and commits the\nbatch afterwards, so a cursor taken at the exact clock would step over a write that was\nin flight and lose that price change permanently. The setback covers two racing poll\ncycles, which makes this feed AT LEAST ONCE — a change may arrive twice, never zero\ntimes. Deduplicate on (race_id, name, bookmaker_key, updated_at); that tuple is stable\nacross a redelivery. Measured over two live polls 75 seconds apart, 2026-09-01: 487 of\n530 runner deltas on the second poll were new and 43 were the deliberate replay.\n\n**`since` older than 30 minutes returns 422 and is not charged.** The live board holds\nupcoming races only and purges a race once it jumps, so a gap wider than the board's own\nlifetime cannot be served from it. Do a full fetch from /v1/racing/next-to-go and resume\npolling here from the `server_time` it hands back.\n\n**What drives `changed_runners`.** `race_runners.updated_at`, which is a genuine\nlast-price-CHANGE stamp, not a poll stamp. Ingest rewrites every runner row on every poll\nbut carries the previous timestamp forward when the win and lay prices are unchanged, so\na steady price does not appear here. Measured on the live board 2026-09-01: 97.4% of\nrunner rows were re-polled inside 2 minutes while 22.4% had an `updated_at` inside the\nsame window. A move in `place_price` alone does not advance the column and is delivered\non that runner's next win-price move — poll /v1/racing/next-to-go instead if place prices\nare your primary signal.\n\n**A race with an empty `changed_runners` is expected.** A race is also returned when its\nown row was written since `since`, and that row's timestamp advances on Betfair market\nvolume as well as on real state changes, so the race-level arm is a superset. On the\nfirst live poll of this endpoint, 46 of 61 races arrived on that arm alone. Read each\nrace object as the CURRENT state of the race and compare against what you hold:\n`scratchings` is always the complete current list for the race, never a delta, so a\nrunner leaving it is visible as an absence.\n\n`status` is not a state signal and is documented as such on the response model: it is\nwritten once at row creation and never revised while a race is upcoming, so it reads\n\"open\" for every race here.\n\n`market_status`, `inplay` and the `market_closed_at` / `inplay_at` lifecycle stamps are\ngated on WHICH FEED reported the state, the same rule /v1/racing/events applies. PointsBet\ncloses its book at the real jump and its state is served to everybody; Betfair Exchange\nstate is withheld from customer responses pending a data licence, so a race whose state\nonly the exchange saw reads null for customers. `market_state_source` names the feed\nbehind the values you receive. The race-level facts a customer can act on are\n`scratchings`, a revised `start_time`, and the lifecycle stamps.\n\nBetfair prices are withheld from customer keys here exactly as they are on every other\nracing endpoint, so a Betfair-only price move produces no `changed_runners` entry.\n\n**Betslip links, Plus and above** (since 21 September 2026). A `changed_runners` entry\nfrom a book with a runner-level betslip route (Unibet and TAB on the web, Ladbrokes and\nNeds in their apps) also carries `betslip_win_url`, `betslip_place_url` and\n`betslip_app_only` - the same fields, the same per-book rules and the same daily check as\n/v1/racing/next-to-go - so a poller holds the move and the link that acts on it in one\nobject. The fields are absent on every other book, and on plans below Plus (a Standard or\nHobby key issued before that date keeps them). `source_url` is not carried here; take it\nfrom next-to-go. With link templates stored through `PUT /v1/link-templates` (since\n2026-10-14) the entry also carries `betslip_win_link` / `betslip_place_link`: the same\nURLs built through your templates, absent where there is nothing to link.\n\n`race_id` is the same stable identifier used by /v1/racing/next-to-go,\n/v1/racing/price-history and /v1/racing/results.","operationId":"racing_changes_v1_racing_changes_get","parameters":[{"name":"since","in":"query","required":true,"schema":{"type":"string","description":"ISO 8601 UTC timestamp, e.g. 2026-09-01T05:06:38Z. On your first call use a recent timestamp; after that send back the `server_time` from the previous response. Older than 30 minutes is refused.","title":"Since"},"description":"ISO 8601 UTC timestamp, e.g. 2026-09-01T05:06:38Z. On your first call use a recent timestamp; after that send back the `server_time` from the previous response. Older than 30 minutes is refused."},{"name":"categories","in":"query","required":false,"schema":{"type":"string","description":"horse, greyhound, harness — comma-separated. Omit for all three. An unknown value is refused rather than silently ignored.","title":"Categories"},"description":"horse, greyhound, harness — comma-separated. Omit for all three. An unknown value is refused rather than silently ignored."},{"name":"category","in":"query","required":false,"schema":{"type":"string","description":"Alias of `categories`. Both spellings are accepted on every racing endpoint. An unknown value is a free 422 naming the valid ones.","title":"Category"},"description":"Alias of `categories`. Both spellings are accepted on every racing endpoint. An unknown value is a free 422 naming the valid ones."},{"name":"country","in":"query","required":false,"schema":{"type":"string","description":"ISO country codes, comma-separated (e.g. AU or AU,NZ). Omit for every country. Races whose country is not resolved yet carry country=null and are EXCLUDED by any value here — pair with include_unresolved=true or you will lose most of an Australian card.","title":"Country"},"description":"ISO country codes, comma-separated (e.g. AU or AU,NZ). Omit for every country. Races whose country is not resolved yet carry country=null and are EXCLUDED by any value here — pair with include_unresolved=true or you will lose most of an Australian card."},{"name":"include_unresolved","in":"query","required":false,"schema":{"type":"boolean","description":"Include races whose country is not resolved yet (country is null). OFF by default, matching /v1/racing/events. Most of an Australian card is unresolved until the meeting is confirmed — 58.8% of horse races in a measured window — so country=AU alone can return almost nothing.","default":false,"title":"Include Unresolved"},"description":"Include races whose country is not resolved yet (country is null). OFF by default, matching /v1/racing/events. Most of an Australian card is unresolved until the meeting is confirmed — 58.8% of horse races in a measured window — so country=AU alone can return almost nothing."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RacingChangesOut"},"example":{"server_time":"2026-09-07T03:11:30Z","races":[{"race_id":"2f0e2df8-5386-4aae-bbdb-7b784dee3e43","venue":"Grafton","race_number":1,"category":"horse","country":"AU","start_time":"2026-09-07T03:20:00Z","status":"open","market_status":null,"inplay":null,"market_closed_at":null,"inplay_at":null,"market_state_source":null,"scratchings":[{"name":"Airhawk","number":1,"barrier":9,"scratched_at":"2026-09-03T11:47:44Z"},{"name":"Sweet September","number":9,"barrier":3,"scratched_at":"2026-09-06T18:58:41Z"}],"track_condition_changed_at":null,"track_condition":"Soft (5)","weather":"Clear Sky","changed_runners":[{"name":"Fabulous Fiano","number":2,"bookmaker_key":"ladbrokes_au","win_price":7.0,"place_price":2.6,"top2_price":2.6,"top3_price":1.6,"top4_price":1.22,"updated_at":"2026-09-07T03:11:44Z"},{"name":"Fabulous Fiano","number":2,"bookmaker_key":"sportsbet","win_price":7.5,"place_price":2.5,"top2_price":2.5,"top3_price":1.53,"top4_price":1.2,"updated_at":"2026-09-07T03:11:52Z"},{"name":"Foxwedge Arrow","number":3,"bookmaker_key":"tab","win_price":51.0,"place_price":11.0,"top2_price":null,"top3_price":null,"top4_price":null,"updated_at":"2026-09-07T03:11:58Z"}]}]}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/track-conditions":{"get":{"tags":["Racing"],"summary":"Track condition, weather and rail: current state and today's change log","description":"Cost: 1 credit. Per meeting: the CURRENT official track condition, weather and rail\nposition (Racing Australia, AU thoroughbred meetings, refreshed every two hours) and the\nday's CHANGE LOG — every transition we observed, with the previous value beside the new\none, when it was seen and which feed saw it.\n\n**Two kinds of change row, told apart by `source`.** `racing_australia` rows come from\nRA's own meeting page, so they are the official rating and carry the rail; bookmaker\nrows (`betright`, `ladbrokes_au`, ...) come from a book's race page moving between two\nof our polls and carry `race_id`. A book row can land minutes before RA re-publishes,\nand a book's spelling (\"Soft (6)\") differs from RA's (\"Soft 6\"); the log compares\nnormalised values per feed, so the same rating spelled two ways never appears as a\nchange, and a book that merely lags another produces no row.\n\n**Baselines.** The first RA row for a meeting has every `prev_*` null: it is the state\nwhen we first read the page (about two days out), not a change. Bookmaker rows are only\never written for a genuine transition, so they always carry a `prev_track_condition`.\n\n**Coverage.** Official state is AU thoroughbred only — RA publishes nothing for\ngreyhounds or harness, so those meetings appear here only when a book reported a\nchange, with `current` labelled by that book. New Zealand meetings likewise.\n\n**One entry per meeting, whatever each feed calls it** (2026-09-10). RA welds the\nsponsor into its own venue key ('Picklebet Park Warwick') and the books send the bare\ntown ('Warwick'); both spellings are folded\nonto one meeting by venue_id, by the sponsor word-subset rule, and (for a course inside\na parent racecourse, e.g. the books' 'Randwick' against RA's 'Kensington') by\nvenue_site, each fold refusing to act unless exactly one candidate meeting qualifies\nand every change row on the bookmaker key is positively categorised thoroughbred.\n`merged_from` lists every raw venue_norm folded in and `official_venue_norm` names RA's\nspelling, so a join on either old string still finds the meeting, and `venue` selects it\nby any of them. A merged entry is published under RA's spelling, because a bookmaker's\ncan be ambiguous about which course of a complex ran ('sandown' for a Sandown HILLSIDE\ncard) and `venue_id` must never name the wrong track; where RA's spelling is not yet a\nvenue-registry entry its `venue_id` is that spelling's slug\n('picklebet-park-warwick'), which is unmapped rather than wrong.\n\n**Mixed `current`.** `current` is built field by field: RA's official block wherever RA\npublished a value, a book filling only a field RA left null, and RA's own later row\nsuperseding either. `current.sources` names the feed behind each value field and\n`current.field_as_of` when it was observed; the block-level `source` reads `mixed` when\nmore than one feed is behind the block, and the block-level `as_of` is the OLDEST of\nthe per-field instants, so neither overstates what the block knows. `changes` keeps\nevery row from both feeds in one sequence.\n\n**`going` / `going_rating`** sit beside `track_condition` (and `prev_going` /\n`prev_going_rating` beside `prev_track_condition`) as the same vocabulary\n/v1/racing/horses/form publishes, so 'Soft 6', 'Soft (6)' and 'Good4' need one parser\nand not three. The raw string is unchanged.\n\nThe live endpoints carry the same signal per race as `track_condition_changed_at` — the\ninstant of the latest logged transition for that race's meeting — so a poller reading\n/v1/racing/changes need not call this to know something moved; call this for the\nsequence.\n\n**How far back** (changed 2026-09-29). Any day from `coverage_from` — the first meeting day\nthe store holds, read from the tables on every call — up to three days ahead. Two floors sit\nunder it and the response names both: the change log reaches back to 2026-09-07, while\nRacing Australia's own meeting block (`official_block_from`) is held only from 2026-09-26,\nbecause until 2026-09-29 it was deleted three days after each meeting; it is kept\npermanently since. On a day between the two, `current` and `changes` come from the log\nalone — RA did publish that day, its block is simply gone. A `date` before `coverage_from`\nis a free 422 whose detail names the floor, and so is a malformed one.","operationId":"track_conditions_v1_racing_track_conditions_get","parameters":[{"name":"date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Meeting day, Australia/Sydney, YYYY-MM-DD. Defaults to today. Any day from the store's first day (`coverage_from` in the response; an earlier date is a free 422 naming it) up to 3 days ahead.","title":"Date"},"description":"Meeting day, Australia/Sydney, YYYY-MM-DD. Defaults to today. Any day from the store's first day (`coverage_from` in the response; an earlier date is a free 422 naming it) up to 3 days ahead."},{"name":"venue","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One meeting, case-insensitive ('Belmont', 'sandown hillside'). Omit for every meeting on the day.","title":"Venue"},"description":"One meeting, case-insensitive ('Belmont', 'sandown hillside'). Omit for every meeting on the day."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackConditionsOut"},"example":{"coverage_from":"2026-09-07","date":"2026-09-10","meetings":[{"category":"horse","changes":[{"category":"horse","going":"Good","going_rating":4,"observed_at":"2026-09-09T04:16:29Z","rail":"True","source":"racing_australia","track_condition":"Good 4","weather":"Windy"},{"category":"horse","going":"Good","going_rating":4,"observed_at":"2026-09-10T02:36:58Z","prev_going":"Good","prev_going_rating":3,"prev_track_condition":"Good3","race_id":"race_123","source":"betgold","track_condition":"Good4"}],"current":{"as_of":"2026-09-10T02:17:24Z","field_as_of":{"penetrometer":"2026-09-10T02:17:24Z","rail":"2026-09-10T02:17:24Z","track_condition":"2026-09-10T02:17:24Z","track_type":"2026-09-10T02:17:24Z","weather":"2026-09-10T02:17:24Z"},"going":"Good","going_rating":4,"penetrometer":"5.63","rail":"True","source":"racing_australia","sources":{"penetrometer":"racing_australia","rail":"racing_australia","track_condition":"racing_australia","track_type":"racing_australia","weather":"racing_australia"},"track_condition":"Good 4","track_type":"Turf","weather":"Windy"},"merged_from":["picklebet park warwick","warwick"],"official_venue_norm":"picklebet park warwick","venue":"picklebet park warwick","venue_id":"picklebet-park-warwick","venue_norm":"picklebet park warwick","venue_site":"picklebet-park-warwick"}],"note":"…","official_block_from":"2026-09-07"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/acceptances":{"get":{"tags":["Racing"],"summary":"Full-day AU thoroughbred acceptance card: every meeting, race and runner","description":"Cost: 2 credits. The day's complete AU thoroughbred card as Racing Australia\npublishes it — meeting, then race, then runners in saddlecloth order — with no\ndependency on any bookmaker having framed a market. Per runner: saddlecloth number,\nbarrier, jockey, trainer, weight, `runner_ref` (Racing Australia's code from its latest read\nof the field; once race day's read has landed it is the code the results and archives carry\nfor the race — RA issues a new code for the same horse each day), Last-10 figures, Hcp Rating,\nthe trainer/raceentry codes, the `emergency` flag, and `scratched` plus a `scratching`\nblock (RA's own scratch time, late flag, \"(Eliminated)\" marker and note) for a runner\nalready off. Per race: official name, local start time, distance, the conditions line\nverbatim, class, prize total and per-place breakdown, bonus note, track name/type,\nfield limit, and the counts. Per meeting: rail, track type, condition — as RA's raw\n`track_condition` and, parsed from it, `going` plus `going_rating`, the same pair\n/v1/racing/horses/form publishes — weather, penetrometer, its own `as_of`,\n`emergency_data`, `scratching_data` and `withheld_acceptance_data`.\n\n**Identity (since 2026-10-12).** Every runner carries `horse_ref`, `pe:<folded name>`:\nthe HORSE, the same value on /v1/racing/form, /v1/racing/horses/form and\n/v1/racing/horses/runs, stable across dates and meetings. `runner_ref` is Racing\nAustralia's code for THIS nomination and is new every time the horse is entered; join\nhistories on `horse_ref`, never on `runner_ref`.\n\n**Fields-page extras (since 2026-09-29).** Every runner also carries what Racing\nAustralia prints under the acceptance table for that horse, verbatim and never inferred:\n`age`, `sex`, `colour`, `sire`, `dam` (without the dam's sire), `breeder`, `owner` (RA's\nOwners line), `gear_changes` for this start (\"Tongue Tie OFF FIRST TIME\"), the apprentice\n`claim_kg` read off the jockey cell (\"(a3/52kg)\" -> 3; 0 is an apprentice who no longer\nclaims), `probable_weight_kg` and `penalty_kg` from their own columns, and the horse's LAST\nSTART — the most recent dated start that is not a trial or jump-out: `last_start_date`,\n`last_start_position`, `last_start_jockey`, `last_start_weight_kg`, `last_start_barrier`,\n`last_start_winner` (the horse itself when it won) and `last_start_rating`, the \"Rtg\" RA\nprints on that start's line. Null where RA prints nothing; null on every row stored before\n2026-09-29 until the meeting is re-read. MEASURED on the first pass that read them\n(2026-09-28 20:58Z, 10 meetings, 501 acceptances): pedigree/breeder/owner on 501 (100%), a\nlast start on 423 (84%), a last-start Rtg on 261 (52%), a claim on 107, gear changes on\n111. `last_start_rating` is NOT a substitute for `hcp_rating`: of the runners with no Hcp\nRating, 5 carried a last-start Rtg on that pass (0 of 43 on the saved Moe card of\n2026-09-08) — the unrated are overwhelmingly horses whose last start printed no Rtg either.\n\n**No prices, on purpose.** This is the entry list, not a market. It is available about\ntwo days out, which is the point of it — /v1/racing/next-to-go and\n/v1/racing/best-odds carry prices when books open, and this endpoint stays readable\nlong before they do.\n\n**Scratched runners are flagged, never removed.** They keep their place in the card so\nit can be reconciled line-for-line against RA's official sheet.\n\n**`emergency` is true, false or null.** Null means the meeting was collected before\nemergency acceptances were recorded (2026-09-08 01:01:39 UTC) — its emergency acceptances are\nabsent from the source table entirely, so \"false\" would be a fabrication. The meeting\nsays which case it is in `emergency_data`: \"parsed\" or \"unknown\".\n\n**`scratched` is true, false or null too, for the same reason.** RA's scratching sheet\ndoes not exist for every date in the window — measured 2026-09-08, 2026-09-05 and\n2026-09-06 have no scratching row at all while 09-07 has 96 and 09-08 has 77 — and each\nmeeting's sheet is collected separately, so one meeting can be missing its sheet while\nits acceptances are present. A row from the sheet is the only positive\nevidence it was read, so each meeting publishes `scratching_data`:\n  * `\"parsed\"` — the sheet was read for this meeting; `scratched` is a real boolean\n    and the counts resting on it are real numbers. `scratching_sheet_as_of` says when.\n  * `\"unknown_date\"` — no meeting on this date has a scratching row. No sheet exists\n    for the day; `scratched` is null everywhere on it.\n  * `\"unknown_meeting\"` — other meetings on this date have a sheet and this one does\n    not: either nobody was scratched here or this meeting's fetch failed, and nothing\n    on this card tells them apart, so `scratched` is null.\nDo not read a null as \"not scratched\", and do not read `scratching_data` other than\n\"parsed\" as \"clean meeting\".\n\n**The counts name their population.** An emergency acceptance runs only if it is\nPROMOTED and RA does not publish whether it was, so a count that mixes emergencies\nwith declared runners overstates the field. Every level (race, meeting, day) carries:\n`runner_count` (rows held, scratched and emergencies included — a floor wherever\n`runner_set_status` is not \"checks_passed\"), `emergency_count`, `declared_count`\n(runner_count minus emergency_count), `scratched_count`, `unscratched_count`\n(runner_count minus scratched_count, a reconciliation figure that STILL includes\nemergencies), `declared_scratched_count`, and `declared_remaining_count` — the horses\nexpected to jump barring a promotion, which is the number to render as the field.\nWorked example, Moe R1 on 2026-09-08: 17 acceptances, 5 emergencies (#1, #14-#17),\n12 declared, 6 scratchings all of declared runners — 11 rows are unscratched but only\n6 declared horses remain. Anything resting on an unknown fact is null, not a guess,\nand the day totals are null unless every meeting with rows knows the fact. With NO\nmeeting on the card publishing acceptances there is nothing to total: runner_count is\na real 0 and every other day count is null, matching what those meetings each report,\nand `counts_cover_every_published_meeting` is false.\n\n**On a race that is not \"checks_passed\", every count is a FLOOR.** All seven are\ncomputed over the acceptance rows this card HOLDS, and such a race is one the card has\nitself declared short. Two of them looked exact and were not.\n`scratched_count` and `declared_scratched_count` count sheet rows MATCHED to an\nacceptance row, so a scratching with no row here is in `missing_runners` and not in the\ncount: Townsville R6 on 2026-09-08 reads `scratching_data: \"parsed\"`,\n`scratched_count: 0`, while RA's sheet lists #4 Five Star King and #9 Kickoff — at\nleast 2. And `declared_remaining_count` is the number to render as the field ONLY on a\n\"checks_passed\" race: Townsville R5 that day is \"gap_detected\" with saddlecloth #6\nunaccounted for and NOT on the sheet — a horse that may well be jumping — so its 11 is\n\"at least 11\", never an exact field. Each such race's `runner_set_note` states the\nbound explicitly.\n\n**Completeness is cross-checked, and the note says what was OBSERVED.** ra_race_fields\nis keyed (meeting_date_aet, runner_key) without a race number, so a horse accepted in\ntwo races on one Sydney day has ONE slot — and since 2026-09-08 the acceptance pass\ngives that slot to NEITHER race rather than publish one race's barrier and weight\nagainst the other race's runner: the horse appears in neither race, not in one of them.\n**Do not de-duplicate by `runner_key`\nto recover them — there is nothing to de-duplicate.** Read `missing_runners` instead:\nevery withheld slot is named there, in its own race, with its REAL saddlecloth number,\n`source` `\"withheld_acceptance\"`, and a `cause_detail` naming the slot published in its\nplace where one was. Each race is checked three ways — for saddlecloth holes, against\nthe independently-keyed scratching sheet where one exists, and against the withheld\nslots recorded as withheld when the card was collected — then reports `runner_set_status`\n(\"checks_passed\", \"gap_detected\" or \"unverified\"), a `runner_set_note` and any\n`missing_runners`. Each missing runner carries the cause actually observed for it —\n`duplicate_runner_key` (the collision is visible, not assumed: the same runner_key is\nheld elsewhere on this card, or the slot was recorded as withheld),\n`declared_preferred` (a declared acceptance elsewhere on the day won the key and this\nrace's emergency slot was dropped — nothing collided, so do not hunt a second race),\n`eliminated_emergency` (RA's sheet marks it an emergency and no acceptance row exists)\nor `not_determinable` — plus a `cause_detail` naming the observation. The endpoint\ndoes not guess which of several indistinguishable causes applies.\n\n**The withheld check is the only one that can see a loss above the top of the\nnumbering**, which is exactly where Racing Australia numbers its emergencies: drop\n#13e from a 1..14 field and 1..12 is perfectly contiguous. Measured 2026-09-10, that\nsilence certified Warwick R2 and Bunbury R2 as \"checks_passed\" with `emergency_count`\n0 while RA listed 14 and 13 entries against the 12 served each — false statements\nunder a schema that calls `declared_remaining_count` exact on a checks_passed race.\nEach meeting publishes `withheld_acceptance_data`: `\"parsed\"` (this card placed this\nmeeting's withheld rows, so its discards are accounted for below), `\"unplaced\"` (the\npass recorded discards here and this card could NOT place a single one — the meeting's\nacceptance fields are unpublished, or the race the row names is not on this card;\n`withheld_acceptance_slots_unplaced` counts them and\n`completeness.withheld_acceptance_slots_unplaced` totals the card) or `\"unknown\"` (no\nrow at all — either nothing was withheld, or the pass that read this meeting predates\nthe record). A row is the only positive evidence, so do NOT read `\"unknown\"` as\n\"nothing was lost\"; `completeness.withheld_acceptances` says whether the whole date\nholds any record at all.\n\nEvery `missing_runners` entry carries `withheld_acceptance` (true/false) — the\nprovenance of the loss, and the field `completeness.withheld_acceptance_slots` counts.\nRead THAT, not `source`: one slot can be named by RA's scratching sheet AND recorded in\nra_withheld_acceptances, and it is listed once, keeping the sheet's `source` (the sheet\nsaw the scratching) while carrying the withheld cause and `withheld_acceptance: true`.\nA withheld slot whose horse this card already holds in that SAME race under another\nsaddlecloth is not listed at all: RA renders one horse twice in one race under two\nsaddlecloths, only one row can be published, and no runner is missing from the race.\n\n**A past-date card is a snapshot and will never complete.** `date_status` is \"past\",\n\"today\" or \"future\". Acceptances are collected for today and forward only, so on\na PAST date: a meeting at `acceptances_published: false` will NEVER fill in (live on\n2026-09-05: sportsbet bundaberg and sportsbet mareeba) — calling again cannot help,\nand the meeting's `publication_note` says so; and the scratchings are frozen at the\nlast collection pass of that day, roughly midday race-day, with no later movement recoverable\nfrom anywhere, /v1/racing/changes included. On \"today\" or \"future\" the next collection pass CAN\nadd both, every two hours — but that is not a promise that the card converges. Racing\nAustralia stops listing a meeting once\nit is UNDERWAY, so a meeting's\nrows stop advancing around 12:15-14:15 AEST while its own races run on to 17:00-19:30.\nLog-verified 2026-09-09: the 02:15Z pass discovered all six of that day's meetings, the\n04:15Z pass discovered one, the 06:15Z pass discovered none. The in-band signal is per\nmeeting — its own `as_of` and `scratching_sheet_as_of`. A stamp that stops moving while\nlater passes land on other meetings is a meeting that has gone underway, not a quiet\none, and from that point `/v1/racing/changes` carries the race-day movement.\n\n**Why 2 credits.** A whole day is one request assembled from four reference tables: a\nmidweek card is a few hundred runners over a handful of meetings and a Saturday well\nover a thousand across seventeen or more (measured: 372 runners / 4 meetings on\nTuesday 2026-09-08; 1,259 / 17 on Saturday 2026-09-05). That is materially more than\n/v1/racing/track-conditions (1 credit, tens of rows) and is priced level with\n/v1/racing/next-to-go; it does no cross-book price assembly, so it is not a 3. The\nunderlying data only updates every two hours — calling this once or twice a day is the\nintended shape, not polling it.\n\n**Coverage, honestly.** Australian THOROUGHBRED meetings only. Racing Australia\npublishes no acceptance card for greyhounds or harness, and none for New Zealand, so\nthose meetings are absent here — not empty, absent. History is kept from 2026-09-29 on:\nuntil then the reference tables were deleted three days after each meeting, so the store\nbegins at the oldest day that survived (2026-09-26) and grows by a day each day.\n`coverage_from` is that first day, read from the tables on every call; any `date` from it\nup to three days ahead is served, and an earlier one is a free 422 naming the floor rather\nthan a truthful-looking empty card. A past day is the card as the LAST read of that day\nleft it — see the past-date paragraph above.\n\n**How current it is.** `as_of` is the newest RA read backing the meetings actually\nreturned (a venue filter narrows it, so one stale meeting cannot borrow another's\nfreshness), and each meeting carries its own plus `scratching_sheet_as_of`.\n\nA malformed or out-of-window `date`, and an unknown query parameter, are both a free\n422 — nothing is charged.","operationId":"acceptances_v1_racing_acceptances_get","parameters":[{"name":"date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Meeting day, Australia/Sydney, YYYY-MM-DD. Defaults to today. Any day from the store's first day (`coverage_from` in the response; an earlier date is a free 422 naming it) up to 3 days ahead.","title":"Date"},"description":"Meeting day, Australia/Sydney, YYYY-MM-DD. Defaults to today. Any day from the store's first day (`coverage_from` in the response; an earlier date is a free 422 naming it) up to 3 days ahead."},{"name":"venue","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One meeting, case-insensitive ('Moe', 'sandown hillside'). Omit for every meeting on the day.","title":"Venue"},"description":"One meeting, case-insensitive ('Moe', 'sandown hillside'). Omit for every meeting on the day."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptancesOut"},"example":{"date":"2026-09-29","date_status":"today","meeting_count":1,"race_count":8,"runner_count":97,"emergency_count":1,"declared_count":96,"scratched_count":16,"unscratched_count":81,"declared_scratched_count":15,"declared_remaining_count":81,"as_of":"2026-09-29T02:16:17.177959Z","coverage_from":"2026-09-26","completeness":{"races_checks_passed":8,"races_gap_detected":0,"races_unverified":0,"known_missing_runners":0,"meetings_acceptances_unpublished":0,"meetings_emergency_data_unknown":0,"meetings_scratching_data_unknown":0,"meetings_scratching_sheet_absent_for_whole_date":0,"runners_without_emergency_data":0,"runners_without_scratching_data":0,"withheld_acceptances":"unknown_date","meetings_withheld_acceptance_data_unknown":1,"withheld_acceptance_slots":0,"withheld_acceptance_slots_unplaced":0,"counts_cover_every_published_meeting":true},"meetings":[{"venue":"Grafton","venue_norm":"grafton","venue_id":"grafton","venue_site":"grafton","acceptances_published":true,"publication_note":null,"race_count":8,"runner_count":97,"emergency_count":1,"declared_count":96,"scratched_count":16,"unscratched_count":81,"declared_scratched_count":15,"declared_remaining_count":81,"emergency_data":"parsed","scratching_data":"parsed","withheld_acceptance_data":"unknown","withheld_acceptance_slots_unplaced":0,"scratching_sheet_as_of":"2026-09-29T02:16:17.177959Z","runner_set_status":"checks_passed","as_of":"2026-09-29T02:16:17.177959Z","track":{"rail":"+3m entire","track_type":"Turf","track_condition":"Soft 5","going":"Soft","going_rating":5,"weather":"Fine","penetrometer":null,"track_info":null,"dual_track":"N"},"races":[{"race_number":4,"race_id":"f182ecf5-f978-4941-a385-1b02db4bcfe4","race_name":"CRJC CHRISTMAS RACE DAY 29 NOVEMBER COUNTRY BOOSTED BENCHMARK 66 HANDICAP","start_time_local":"2:45PM","distance_m":1106,"race_class":"BenchMark 66","conditions":"BenchMark 66, Handicap, Minimum Weight 55kg, Apprentices can claim.","prize_total":30000,"prize_breakdown":{"1st":15000,"2nd":5300,"3rd":2800,"4th":1475,"5th":975,"6th":800,"7th":725,"8th":725,"9th":725,"10th":725},"bonus_note":"BOBS Silver Bonus Available Up To $9625","track_name":"Main","track_type":"Turf","field_limit":"14 + 6 EM","runner_count":10,"emergency_count":0,"declared_count":10,"scratched_count":0,"unscratched_count":10,"declared_scratched_count":0,"declared_remaining_count":10,"runner_set_status":"checks_passed","runner_set_note":null,"missing_runners":[],"runners":[{"number":2,"barrier":9,"runner_name":"Geegee Jet By","runner_key":"geegeejetby","horse_ref":"pe:geegeejetby","runner_ref":"ra:NDE3ODM4NjM5ODQ","jockey":"Grady Spokes","trainer":"Brett Dodson","weight_kg":61.0,"last10":"30581175x0","hcp_rating":66,"trainer_code":"MTA3Mjc2ODAw","raceentry_code":"MTI0NzkwNTUwMDY0","emergency":false,"scratched":false,"scratching":null,"claim_kg":1.5,"probable_weight_kg":59.5,"penalty_kg":null,"age":7,"sex":"gelding","colour":"brown","sire":"Wordsmith","dam":"Geegee Pretty Jess","breeder":"P A & E J Geard","owner":"D J Dick, T M Booth, K J Twohill, J L Haimes, G J Monaghan, Sea Eels (Mgr: G M Wyborn), S D Monaghan, P G Sinclair, D R MacKenzie, N Gibbons & A C Durrington","gear_changes":"Synthetic Hoof Filler OFF FIRST TIME","last_start_date":"2026-09-06","last_start_position":11,"last_start_rating":66,"last_start_jockey":"Archie McColm","last_start_weight_kg":55.0,"last_start_barrier":13,"last_start_winner":"Speck"},{"number":1,"barrier":6,"runner_name":"Dune And Dusted","runner_key":"duneanddusted","horse_ref":"pe:duneanddusted","runner_ref":"ra:NDE5MTM2NTMxMzY","jockey":"Nathan Thomas","trainer":"Paddy Cunningham","weight_kg":61.5,"last10":"60x6094082","hcp_rating":67,"trainer_code":"MTQ1NzE1MDI0","raceentry_code":"MTI0Nzk1Mzg1NDA4","emergency":false,"scratched":false,"scratching":null,"claim_kg":null,"probable_weight_kg":null,"penalty_kg":null,"age":7,"sex":"gelding","colour":"bay","sire":"Hidden Dragon","dam":"Miss Caitlyn","breeder":"Mr P J Cunningham","owner":"P J Cunningham & Mrs K A Cunningham","gear_changes":null,"last_start_date":"2026-09-20","last_start_position":2,"last_start_rating":67,"last_start_jockey":"Jenny Duggan","last_start_weight_kg":61.5,"last_start_barrier":8,"last_start_winner":"Soobooco"}]}]}],"note":"Australian thoroughbred acceptances from Racing Australia, re-read every two hours and typically published about two days ahead. No prices: this is the entry list, not a market. …"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/clv":{"post":{"tags":["Racing"],"summary":"Score bets against the closing line (CLV)","description":"Cost: 5 credits per request, up to 200 bets. Score a list of bets you struck against the\nclosing line each race actually closed at, and get the record back as numbers: per bet the\nclosing price (the named book's, or the market's best or median), the closing-line value in\npercent, whether it beat the close, the result and the flat-stake P&L; and across the list\nthe average and median CLV, the beat-the-close rate, the win rate and the ROI.\n\n**Naming a race.** Send the `race_id` you already hold from next-to-go, events, results or\nthe archive; failing that, `venue` + `meeting_date` (AET) + `race_number`. **Naming a\nrunner.** `runner_ref` (the ra:/grv: registry id on results and the archive) is exact;\n`runner_number` is the saddlecloth; `runner_name` is matched the way the feeds match names.\n\n**What you are measured against.** A closing line is a book's last price within 300 s of\nthe jump. Name a `bookmaker` and `clv_pct` is against that book's close (`close_book`);\notherwise against the market `reference` — `best` (the highest close across books, the\nstrictest test) or `median`. `close_best` and `close_median` come back on every bet\neither way, so you can re-cut the record yourself.\n\n**Coverage.** The archive holds AU and NZ races from `archive_floor` onward; a bet before\nthat, or on a race no book priced at the close, resolves with a `reason` and is left out\nof the averages rather than guessed. Results are settled only when `result_status` is\n`final`; `won` and `pnl` are null until then. Betfair exchange prices are withheld.\n\nPlus and above. Free and paid tiers below Plus receive a 403 and are not charged; the 403\ncarries a `sample`: five example bets on one recent race, scored the way yours would be.","operationId":"racing_clv_v1_racing_clv_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClvIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClvOut"},"example":{"reference":"spring-2026","archive_floor":"2026-08-18","summary":{"bets":3,"resolved":3,"unresolved":0,"scored":3,"avg_clv_pct":1.11,"median_clv_pct":5.0,"beat_close":2,"beat_close_rate_pct":66.7,"resulted":3,"wins":1,"win_rate_pct":33.3,"staked":150.0,"pnl":60.0,"roi_pct":40.0},"bets":[{"ref":"b-1001","resolved":true,"reason":null,"race_id":"2f0e2df8-5386-4aae-bbdb-7b784dee3e43","venue":"Grafton","race_number":1,"runner_key":"fabulousfiano","runner_name":"Fabulous Fiano","runner_number":4,"runner_ref":"ra:1147732","bookmaker":"sportsbet","price_taken":7.5,"stake":50.0,"close_book":7.0,"close_best":8.0,"close_median":7.25,"books_at_close":11,"close_reference":7.0,"clv_pct":7.14,"beat_close":true,"finish_position":5,"result_status":"final","won":false,"pnl":-50.0},{"ref":"b-1002","resolved":true,"reason":null,"race_id":"104702cc-8a42-4b14-88b0-1fb6de91e913","venue":"Kalgoorlie","race_number":8,"runner_key":"foxwedgearrow","runner_name":"Foxwedge Arrow","runner_number":2,"runner_ref":"ra:1102884","bookmaker":"tab","price_taken":4.2,"stake":50.0,"close_book":4.0,"close_best":4.4,"close_median":4.1,"books_at_close":10,"close_reference":4.0,"clv_pct":5.0,"beat_close":true,"finish_position":1,"result_status":"final","won":true,"pnl":160.0},{"ref":"b-1003","resolved":true,"reason":null,"race_id":"c2cfc9b1-a037-4bcb-841f-5496bb0c9b82","venue":"Tatura","race_number":1,"runner_key":"bellbirdlane","runner_name":"Bellbird Lane","runner_number":6,"runner_ref":null,"bookmaker":"ladbrokes_au","price_taken":3.1,"stake":50.0,"close_book":3.4,"close_best":3.5,"close_median":3.3,"books_at_close":9,"close_reference":3.4,"clv_pct":-8.82,"beat_close":false,"finish_position":7,"result_status":"final","won":false,"pnl":-50.0}],"notes":["CLV is measured against the last price the NAMED bookmaker showed before the jump (close_book). Name no bookmaker, or name one that recorded no close, and close_reference falls back to the market figure — the row says which it used.","A bet whose race or runner is not in the archive comes back resolved=false with a reason and no numbers, rather than being dropped from the response."]}}}},"403":{"description":"Closing-line value scoring is available on Plus, Business, Platform and Scale plans only. The refusal is free and carries a `sample`: five example bets on one recent race, scored."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/markets":{"get":{"tags":["Racing"],"summary":"Every market on one race: win, place, Top 2/3/4 and exotic dividends","description":"Cost: 2 credits. One race, every market the panel quotes on it, book by book: the win\nprice, the place price, the Same Race Multi legs (**Top 2, Top 3 and Top 4**), each book's\nmarket percentage, the best price per runner in each market, and — once the race has been\nrun — the exotic dividends it paid.\n\n**Naming the race.** `race_id` is exact. Failing that, `venue` + `meeting_date` (the AET\nmeeting date) + `race_number`; two races sharing those three answer 409 `race_ambiguous`\nand cost nothing, because picking one of a venue split silently would be worse than\nrefusing.\n\n**Who quotes what.** Every book in the panel prices win, and all but the exchange price\nplace. Top 2/3/4 come from **sportsbet, ladbrokes_au, neds and pointsbetau** — no other\nbook in the feed publishes Same Race Multi legs, so an empty `top3` is a fact about the\nmarket and not a gap in the feed. Each leg is validated against the chain\nwin >= top2 >= top3 >= top4 > 1.0 before it is stored, so a leg that would break it is\nabsent rather than wrong.\n\n**overround_pct** is Σ (1/price) × 100 over the runners that book quoted in that market —\n100% is a book taking no margin. It is null where a book quoted fewer than two runners,\nwhich is not a market. Place and Top-N percentages are naturally far above 100 because\nseveral runners can win the bet.\n\n**Live or stored.** A race whose market is still open is read live (`source: \"live\"`). Once\nit has been run the live rows are purged, and the answer comes from the minute-by-minute\ncapture of the last price each book showed before the jump (`source: \"closes\"`). Races that\njumped before that capture began carry no market rows and say so in `note`.\n\n**exotics** are SETTLED DIVIDENDS — what the tote paid — not prices. Nothing in this feed\nquotes fixed-odds quinella, exacta, trifecta or first four before a race, so `exotics` is\nnull until the race is resulted, and then carries the result's own `status`: `interim`\ndividends can still move, `final` cannot.\n\n**quote_detail** (since 2026-09-29) lists, per book and runner, what the book publishes\nbeside its prices: `open_price` (the book's own opening price — not the archive's first\ncaptured price), `flucs` (its recent prices, most recent first), `tote_win` / `tote_place`\n(its tote dividends keyed by product code; `PROV` is an indicative pre-race dividend that\nmoves until the pool closes), `boost_price` / `boost_type`, and the exchange fields. Each\nentry names `bookmaker`, `number`, `name`, `runner_ref` and `seen_at` — when the book was\nlast read — and only the fields that book carries. Who carries them, measured on the AU\nboard of 2026-09-29 before its first race (20 races, 2,219 book-runner rows): flucs at\nbetdeluxe, betgold, boostbet and betright on every row, open_price at betright on every\nrow, tote_win at tabtouch on 89 of 194 rows and tote_place on 26 (a provisional dividend\nappears as the pool forms), no boost that morning. The exchange fields are Betfair's, and\nBetfair is withheld from customer answers. For a race that has been run the values are\nthe last read before the jump.\n\n**scratchings** (since 2026-09-29) are the race's scratched runners — `name`, `number`,\n`barrier`, `scratched_at` (the scratching book's own time, UTC) and `vacant` for an empty\nbox — from the open race while it is live, and once it has been purged from the capture\ntaken at the jump. A race run before 2026-09-29 answers an empty list after its purge. A\nscratching no Australian bookmaker has reported, while two or more of them still price the\nrunner, is held back: the runner stays in the markets until a bookmaker reports it.\n\n**tote_series** (since 2026-10-06) is each book's tote approximate as the pools formed:\none entry per book and runner, with points at fixed marks before the jump — 3600, 1800,\n900, 600, 300, 180, 120 and 60 seconds — each the first read the minute-by-minute capture\nsaw at or inside its mark, carrying `mark_seconds`, `read_at`, `secs_to_jump` and\n`tote_win` / `tote_place` by product code. TABtouch publishes a provisional dividend\n(`PROV`) for every runner hours out; BetRight its products in the final quarter hour. A\nrace first seen inside a mark has no earlier points: nothing is filled in. The last read\nbefore the jump stays in `quote_detail`.\n\n**book_scratchings** (since 2026-10-06) says, book by book, when a scratching reached each\nfeed: `reported_at` is the first time that book's own feed listed it, `book_scratched_at`\nthe scratch time the book itself states (Ladbrokes, Neds, BetRight and the BetMakers books\ncarry one), and `first_quoted_at` / `last_quoted_at` with `last_quoted_win` the polls, to\nthe minute, at which that book's feed still carried a win price for the runner after the\nrace's scratchings named it. Ladbrokes and Neds keep a scratched runner's last price on the\ncard beside their own flag, so for a book that flags it the price is recorded once, at the\nflag: its price at the scratching. A book that carries a price with no flag is recorded\neach minute until it clears the runner. None of these prices is served as a price: a\nscratched runner is dropped from every price endpoint.\n\n**Your own links** (`source_link` beside each runner's `source_url`, since 2026-10-14, Plus\nand above): store your bookmaker link templates once with `PUT /v1/link-templates` and each\nprice also carries a ready-made link built through them. Absent where there is nothing to\nlink, and for an account with no stored templates.\n\nPlus and above, and Standard keys issued before 20 September 2026. Every other key below\nPlus receives a 403 and is not charged; the 403 carries a `sample`: the winner of one\nrecent race, its best price in each market and the book that offered it.","operationId":"racing_markets_v1_racing_markets_get","parameters":[{"name":"race_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"description":"The race_id from /v1/racing/next-to-go, /events, /results or the archive. The primary form; use venue + meeting_date + race_number only when you do not hold it.","title":"Race Id"},"description":"The race_id from /v1/racing/next-to-go, /events, /results or the archive. The primary form; use venue + meeting_date + race_number only when you do not hold it."},{"name":"venue","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"description":"Venue name as the feeds spell it (case-insensitive). Needs meeting_date and race_number.","title":"Venue"},"description":"Venue name as the feeds spell it (case-insensitive). Needs meeting_date and race_number."},{"name":"meeting_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"AET meeting date, YYYY-MM-DD.","title":"Meeting Date"},"description":"AET meeting date, YYYY-MM-DD."},{"name":"race_number","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":20,"minimum":1},{"type":"null"}],"title":"Race Number"}},{"name":"bookmakers","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":400},{"type":"null"}],"description":"Comma-separated bookmaker keys to restrict to. Omit for every book quoting the race.","title":"Bookmakers"},"description":"Comma-separated bookmaker keys to restrict to. Omit for every book quoting the race."},{"name":"markets","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"description":"Comma-separated subset of win, place, top2, top3, top4, exotics. Omit for all of them.","title":"Markets"},"description":"Comma-separated subset of win, place, top2, top3, top4, exotics. Omit for all of them."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketsOut"},"example":{"race":{"race_id":"81db7342-2e44-482a-bf62-5d5166d764f5","venue":"Tatura","race_number":1,"category":"horse","country":"AU","start_time":"2026-09-29T03:30:00Z","places_paid":3,"status":"resulted","source":"closes"},"markets":[{"market":"win","places":1,"bookmakers":[{"key":"betright","runners_quoted":9,"overround_pct":120.34,"observed_at":"2026-09-29T03:29:36.794038Z","runners":[{"number":1,"name":"Alpha Mensae","runner_ref":"ra:NDIzMzYyNjA2MjQ","horse_ref":"pe:alphamensae","price":17.0,"source_url":null},{"number":8,"name":"The Shyster","runner_ref":"ra:NDIzMjAzNDI2NDA","horse_ref":"pe:theshyster","price":2.35,"source_url":null}]},{"key":"pointsbetau","runners_quoted":9,"overround_pct":120.49,"observed_at":"2026-09-29T03:29:58.949035Z","runners":[{"number":1,"name":"Alpha Mensae","runner_ref":"ra:NDIzMzYyNjA2MjQ","horse_ref":"pe:alphamensae","price":17.0,"source_url":null},{"number":8,"name":"The Shyster","runner_ref":"ra:NDIzMjAzNDI2NDA","horse_ref":"pe:theshyster","price":2.3,"source_url":null}]}],"best":[{"number":1,"name":"Alpha Mensae","runner_ref":"ra:NDIzMzYyNjA2MjQ","price":17.0,"bookmaker":"betright"},{"number":8,"name":"The Shyster","runner_ref":"ra:NDIzMjAzNDI2NDA","price":2.35,"bookmaker":"sportsbet"}]},{"market":"place","places":3,"bookmakers":[{"key":"betright","runners_quoted":9,"overround_pct":363.62,"observed_at":"2026-09-29T03:29:36.794038Z","runners":[{"number":1,"name":"Alpha Mensae","runner_ref":"ra:NDIzMzYyNjA2MjQ","horse_ref":"pe:alphamensae","price":3.8,"source_url":null},{"number":8,"name":"The Shyster","runner_ref":"ra:NDIzMjAzNDI2NDA","horse_ref":"pe:theshyster","price":1.25,"source_url":null}]},{"key":"pointsbetau","runners_quoted":9,"overround_pct":376.36,"observed_at":"2026-09-29T03:29:58.949035Z","runners":[{"number":1,"name":"Alpha Mensae","runner_ref":"ra:NDIzMzYyNjA2MjQ","horse_ref":"pe:alphamensae","price":3.6,"source_url":null},{"number":8,"name":"The Shyster","runner_ref":"ra:NDIzMjAzNDI2NDA","horse_ref":"pe:theshyster","price":1.17,"source_url":null}]}],"best":[{"number":1,"name":"Alpha Mensae","runner_ref":"ra:NDIzMzYyNjA2MjQ","price":3.8,"bookmaker":"betright"},{"number":8,"name":"The Shyster","runner_ref":"ra:NDIzMjAzNDI2NDA","price":1.26,"bookmaker":"tab"}]},{"market":"top2","places":2,"bookmakers":[{"key":"pointsbetau","runners_quoted":9,"overround_pct":251.86,"observed_at":"2026-09-29T03:29:58.949035Z","runners":[{"number":1,"name":"Alpha Mensae","runner_ref":"ra:NDIzMzYyNjA2MjQ","horse_ref":"pe:alphamensae","price":6.33,"source_url":null},{"number":8,"name":"The Shyster","runner_ref":"ra:NDIzMjAzNDI2NDA","horse_ref":"pe:theshyster","price":1.43,"source_url":null}]}],"best":[{"number":1,"name":"Alpha Mensae","runner_ref":"ra:NDIzMzYyNjA2MjQ","price":6.33,"bookmaker":"pointsbetau"},{"number":8,"name":"The Shyster","runner_ref":"ra:NDIzMjAzNDI2NDA","price":1.5,"bookmaker":"ladbrokes_au"}]}],"exotics":{"source":"results","status":"final","dividends":[{"code":"QN","type":"Quinella","product":"VIC","selection":"8,13","amount":25.5},{"code":"TF","type":"Trifecta","product":"VIC","selection":"8,13,7","amount":178.1}]},"note":null,"quote_detail":[{"bookmaker":"betright","number":2,"name":"Around The Marx","runner_ref":"ra:NDIzMDk5ODU2OTY","seen_at":"2026-09-29T03:29:58.463340Z","open_price":19.0,"flucs":[23.0,26.0,21.0],"tote_win":{"RD+":21.909},"tote_place":{"MIDDIV":4.0}},{"bookmaker":"tabtouch","number":1,"name":"Alpha Mensae","runner_ref":"ra:NDIzMzYyNjA2MjQ","seen_at":"2026-09-29T03:29:59.993599Z","tote_win":{"PROV":16.2},"tote_place":{"PROV":3.6}},{"bookmaker":"betgold","number":8,"name":"The Shyster","runner_ref":"ra:NDIzMjAzNDI2NDA","seen_at":"2026-09-29T03:30:00.918826Z","flucs":[2.3,2.35,2.4,2.45,2.4,2.45,2.4,2.45,2.4,2.45,2.4,2.45,2.4]},{"bookmaker":"betdeluxe","number":null,"name":"Alpha Mensae","runner_ref":"ra:NDIzMzYyNjA2MjQ","seen_at":"2026-09-29T03:30:00.351934Z","flucs":[8.5,15.0,16.0,18.0]}],"scratchings":[{"name":"Inside Word","number":5,"barrier":11,"scratched_at":"2026-09-28T21:30:08Z"},{"name":"Japanese Moka","number":6,"barrier":13,"scratched_at":"2026-09-27T22:56:17Z"},{"name":"Tyrant Rex","number":9,"barrier":9,"scratched_at":"2026-09-28T10:18:19Z"},{"name":"Chapel Belle","number":10,"barrier":12,"scratched_at":"2026-09-28T04:09:14Z"}]}}}},"403":{"description":"The race markets view is available on Plus, Business, Platform and Scale plans only. Standard keys issued before 20 September 2026 have it too. The refusal is free and carries a `sample` of the view for one recent race."},"404":{"description":"No race matched. The body says whether the id is unknown or a known race with no stored market, and where its prices are. Nothing was charged."},"409":{"description":"Two races share that venue, date and number (a venue split). Nothing was charged."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/consensus":{"get":{"tags":["Racing"],"summary":"The market consensus on one race: margin-free probability per runner","description":"Cost: 3 credits. The market's consensus on one race while its market is open: for\nevery runner, the implied probability after each book's margin is removed and the books\nare combined, the price that probability corresponds to, the median, mean, best and\nworst price, and how far the best available price sits from the consensus. The inputs\nare printed beside the answer — which books were counted, which were left out and why.\n\n**Naming the race.** `race_id` from /v1/racing/next-to-go or /v1/racing/events is exact.\nFailing that, `venue` + `meeting_date` (the AET meeting date) + `race_number`; two races\nsharing those three answer 409 `race_ambiguous` and cost nothing.\n\n**How the number is made.** Each included book's win prices on the field are turned into\nimplied probabilities (1 / price) and its margin removed — `power` (default) raises them\nto the exponent that makes them sum to one, `multiplicative` divides them by their sum\n— so every book contributes a field that sums to 1. The consensus for a runner is the\nMEDIAN of those per-book probabilities, and the medians are scaled once so the field\nsums to 1 again; `market.consensus_sum_before_normalisation` shows how far from 1 they\nwere before scaling. Every included book counts once. Nothing is weighted by an opinion\nof which book is sharp.\n\n**Which books count.** A book is included when it is visible to you (the exchange and the\nnon-Australian reference books are withheld here as on every racing endpoint), its quote\nis inside its refresh-tier threshold (the same per-book rule next-to-go publishes: 120 s\ninside three hours of the jump, 1800 s on the morning card, with Betr and NextBet on\ntheir own horizons), and it prices the complete field — `market.field_size` is the number\nof runners a complete-field book quotes, and a book pricing fewer is `partial_field`.\nPlaceholder prices are dropped quote by quote on the rule /v1/racing/best-odds applies\n(a long price at least four times the median of three or more other books) and listed in\n`excluded_quotes`. Scratched runners are dropped and listed under `scratchings`.\n\n**Reading `consensus_prob`.** It is the market's implied probability after margin, not a\nprobability of winning and not a fair price. `best_vs_consensus_pct` is\nbest_price / consensus_price − 1: negative is normal, because every quoted price carries\nmargin and the best quote usually sits below the panel's margin-free price (about −20 %\non a 130 % greyhound market); it turns positive only when one book prices a runner above\nthe panel's margin-free view. It says where the best quote sits, nothing more. The\nfields a reader would use to judge how much to trust a figure — `books`, the spread, the\nages, `secs_to_jump` — are separate on purpose; there is no single confidence score.\n\n**Live only.** The consensus is read from the open market. A race that has left the live\nboard answers 404; the consensus at the close will arrive as columns on the permanent\narchive, where it belongs. A race inside the ten minutes after its jump is still served\n(`status: jumped`) while the books drop off, with a note.\n\nEvery plan, Free included. 3 credits, charged after the race is found; a race we cannot\nfind, an unparseable parameter and an unknown bookmaker key are free.","operationId":"racing_consensus_v1_racing_consensus_get","parameters":[{"name":"race_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"description":"The race_id from /v1/racing/next-to-go or /events. The primary form; use venue + meeting_date + race_number only when you do not hold it.","title":"Race Id"},"description":"The race_id from /v1/racing/next-to-go or /events. The primary form; use venue + meeting_date + race_number only when you do not hold it."},{"name":"venue","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"description":"Venue name as the feeds spell it (case-insensitive). Needs meeting_date and race_number.","title":"Venue"},"description":"Venue name as the feeds spell it (case-insensitive). Needs meeting_date and race_number."},{"name":"meeting_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"AET meeting date, YYYY-MM-DD.","title":"Meeting Date"},"description":"AET meeting date, YYYY-MM-DD."},{"name":"race_number","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":20,"minimum":1},{"type":"null"}],"title":"Race Number"}},{"name":"method","in":"query","required":false,"schema":{"type":"string","description":"How each book's margin is removed before the books are combined. `power` (default) raises the implied probabilities to the exponent that makes them sum to one, which takes more margin off the longshots and is calibrated across the price range. `multiplicative` divides them by their sum and reproduces from the published overround.","default":"power","title":"Method"},"description":"How each book's margin is removed before the books are combined. `power` (default) raises the implied probabilities to the exponent that makes them sum to one, which takes more margin off the longshots and is calibrated across the price range. `multiplicative` divides them by their sum and reproduces from the published overround."},{"name":"bookmakers","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":400},{"type":"null"}],"description":"Comma-separated bookmaker keys to build the consensus from. Omit for every visible book.","title":"Bookmakers"},"description":"Comma-separated bookmaker keys to build the consensus from. Omit for every visible book."},{"name":"min_books","in":"query","required":false,"schema":{"type":"integer","maximum":14,"minimum":1,"description":"Included books a runner needs before it carries a consensus probability.","default":2,"title":"Min Books"},"description":"Included books a runner needs before it carries a consensus probability."},{"name":"include_stale","in":"query","required":false,"schema":{"type":"boolean","description":"Count books whose quote is older than their refresh-tier threshold. Off by default; they are listed under books_excluded with reason stale.","default":false,"title":"Include Stale"},"description":"Count books whose quote is older than their refresh-tier threshold. Off by default; they are listed under books_excluded with reason stale."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsensusOut"},"example":{"race":{"race_id":"2e33fc28-13a5-421b-a6f3-6f99283c5cb6","venue":"Menangle","race_number":2,"category":"harness","country":"AU","start_time":"2026-10-06T01:43:00Z","secs_to_jump":771,"refresh_tier":"live","status":"upcoming","as_of":"2026-10-06T01:30:08Z"},"method":{"normalisation":"power","aggregation":"median_probability","renormalised":true,"stale_rule":"refresh_tier","field_rule":"complete_field","min_books":2,"include_stale":false},"books_quoting":13,"books_in_consensus":13,"books_excluded":[],"bookmakers":[{"bookmaker":"betdeluxe","runners_priced":10,"overround_pct":135.08,"age_seconds":35,"last_update":"2026-10-06T01:29:32.828100Z","refresh_tier":"live","stale":false,"included":true,"reason":null},{"bookmaker":"betgold","runners_priced":10,"overround_pct":143.72,"age_seconds":35,"last_update":"2026-10-06T01:29:33.273348Z","refresh_tier":"live","stale":false,"included":true,"reason":null},{"bookmaker":"betr_au","runners_priced":10,"overround_pct":139.49,"age_seconds":48,"last_update":"2026-10-06T01:29:20.528874Z","refresh_tier":"live","stale":false,"included":true,"reason":null},{"bookmaker":"betright","runners_priced":10,"overround_pct":129.01,"age_seconds":37,"last_update":"2026-10-06T01:29:31.051977Z","refresh_tier":"live","stale":false,"included":true,"reason":null}],"market":{"median_overround_pct":137.08,"best_price_market_pct":124.44,"median_runner_spread_pct":20.33,"consensus_sum_before_normalisation":0.9933,"field_size":10},"runners":[{"number":10,"name":"Alpha Stride","runner_ref":null,"horse_ref":null,"scratched":false,"books":13,"consensus_prob":0.2652,"consensus_price":3.77,"median_price":3.1,"mean_price":3.14,"best_price":3.6,"best_bookmaker":"playup","worst_price":2.9,"price_spread_pct":24.14,"best_vs_consensus_pct":-4.54,"freshest_age_seconds":32,"oldest_age_seconds":168,"quotes":[{"bookmaker":"playup","price":3.6,"no_vig_prob":0.2213,"age_seconds":168},{"bookmaker":"ladbrokes_au","price":3.3,"no_vig_prob":0.2508,"age_seconds":36},{"bookmaker":"neds","price":3.3,"no_vig_prob":0.2508,"age_seconds":36}]},{"number":2,"name":"Call Me Jack","runner_ref":null,"horse_ref":null,"scratched":false,"books":13,"consensus_prob":0.2021,"consensus_price":4.95,"median_price":3.8,"mean_price":3.88,"best_price":4.0,"best_bookmaker":"betright","worst_price":3.7,"price_spread_pct":8.11,"best_vs_consensus_pct":-19.17,"freshest_age_seconds":32,"oldest_age_seconds":168,"quotes":[{"bookmaker":"betright","price":4.0,"no_vig_prob":0.2053,"age_seconds":37},{"bookmaker":"ladbrokes_au","price":4.0,"no_vig_prob":0.2007,"age_seconds":36},{"bookmaker":"neds","price":4.0,"no_vig_prob":0.2007,"age_seconds":36}]},{"number":1,"name":"Bertinelli","runner_ref":null,"horse_ref":null,"scratched":false,"books":13,"consensus_prob":0.149,"consensus_price":6.71,"median_price":5.0,"mean_price":5.04,"best_price":5.5,"best_bookmaker":"betright","worst_price":5.0,"price_spread_pct":10.0,"best_vs_consensus_pct":-18.08,"freshest_age_seconds":32,"oldest_age_seconds":168,"quotes":[{"bookmaker":"betright","price":5.5,"no_vig_prob":0.1427,"age_seconds":37},{"bookmaker":"betdeluxe","price":5.0,"no_vig_prob":0.152,"age_seconds":35},{"bookmaker":"betgold","price":5.0,"no_vig_prob":0.1416,"age_seconds":35}]}],"excluded_quotes":[],"scratchings":[],"note":null}}}},"404":{"description":"No race on the live board matched. Nothing was charged."},"409":{"description":"Two races share that venue, date and number (a venue split). Nothing was charged."},"422":{"description":"Unparseable parameter or unknown bookmaker key. Nothing was charged."},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/intelligence":{"get":{"tags":["Racing"],"summary":"Market intelligence on one race: consensus at fixed marks, time-anchored moves, breadth, first mover, signal","description":"Cost: 3 credits. One race on the live board: for every runner, the market consensus\nprice now and at fixed marks before the jump (T-60, T-30, T-15, T-10, T-5, T-2), its high\nand low since capture began, movement measured against those marks and against the last\nfive and two minutes, the number of ticks in the last five minutes, the best price against\nthe consensus, how many books moved with the headline, the first attributable book to move,\nand a signal class whose rule is printed in the response.\n\n**Naming the race.** `race_id` from /v1/racing/next-to-go or /v1/racing/events is exact.\nFailing that, `venue` + `meeting_date` (AET) + `race_number`; a venue split answers 409 and\ncosts nothing.\n\n**Time-anchored, not open-anchored.** Every movement figure compares the consensus NOW with\nthe consensus at a fixed time, computed by the same arithmetic as GET /v1/racing/consensus\nand the archive's consensus columns: each book's win prices with its margin removed, the\nmedian across books, the field scaled to one. `move_from_t60_pct` is negative for a runner\nthat shortened. A mark is null when the race had not entered the capture window by then —\ncapture begins 60 minutes before the jump, so a race further out than that carries `now`\nonly and `window.open` is false.\n\n**Breadth.** `with_history` counts books quoting now with two or more captured prices on\nthe runner; `firming`, `drifting` and `unchanged` partition them by each book's own price\nagainst its price at T-60 (or its first tick, when it joined later). A headline move that\nmost of `with_history` did not make is one book repricing, not the market.\n\n**First mover** names the earliest attributable book to move at least 5 % in the headline\ndirection, with its lead over the next book and the median lag of the followers. It is\n`undetermined` when fewer than two attributable books moved or when the lead is inside the\n30-second attribution floor (the books are polled every 8–17 seconds). Books whose\ntimestamps cannot order a move are excluded and named in `method`: push-fed books (the stamp\nis the receipt, not the read) and books polled slower than the floor.\n\n**Signal** is one of `strong_firm`, `firm`, `steady`, `drift`, `strong_drift` or\n`insufficient`, from `move_from_t60_pct` and the breadth, by the rule in `method.signal_rule`.\nIt is a classification of what the market did, not a prediction; its strike rate against\nresults will be published on the market report once the archive carries it.\n\n**Live only.** A race that has left the live board answers 404; a race inside the ten\nminutes after its jump is still served while the books drop off, with a note.\n\nEvery plan, Free included. 3 credits, charged after the race is found.","operationId":"racing_intelligence_v1_racing_intelligence_get","parameters":[{"name":"race_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"description":"The race_id from /v1/racing/next-to-go or /events.","title":"Race Id"},"description":"The race_id from /v1/racing/next-to-go or /events."},{"name":"venue","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"description":"Venue as the feeds spell it; needs meeting_date and race_number.","title":"Venue"},"description":"Venue as the feeds spell it; needs meeting_date and race_number."},{"name":"meeting_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"AET meeting date, YYYY-MM-DD.","title":"Meeting Date"},"description":"AET meeting date, YYYY-MM-DD."},{"name":"race_number","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":20,"minimum":1},{"type":"null"}],"title":"Race Number"}},{"name":"min_books","in":"query","required":false,"schema":{"type":"integer","maximum":14,"minimum":1,"description":"Included books a runner needs before it carries a consensus at a mark.","default":2,"title":"Min Books"},"description":"Included books a runner needs before it carries a consensus at a mark."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntelOut"},"example":{"race":{"race_id":"2e33fc28-13a5-421b-a6f3-6f99283c5cb6","venue":"Menangle","race_number":2,"category":"harness","country":"AU","start_time":"2026-10-06T01:43:00Z","secs_to_jump":763,"status":"upcoming","as_of":"2026-10-06T01:30:16Z"},"window":{"open":true,"first_tick_at":"2026-10-06T00:43:01Z","ticks_total":412,"books_in_ticks":14,"marks_available":["t60","t30","t15","m5","m2"]},"method":{"consensus":"power, median across books, field scaled to one (consensus_math); a book counts at a mark when it has a tick at or before it and is still in the market","min_books":2,"signal_rule":"strong_firm: move_from_t60_pct <= -15 and firming >= 0.6 x with_history; firm: <= -7 and firming >= 0.5 x; strong_drift / drift the mirror; steady otherwise; insufficient when there is no T-60 consensus, or when a move of 7% or more rests on fewer than 3 books with history","attribution_floor_seconds":30,"attribution_excluded_books":["betgold","boostbet","palmerbet","sportsbet"]},"market":{"market_pct":{"now":124.44,"t15":127.26,"t60":126.78},"favourite":{"now":"Alpha Stride","at_t10":null,"changed_last_10m":null},"books_in_consensus_now":13,"books_at_t60":14},"runners":[{"runner_key":"alphastride","number":10,"name":"Alpha Stride","runner_ref":null,"horse_ref":null,"books_at":{"now":13,"t60":14,"t15":13},"consensus":{"now":3.77,"t60":4.5,"t30":4.46,"t15":4.44,"t10":null,"t5":null,"t2":null,"high":{"price":4.5,"at":"2026-10-06T00:43:01Z"},"low":{"price":3.77,"at":"2026-10-06T01:30:16Z"}},"move_from_t60_pct":-16.22,"move_from_t15_pct":-15.09,"move_last_5m_pct":-15.09,"move_last_2m_pct":-9.59,"ticks_last_5m":25,"best":{"price":3.6,"bookmaker":"playup","vs_consensus_pct":-4.54},"breadth":{"quoting":13,"with_history":13,"firming":12,"drifting":1,"unchanged":0},"first_mover":{"bookmaker":null,"status":"undetermined","reason":"lead of 0s is inside the 30s attribution floor"},"signal":"strong_firm"},{"runner_key":"callmejack","number":2,"name":"Call Me Jack","runner_ref":null,"horse_ref":null,"books_at":{"now":13,"t60":14,"t15":13},"consensus":{"now":4.95,"t60":4.52,"t30":4.64,"t15":4.72,"t10":null,"t5":null,"t2":null,"high":{"price":4.95,"at":"2026-10-06T01:30:16Z"},"low":{"price":4.5,"at":"2026-10-06T00:44:01Z"}},"move_from_t60_pct":9.51,"move_from_t15_pct":4.87,"move_last_5m_pct":5.1,"move_last_2m_pct":2.91,"ticks_last_5m":11,"best":{"price":4.0,"bookmaker":"betright","vs_consensus_pct":-19.17},"breadth":{"quoting":13,"with_history":13,"firming":0,"drifting":13,"unchanged":0},"first_mover":{"bookmaker":"playup","status":"attributed","at":"2026-10-06T00:43:32Z","lead_seconds":35,"median_follower_lag_seconds":1334,"followers":["betright","pointsbetau","tabtouch","betr_au","betdeluxe","tab","unibet"]},"signal":"drift"},{"runner_key":"bertinelli","number":1,"name":"Bertinelli","runner_ref":null,"horse_ref":null,"books_at":{"now":13,"t60":14,"t15":13},"consensus":{"now":6.71,"t60":6.61,"t30":6.51,"t15":6.52,"t10":null,"t5":null,"t2":null,"high":{"price":6.71,"at":"2026-10-06T01:30:01Z"},"low":{"price":6.5,"at":"2026-10-06T01:17:01Z"}},"move_from_t60_pct":1.51,"move_from_t15_pct":2.91,"move_last_5m_pct":2.91,"move_last_2m_pct":1.82,"ticks_last_5m":1,"best":{"price":5.5,"bookmaker":"betright","vs_consensus_pct":-18.08},"breadth":{"quoting":13,"with_history":4,"firming":0,"drifting":4,"unchanged":0},"first_mover":{"bookmaker":null,"status":"undetermined","reason":"fewer than two attributable books moved"},"signal":"steady"}],"scratchings":[],"note":null}}}},"404":{"description":"No race on the live board matched. Nothing was charged."},"409":{"description":"Two races share that venue, date and number. Nothing was charged."},"422":{"description":"Unparseable parameter or unknown bookmaker key. Nothing was charged."},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/intelligence/scan":{"get":{"tags":["Racing"],"summary":"Scan the live window for runners that moved: time-anchored, breadth-gated","description":"Cost: 5 credits. Every race inside the 60-minute capture window, analysed exactly as\nGET /v1/racing/intelligence analyses one race, with the runners that cleared the filters\nreturned largest move first. This is the time-anchored, breadth-gated form of\n/v1/racing/movers, and /movers keeps serving unchanged.\n\n`window` selects the movement figure the gate applies to: `60m` for the move since the\nT-60 consensus, `15m` since T-15, `5m` over the last five minutes — the late-window\nfigure a last-minute mover product wants. `min_books` is the breadth gate: a runner needs\nthat many books with two or more captured prices, so a single book repricing cannot clear\nit. `signal` keeps only the named classes. The response carries the race header beside\neach runner so nothing has to be joined.\n\nServed from a 15-second cache shared by every caller with the same filters; `cached`\nsays so and `as_of` is the moment the scan was computed.\n\nPlus and above. 5 credits. An unknown parameter value is a free 422.","operationId":"racing_intelligence_scan_v1_racing_intelligence_scan_get","parameters":[{"name":"window","in":"query","required":false,"schema":{"type":"string","description":"Which movement gates the result: 60m (move_from_t60_pct), 15m (move_from_t15_pct) or 5m (move_last_5m_pct).","default":"60m","title":"Window"},"description":"Which movement gates the result: 60m (move_from_t60_pct), 15m (move_from_t15_pct) or 5m (move_last_5m_pct)."},{"name":"min_move_pct","in":"query","required":false,"schema":{"type":"number","maximum":500.0,"minimum":0.0,"description":"Minimum absolute consensus move, in percent.","default":10.0,"title":"Min Move Pct"},"description":"Minimum absolute consensus move, in percent."},{"name":"min_books","in":"query","required":false,"schema":{"type":"integer","maximum":14,"minimum":1,"description":"Minimum books with history on the runner (breadth).","default":3,"title":"Min Books"},"description":"Minimum books with history on the runner (breadth)."},{"name":"direction","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"firming or drifting; omit for both.","title":"Direction"},"description":"firming or drifting; omit for both."},{"name":"signal","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}],"description":"Comma-separated signal classes to keep, e.g. strong_firm,firm.","title":"Signal"},"description":"Comma-separated signal classes to keep, e.g. strong_firm,firm."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":8},{"type":"null"}],"description":"Country code, e.g. AU or NZ.","title":"Country"},"description":"Country code, e.g. AU or NZ."},{"name":"categories","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}],"description":"Comma-separated subset of horse, greyhound, harness.","title":"Categories"},"description":"Comma-separated subset of horse, greyhound, harness."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanOut"},"example":{"as_of":"2026-10-06T01:30:16Z","window_minutes":60,"races_scanned":9,"filters":{"window":"60m","min_move_pct":3.0,"min_books":2,"direction":null,"signal":null,"country":"AU","categories":null,"limit":50},"method":{"consensus":"power, median across books, field scaled to one","signal_rule":"strong_firm: move_from_t60_pct <= -15 and firming >= 0.6 x with_history; firm: <= -7 and firming >= 0.5 x; strong_drift / drift the mirror; steady otherwise; insufficient when there is no T-60 consensus, or when a move of 7% or more rests on fewer than 3 books with history","attribution_floor_seconds":30},"results":[{"race":{"race_id":"be7c3a48-7b83-4f28-84d6-209c51d03adc","venue":"HOBART","race_number":6,"category":"greyhound","country":"AU","start_time":"2026-10-06T02:22:00Z","secs_to_jump":3103},"move_pct":31.83,"runner_key":"pushthebutton","number":1,"name":"Push The Button","runner_ref":null,"horse_ref":null,"books_at":{"now":13,"t60":14,"t15":null},"consensus":{"now":109.1,"t60":82.76,"t30":null,"t15":null,"t10":null,"t5":null,"t2":null,"high":{"price":109.1,"at":"2026-10-06T01:29:02Z"},"low":{"price":82.76,"at":"2026-10-06T01:22:02Z"}},"move_from_t60_pct":31.83,"move_from_t15_pct":null,"move_last_5m_pct":6.31,"move_last_2m_pct":6.34,"ticks_last_5m":1,"best":{"price":41.0,"bookmaker":"betright","vs_consensus_pct":-62.42},"breadth":{"quoting":13,"with_history":7,"firming":0,"drifting":7,"unchanged":0},"first_mover":{"bookmaker":"unibet","status":"attributed","at":"2026-10-06T01:22:32Z","lead_seconds":35,"median_follower_lag_seconds":85,"followers":["betdeluxe","neds","ladbrokes_au","pointsbetau","playup"]},"signal":"strong_drift"},{"race":{"race_id":"be7c3a48-7b83-4f28-84d6-209c51d03adc","venue":"HOBART","race_number":6,"category":"greyhound","country":"AU","start_time":"2026-10-06T02:22:00Z","secs_to_jump":3103},"move_pct":22.9,"runner_key":"bundaberggum","number":6,"name":"Bundaberg Gum","runner_ref":null,"horse_ref":null,"books_at":{"now":13,"t60":14,"t15":null},"consensus":{"now":500.38,"t60":407.13,"t30":null,"t15":null,"t10":null,"t5":null,"t2":null,"high":{"price":500.54,"at":"2026-10-06T01:25:02Z"},"low":{"price":407.13,"at":"2026-10-06T01:22:02Z"}},"move_from_t60_pct":22.9,"move_from_t15_pct":null,"move_last_5m_pct":-0.03,"move_last_2m_pct":-0.01,"ticks_last_5m":0,"best":{"price":126.0,"bookmaker":"sportsbet","vs_consensus_pct":-74.82},"breadth":{"quoting":13,"with_history":3,"firming":0,"drifting":3,"unchanged":0},"first_mover":{"bookmaker":"unibet","status":"attributed","at":"2026-10-06T01:22:32Z","lead_seconds":60,"median_follower_lag_seconds":60,"followers":["betdeluxe"]},"signal":"strong_drift"},{"race":{"race_id":"be7c3a48-7b83-4f28-84d6-209c51d03adc","venue":"HOBART","race_number":6,"category":"greyhound","country":"AU","start_time":"2026-10-06T02:22:00Z","secs_to_jump":3103},"move_pct":22.9,"runner_key":"sydneydanger","number":8,"name":"Sydney Danger","runner_ref":null,"horse_ref":null,"books_at":{"now":13,"t60":14,"t15":null},"consensus":{"now":500.38,"t60":407.13,"t30":null,"t15":null,"t10":null,"t5":null,"t2":null,"high":{"price":500.54,"at":"2026-10-06T01:25:02Z"},"low":{"price":407.13,"at":"2026-10-06T01:22:02Z"}},"move_from_t60_pct":22.9,"move_from_t15_pct":null,"move_last_5m_pct":-0.03,"move_last_2m_pct":-0.01,"ticks_last_5m":0,"best":{"price":126.0,"bookmaker":"betright","vs_consensus_pct":-74.82},"breadth":{"quoting":13,"with_history":3,"firming":0,"drifting":3,"unchanged":0},"first_mover":{"bookmaker":"unibet","status":"attributed","at":"2026-10-06T01:22:32Z","lead_seconds":60,"median_follower_lag_seconds":60,"followers":["betdeluxe"]},"signal":"strong_drift"}],"cached":false}}}},"403":{"description":"The scan is available on Plus, Business, Platform and Scale plans. The refusal is free."},"422":{"description":"Unparseable parameter. Nothing was charged."},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/racing/market-summary":{"get":{"tags":["Racing Archive"],"summary":"The market's path per runner, stored at the jump: consensus at fixed marks, moves, breadth, first mover, signal","description":"Cost: 5 credits (JSON) / 20 credits (CSV). The market summary archive: one row per race\nand runner, written at the jump, carrying the market consensus price at T-60, T-30, T-15,\nT-10, T-5 and T-2 and at the close, its high and low through the capture window,\n`move_from_t60_pct` / `move_from_t15_pct` / `move_t5_close_pct` (close against those\nmarks; negative is a runner that shortened), the breadth of the move (books with history,\nfirming, drifting, unchanged), the first attributable book to move with its lead and the\nfollowers' median lag, the signal class, and the best closing price against the consensus.\n\n**It is GET /v1/racing/intelligence, kept.** The same analysis, run at the jump with the\narchive's own `consensus_close_price` as the close (`close_source: archive`; a runner the\narchive could not score falls back to the ticks at the jump, `close_source: ticks`), so a\nlive reading and the stored row are one quantity. The ticks it is built from live in the\n45-day snapshot store; this row is permanent, from `summary_from` forward.\n\n**Joined at serve time, appended last:** `venue_id`, `venue_site`, `horse_ref`, and the\nrunner's `finish_position` and `result_status` from the closing-line archive, which land\nafter the summary is written. Scratched runners carry no row.\n\n**The signal classes against results** are what this table exists for. `signal` filters\non them; joined to `finish_position` the table says, for any window, how often each class\nwon and at what price. Those strike rates belong on the market report; until they are\nprinted there, compute them from this table rather than trusting the label.\n\n**Plans.** Plus and above read the whole table; Standard a rolling 90-day window\n(`window_days` 90, `X-Archive-Window-Days`); Free and Hobby a free 403. Standard keys\nissued before 2026-09-20 keep the whole archive they were sold. `date=` is a single-day\nalias for `from`/`to`; an unknown parameter, signal or date is a free 422.","operationId":"market_summary_v1_racing_market_summary_get","parameters":[{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date/time on the race start. Defaults to the table's floor.","title":"From"},"description":"ISO date/time on the race start. Defaults to the table's floor."},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date/time. Defaults to now.","title":"To"},"description":"ISO date/time. Defaults to now."},{"name":"date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Single-day alias: date=YYYY-MM-DD is exactly from=YYYY-MM-DD&to=YYYY-MM-DD. Mutually exclusive with from/to.","title":"Date"},"description":"Single-day alias: date=YYYY-MM-DD is exactly from=YYYY-MM-DD&to=YYYY-MM-DD. Mutually exclusive with from/to."},{"name":"venue","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Venue name, case-insensitive, exact match.","title":"Venue"},"description":"Venue name, case-insensitive, exact match."},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"horse, harness or greyhound — comma-separated for more than one.","title":"Category"},"description":"horse, harness or greyhound — comma-separated for more than one."},{"name":"categories","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Alias of `category`. Comma-separated horse, harness, greyhound. Both spellings are accepted on every racing endpoint.","title":"Categories"},"description":"Alias of `category`. Comma-separated horse, harness, greyhound. Both spellings are accepted on every racing endpoint."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter country code, e.g. AU, NZ.","title":"Country"},"description":"Two-letter country code, e.g. AU, NZ."},{"name":"race_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One race.","title":"Race Id"},"description":"One race."},{"name":"signal","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"description":"Comma-separated signal classes to keep: strong_firm, firm, steady, drift, strong_drift, insufficient.","title":"Signal"},"description":"Comma-separated signal classes to keep: strong_firm, firm, steady, drift, strong_drift, insufficient."},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv)$","description":"csv streams a flat table with a stable column order.","default":"json","title":"Format"},"description":"csv streams a flat table with a stable column order."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50000,"minimum":1,"default":500,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketSummaryOut"},"example":{"rows_returned":2,"total_rows":6953,"limit":2,"offset":0,"summary_from":"2026-08-21T22:26:00Z","window_days":null,"rows":[{"race_id":"ed191df6-9f77-4e35-a7a9-0757d89430d9","runner_key":"trantoro","venue":"Gawler","race_number":7,"category":"horse","country":"AU","start_time":"2026-10-05T06:55:00Z","meeting_date_aet":"2026-10-05","runner_name":"Trantoro","runner_number":10,"runner_ref":"ra:NzM2MjcwNDkwMA","consensus_t60":4.77,"consensus_t30":4.75,"consensus_t15":4.73,"consensus_t10":4.74,"consensus_t5":4.49,"consensus_t2":4.48,"consensus_close":4.269,"close_source":"archive","books_t60":13,"books_t15":13,"books_close":13,"consensus_high":4.77,"consensus_high_at":"2026-10-05T06:12:04Z","consensus_low":4.27,"consensus_low_at":"2026-10-05T06:54:04Z","move_from_t60_pct":-10.5,"move_from_t15_pct":-9.75,"move_t5_close_pct":-4.92,"ticks_total":96,"ticks_last_5m":42,"books_with_history":14,"books_firming":14,"books_drifting":0,"books_unchanged":0,"first_mover_bookmaker":null,"first_mover_at":null,"first_mover_lead_s":null,"follower_median_lag_s":null,"signal":"firm","best_close_price":4.4,"best_close_bookmaker":"betgold","best_vs_consensus_pct":3.07,"venue_id":"gawler","venue_site":"gawler","horse_ref":"pe:trantoro","finish_position":1,"result_status":"final"},{"race_id":"ed191df6-9f77-4e35-a7a9-0757d89430d9","runner_key":"willaston","venue":"Gawler","race_number":7,"category":"horse","country":"AU","start_time":"2026-10-05T06:55:00Z","meeting_date_aet":"2026-10-05","runner_name":"Willaston","runner_number":16,"runner_ref":"ra:NzMyOTk0Njc3MA","consensus_t60":44.8,"consensus_t30":44.59,"consensus_t15":45.82,"consensus_t10":49.52,"consensus_t5":36.83,"consensus_t2":31.58,"consensus_close":31.41,"close_source":"archive","books_t60":13,"books_t15":13,"books_close":13,"consensus_high":50.3,"consensus_high_at":"2026-10-05T06:43:04Z","consensus_low":31.58,"consensus_low_at":"2026-10-05T06:53:04Z","move_from_t60_pct":-29.89,"move_from_t15_pct":-31.45,"move_t5_close_pct":-14.72,"ticks_total":100,"ticks_last_5m":45,"books_with_history":13,"books_firming":13,"books_drifting":0,"books_unchanged":0,"first_mover_bookmaker":null,"first_mover_at":null,"first_mover_lead_s":null,"follower_median_lag_s":null,"signal":"strong_firm","best_close_price":34.0,"best_close_bookmaker":"betr_au","best_vs_consensus_pct":8.25,"venue_id":"gawler","venue_site":"gawler","horse_ref":"pe:willaston","finish_position":2,"result_status":"final"}]}},"text/csv":{"schema":{"type":"string","example":"race_id,runner_key,venue,..."}}}},"403":{"description":"A paid endpoint; the refusal is free and names the tiers."},"422":{"description":"Unparseable parameter or unknown signal. Nothing was charged."},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"x-curl-query":"date=2026-10-03&category=horse&signal=strong_firm,firm"}},"/v1/bulk/datasets":{"get":{"tags":["Bulk data"],"summary":"Bulk dataset catalogue","description":"Cost: 0 credits. Every dataset this API publishes as pre-built files, with the daily\nfiles and monthly bundles that exist right now: rows, bytes per format, digest, when each\nwas generated, and the URL that serves it. Read it before pulling, and re-read it nightly\n— a day's file is regenerated for three nights after the day (late results, late closing\nlines) and its digest changes when its rows do.\n\nDaily files are Business and above; monthly bundles are Platform and above. Both formats\nare always present: `format=parquet` (typed columns, snappy) and `format=csv` (gzip).","operationId":"bulk_catalogue_v1_bulk_datasets_get","parameters":[{"name":"dataset","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Restrict to one dataset name.","title":"Dataset"},"description":"Restrict to one dataset name."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkCatalogueOut"},"example":{"datasets":[{"name":"closing_lines","title":"Closing lines","description":"Every bookmaker's last observed price before the jump, one row per runner per book, with the result joined on.","grain":"runner x bookmaker x race","columns":["race_id","runner_key","bookmaker_key","venue","race_number","category","country","start_time","meeting_date_aet","places_paid","runner_name","runner_number","runner_ref","horse_ref","win_price","place_price","finish_position","venue_id","venue_site"],"available_from":"2026-08-18","days":[{"period":"2026-09-24","kind":"day","rows":31221,"generated_at":"2026-09-25T04:31:02Z","bytes":{"parquet":1842104,"csv":4310882},"sha256":{"parquet":"3f1c…","csv":"a07e…"},"url":"/v1/bulk/closing_lines/2026-09-24"}],"months":[{"period":"2026-08","kind":"month","rows":742118,"generated_at":"2026-09-01T04:33:41Z","bytes":{"parquet":41220918,"csv":98551204},"sha256":{"parquet":"b914…","csv":"6c2d…"},"url":"/v1/bulk/closing_lines/2026-08"}]}],"formats":["parquet","csv"],"day_plans":"Business and above","month_plans":"Platform and above","credits":{"day":50,"month":500},"schedule":"Daily files land by 05:00 AEST; monthly bundles on the 1st.","notes":["Send If-None-Match with a file's sha256 and an unchanged file answers 304 for free."]}}}},"403":{"description":"Bulk dataset files are available on Business, Platform, Scale and Enterprise plans only."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/bulk/restatements":{"get":{"tags":["Bulk data"],"summary":"Changes to already-published dataset files","description":"Cost: 0 credits. Every time a dataset file changed AFTER it was first published, newest\nfirst: which file, when, why, and how many rows were removed, changed and added.\n\nA day's file is regenerated for three nights after the day, and a closed month's bundle on\nthe first three nights of the next month. When a regeneration produces different bytes\n(the csv digest changes), the change is recorded here and in the file's own sidecar. The\nreason is `orphan_merge` when the archive folded a duplicate race record onto the race\nthat was run, and `regeneration` for anything else — usually late results or late closing\nlines. A mirror that re-checks files by ETag sees the same changes; this list says why.\n\nThe current month's bundle grows every night as days are added and is not listed until the\nmonth has closed.","operationId":"bulk_restatements_v1_bulk_restatements_get","parameters":[{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date or date-time (UTC); only restatements published at or after it.","title":"Since"},"description":"ISO date or date-time (UTC); only restatements published at or after it."},{"name":"dataset","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Restrict to one dataset name.","title":"Dataset"},"description":"Restrict to one dataset name."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"At most 200.","default":50,"title":"Limit"},"description":"At most 200."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RestatementsOut"},"example":{"count":1,"notes":["Newest first. A restatement is recorded when a regenerated file's csv sha256 differs from the file it replaces."],"restatements":[{"at":"2026-10-13T15:31:07Z","dataset":"closing_lines","detail":{"merges":1,"method":"keyed","previous_generated_at":"2026-10-12T15:30:44Z","rows_after":31221,"rows_before":31230,"store_rows_deleted":9,"store_rows_repointed":0,"within_settle_window":true},"id":412,"period":"2026-10-11","period_kind":"day","reason":"orphan_merge","rows_added":0,"rows_changed":0,"rows_removed":9,"sha256_after":"3f1c92…","sha256_before":"a07e5c…"}]}}}},"403":{"description":"Bulk dataset files are available on Business, Platform, Scale and Enterprise plans only."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/bulk/{dataset}/{period}":{"get":{"tags":["Bulk data"],"summary":"Download one dataset file","description":"Cost: 20 credits for a daily file, 100 for a monthly bundle, charged when the file is\nserved; a 304 costs nothing. One request returns the whole day or month as one file, with\nexactly the rows the paged endpoints would give you: Betfair withheld, split-flagged rows\nexcluded, the field-disclosure rule applied to results.\n\n`pd.read_parquet(BytesIO(r.content))` reads the Parquet; `format=csv` is a gzip file\n(`pd.read_csv(BytesIO(r.content), compression=\"gzip\")`) with the same column order,\nwhich is the order listed in the catalogue.\n\nDaily files are Business and above. Monthly bundles are Platform and above; a Business\nkey asking for a month gets a 403 that says so and is not charged.","operationId":"bulk_file_v1_bulk__dataset___period__get","parameters":[{"name":"dataset","in":"path","required":true,"schema":{"type":"string","description":"Dataset name from /v1/bulk/datasets.","examples":["closing_lines"],"title":"Dataset"},"description":"Dataset name from /v1/bulk/datasets."},{"name":"period","in":"path","required":true,"schema":{"type":"string","description":"YYYY-MM-DD for a Sydney meeting day, YYYY-MM for a calendar month.","examples":["2026-09-19"],"title":"Period"},"description":"YYYY-MM-DD for a Sydney meeting day, YYYY-MM for a calendar month."},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(parquet|csv)$","description":"parquet (typed, snappy) or csv (gzip-compressed).","default":"parquet","title":"Format"},"description":"parquet (typed, snappy) or csv (gzip-compressed)."},{"name":"If-None-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"If-None-Match"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"The file. ETag is the sha256; X-Dataset-Rows and X-Dataset-Generated-At describe it.","content":{"application/json":{"schema":{}},"application/vnd.apache.parquet":{},"text/csv":{}}},"304":{"description":"If-None-Match matched — unchanged, nothing charged."},"403":{"description":"Daily files: Business and above. Monthly bundles: Platform and above."},"404":{"description":"No file for that dataset and period (yet)."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/keys/team":{"post":{"tags":["Account"],"summary":"Mint a team key","description":"Cost: 0 credits. Mint another key on this account. It shares this account's monthly\ncredit pool and plan, has its own rate-limit bucket, its own IP whitelist and its own line\nin the usage breakdown, and can be revoked on its own. Business accounts may hold 3 keys\nin total, Platform 5, Scale 10 — the primary key counts as one.\n\nPass `monthly_cap` to limit what this one key may spend per month inside the pool — the\nkey refuses with 402 at its cap and the pool is not touched, so one client, one worker or\none trial cannot spend the account's month. Change or remove it later with\n`PATCH /v1/keys/team/{key_id}`. Caps reset with the pool on the 1st.\n\nThe key is returned once. It is stored hashed, like every key here, so if it is lost the\nanswer is revoke and mint again, not recovery.","operationId":"mint_team_key_v1_keys_team_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamKeyIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamKeyMintOut"},"example":{"key_id":"0c9a52d1-6b34-47ef-8a2b-5d1e93c4f087","api_key":"pe_4f9c2a7e1b8d3c6a5e0f7b2d9c4a1e8f","label":"Staging","plan":"pro_plus","allowance":3,"keys_in_use":2,"pool_credits_limit":300000,"pool_credits_used":221110,"monthly_cap":25000,"max_monthly_cap":300000}}}},"403":{"description":"Team keys are available on Business, Platform, Scale and Enterprise plans only, and only the primary key may mint them."},"409":{"description":"The account already holds its allowance of keys."},"422":{"description":"monthly_cap is outside 1..the plan's monthly credit allowance."},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"get":{"tags":["Account"],"summary":"List this account's keys","description":"Cost: 0 credits. The primary key and every active team key on this account, with each\none's calls and credits this billing period, its monthly cap if it has one, and the\nshared pool's balance.","operationId":"list_team_keys_v1_keys_team_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamKeyListOut"},"example":{"plan":"pro_plus","allowance":3,"keys_in_use":2,"pool_credits_used":221110,"pool_credits_limit":300000,"pool_credits_remaining":78890,"max_monthly_cap":300000,"primary":{"key_id":"b7f41c2e-3d88-4a15-9e60-1c2fa7d05b3e","key_hint":"9f3c1a7d…","label":"Production","plan":"pro_plus","created_at":"2026-07-14T02:11:09Z","last_used_at":"2026-09-25T00:04:37Z","period_calls":184206,"period_credits":214930,"monthly_cap":null,"key_credits_used":0},"team":[{"key_id":"0c9a52d1-6b34-47ef-8a2b-5d1e93c4f087","key_hint":"41e8b2c5…","label":"Staging","plan":"pro_plus","created_at":"2026-08-02T23:40:55Z","last_used_at":"2026-09-24T21:58:02Z","period_calls":5120,"period_credits":6180,"monthly_cap":25000,"key_credits_used":6180}]}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/keys/team/{key_id}":{"patch":{"tags":["Account"],"summary":"Change a team key's cap or label","description":"Cost: 0 credits. Set, change or remove one team key's monthly credit cap, and/or\nrelabel it. The key string does not change and nothing needs redeploying.\n\n`{\"monthly_cap\": 5000}` caps this key at 5,000 credits for the rest of this period —\nagainst the counter it has ALREADY spent, so lowering a cap below what a key has used\nthis month refuses it until the 1st rather than rewriting history. `{\"monthly_cap\":\nnull}` removes the cap and lets the key spend the whole pool. Leave a field out and it is\nnot touched: `{\"label\": \"Jo's laptop\"}` alone renames the key and leaves its cap alone.","operationId":"patch_team_key_v1_keys_team__key_id__patch","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","description":"key_id from GET /v1/keys/team.","title":"Key Id"},"description":"key_id from GET /v1/keys/team."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamKeyPatchIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamKeyOut"},"example":{"key_id":"0c9a52d1-6b34-47ef-8a2b-5d1e93c4f087","key_hint":"41e8b2c5…","label":"Staging","plan":"pro_plus","created_at":"2026-08-02T23:40:55Z","last_used_at":"2026-09-24T21:58:02Z","period_calls":5120,"period_credits":6180,"monthly_cap":25000,"key_credits_used":6180}}}},"403":{"description":"Only the account's primary key may change a team key."},"404":{"description":"No team key with that id on this account."},"422":{"description":"monthly_cap is outside 1..the plan's monthly credit allowance."},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"tags":["Account"],"summary":"Revoke a team key","description":"Cost: 0 credits. Deactivate one team key immediately. Its usage history stays in the\naccount's breakdown; the key string stops authenticating on the next request.","operationId":"revoke_team_key_v1_keys_team__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","description":"key_id from GET /v1/keys/team.","title":"Key Id"},"description":"key_id from GET /v1/keys/team."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"revoked":"0c9a52d1-6b34-47ef-8a2b-5d1e93c4f087","label":"Staging","keys_in_use":1}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/widgets":{"post":{"tags":["Account"],"summary":"Create a white-label widget","description":"Cost: 0 credits. Create an embeddable odds widget that carries YOUR branding.\n\nThe widget renders from the same feed as our own public widget, in your colours, with\nyour bookmaker links and no PuntersEdge credit. Embed it with the `embed` snippets in\nthe response; the `csp` line is what an embedding page with a Content-Security-Policy\nneeds to allow the frame.\n\nEach RENDER is metered against this account — 2 credits for a racing fill, 1 for a\nsports fill — but the page is cached for 60 seconds per parameter set, so the cost\nfollows time, not traffic: a widget on a page with ten thousand readers costs the same\nas one on a page with one.","operationId":"create_widget_v1_widgets_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WidgetIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"widget_id":"wgt_3f9a1c74e20b6d58","label":"Form guide sidebar","config":{"bookmakers":["sportsbet","tab","ladbrokes_au"],"defaults":{"country":"AU","sports":["afl","nrl"]},"theme":"light"},"embed":{"racing":"<iframe src=\"https://puntersedge.online/widget/v1/wgt_3f9a1c74e20b6d58/racing\" width=\"100%\" height=\"500\" frameborder=\"0\" loading=\"lazy\" title=\"Next to go\"></iframe>","sports":{"afl":"<iframe src=\"https://puntersedge.online/widget/v1/wgt_3f9a1c74e20b6d58/afl\" width=\"100%\" height=\"500\" frameborder=\"0\" loading=\"lazy\" title=\"afl\"></iframe>","nrl":"<iframe src=\"https://puntersedge.online/widget/v1/wgt_3f9a1c74e20b6d58/nrl\" width=\"100%\" height=\"500\" frameborder=\"0\" loading=\"lazy\" title=\"nrl\"></iframe>"}},"csp":"frame-src https://puntersedge.online"}}}},"403":{"description":"The white-label widget is a Platform entitlement, and only the account's primary key may manage widgets."},"409":{"description":"The account already holds its 10 active widgets."},"422":{"description":"A config field is invalid; the message names it."},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"get":{"tags":["Account"],"summary":"List this account's widgets","description":"Cost: 0 credits. Every widget on this account, with how many times each has been\nfilled (`fills_total`, exact per widget) and what widgets have cost this ACCOUNT so far\nthis billing period (`credits_this_period`, the account total — usage_logs is keyed by\nkey and endpoint, so a per-widget split of the spend does not exist).","operationId":"list_widgets_v1_widgets_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"widgets":[{"widget_id":"wgt_3f9a1c74e20b6d58","label":"Form guide sidebar","active":true,"created_at":"2026-09-11T05:22:40Z","last_served_at":"2026-09-25T00:03:12Z","fills_total":48211}],"credits_this_period":4821,"credits_this_period_scope":"account","max_widgets":10,"csp":"frame-src https://puntersedge.online"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/widgets/{widget_id}":{"get":{"tags":["Account"],"summary":"One widget","description":"Cost: 0 credits. The stored (normalised) config, the embed snippets and the counters.","operationId":"get_widget_v1_widgets__widget_id__get","parameters":[{"name":"widget_id","in":"path","required":true,"schema":{"type":"string","description":"wgt_… from POST /v1/widgets.","title":"Widget Id"},"description":"wgt_… from POST /v1/widgets."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"widget_id":"wgt_3f9a1c74e20b6d58","label":"Form guide sidebar","active":true,"created_at":"2026-09-11T05:22:40Z","last_served_at":"2026-09-25T00:03:12Z","fills_total":48211,"config":{"bookmakers":["sportsbet","tab","ladbrokes_au"],"defaults":{"country":"AU","sports":["afl","nrl"]},"theme":"light"},"embed":{"racing":"<iframe src=\"https://puntersedge.online/widget/v1/wgt_3f9a1c74e20b6d58/racing\" width=\"100%\" height=\"500\" frameborder=\"0\" loading=\"lazy\" title=\"Next to go\"></iframe>","sports":{"afl":"<iframe src=\"https://puntersedge.online/widget/v1/wgt_3f9a1c74e20b6d58/afl\" width=\"100%\" height=\"500\" frameborder=\"0\" loading=\"lazy\" title=\"afl\"></iframe>","nrl":"<iframe src=\"https://puntersedge.online/widget/v1/wgt_3f9a1c74e20b6d58/nrl\" width=\"100%\" height=\"500\" frameborder=\"0\" loading=\"lazy\" title=\"nrl\"></iframe>"}},"csp":"frame-src https://puntersedge.online"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"patch":{"tags":["Account"],"summary":"Change a widget's label or config","description":"Cost: 0 credits. `config` is a FULL replacement, validated the same way as on create —\na merge would make it impossible to remove an allowed origin or a per-book override, and\n\"send it back with your change in it\" is the only rule that has one meaning. The widget\nid and the embed snippets do not change, so nothing on the customer's page needs redeploying.","operationId":"patch_widget_v1_widgets__widget_id__patch","parameters":[{"name":"widget_id","in":"path","required":true,"schema":{"type":"string","description":"wgt_… from POST /v1/widgets.","title":"Widget Id"},"description":"wgt_… from POST /v1/widgets."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WidgetPatchIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"widget_id":"wgt_3f9a1c74e20b6d58","label":"Form guide sidebar","active":true,"created_at":"2026-09-11T05:22:40Z","last_served_at":"2026-09-25T00:03:12Z","fills_total":48211,"config":{"bookmakers":["sportsbet","tab","ladbrokes_au"],"defaults":{"country":"AU","sports":["afl","nrl"]},"theme":"light"},"embed":{"racing":"<iframe src=\"https://puntersedge.online/widget/v1/wgt_3f9a1c74e20b6d58/racing\" width=\"100%\" height=\"500\" frameborder=\"0\" loading=\"lazy\" title=\"Next to go\"></iframe>","sports":{"afl":"<iframe src=\"https://puntersedge.online/widget/v1/wgt_3f9a1c74e20b6d58/afl\" width=\"100%\" height=\"500\" frameborder=\"0\" loading=\"lazy\" title=\"afl\"></iframe>","nrl":"<iframe src=\"https://puntersedge.online/widget/v1/wgt_3f9a1c74e20b6d58/nrl\" width=\"100%\" height=\"500\" frameborder=\"0\" loading=\"lazy\" title=\"nrl\"></iframe>"}},"csp":"frame-src https://puntersedge.online"}}}},"422":{"description":"A config field is invalid; the message names it."},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"tags":["Account"],"summary":"Revoke a widget","description":"Cost: 0 credits. Stop serving this widget. Any iframe still embedding it renders the\nneutral \"odds unavailable\" frame from the next fill on — never an error page on someone\nelse's website. The row is kept (active=false) so its counters survive.","operationId":"revoke_widget_v1_widgets__widget_id__delete","parameters":[{"name":"widget_id","in":"path","required":true,"schema":{"type":"string","description":"wgt_… from POST /v1/widgets.","title":"Widget Id"},"description":"wgt_… from POST /v1/widgets."},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"revoked":"wgt_3f9a1c74e20b6d58","label":"Form guide sidebar","active":false}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/link-templates":{"get":{"tags":["Account"],"summary":"Your stored bookmaker link templates","description":"Cost: 0 credits. The link templates stored on this account, or `links: null` when none\nare. Any key on the account may read them; team keys use the same templates.\n\n`fields` names each `*_url` field and the `*_link` twin built from it, `endpoints` lists\nwhere the twins appear, and `preview` renders every rule against an example URL so you can\ncheck a template by eye. Change them with PUT, remove them with DELETE.","operationId":"get_link_templates_v1_link_templates_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"links":{"mode":"source","per_bookmaker":{"ladbrokes_au":"https://track.example/click?aff=123&dest={url}","playup":"none"}},"updated_at":"2026-10-14T01:12:05Z","fields":{"source_url":"source_link","betslip_win_url":"betslip_win_link","betslip_place_url":"betslip_place_link","betslip_url":"betslip_link"},"endpoints":["/v1/racing/next-to-go","/v1/racing/best-odds","/v1/racing/markets","/v1/racing/changes"],"preview":[{"bookmaker":"ladbrokes_au","source_url":"https://www.bookmaker.example/racing/race/1","source_link":"https://track.example/click?aff=123&dest=https%3A%2F%2Fwww.bookmaker.example%2Fracing%2Frace%2F1"},{"bookmaker":"playup","source_url":"https://www.bookmaker.example/racing/race/1","source_link":null},{"bookmaker":"(every other book)","source_url":"https://www.bookmaker.example/racing/race/1","source_link":"https://www.bookmaker.example/racing/race/1"}]}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"put":{"tags":["Account"],"summary":"Store your bookmaker link templates","description":"Cost: 0 credits. Store how every bookmaker link in your racing responses should be built.\nA FULL replacement: send the whole document each time.\n\nOnce stored, every racing response that carries a bookmaker URL also carries a ready-made\ntwin built through your templates: `source_link` beside `source_url` (next-to-go legs,\nbest-odds quotes, markets), `betslip_win_link` / `betslip_place_link` beside the betslip\nURLs (next-to-go, changes) and `betslip_link` beside `betslip_url` (best-odds). Put the\n`*_link` value on your page. A twin is absent where there is nothing to link: the book gave\nno URL, or your rule for that book is \"none\". The `*_url` fields are unchanged.\n\nThe document:\n\n- `mode`: what a book without its own rule gets. `\"source\"` (default) links straight to the\n  bookmaker's URL, `\"template\"` sends it through `template`, `\"none\"` gives no link.\n- `template`: your tracking link with `{url}` where the bookmaker's URL goes\n  (percent-encoded), or `{url_raw}` to insert it unencoded. Must start `https://`. Required\n  when `mode` is `\"template\"`.\n- `per_bookmaker`: a rule per bookmaker key (`ladbrokes_au`, `unibet`, `tab`, ...): a\n  template, `\"source\"` or `\"none\"`. It beats `mode`. An unknown key is refused, so a typo\n  cannot silently drop a book's link.\n\nWe append nothing of our own to any link. Whether a bookmaker's affiliate programme credits\na click, and whether it accepts a deep-link destination at all, is between you and that\nprogramme. Prices shown to your users still need the visible \"Prices by PuntersEdge\"\ncredit your plan's licence requires (Terms 8.1 and 8.3); Platform's white-label widget is\nthe version without it.\n\nTakes effect on your next request. Primary key only. Step-by-step guide with curl, Python and\nJavaScript examples: https://puntersedge.online/developers/affiliate-links","operationId":"put_link_templates_v1_link_templates_put","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","examples":[{"mode":"source","per_bookmaker":{"ladbrokes_au":"https://track.example/click?aff=123&dest={url}","unibet":"https://other-tracker.example/?pid=9&url={url}","playup":"none"}}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"links":{"mode":"source","per_bookmaker":{"ladbrokes_au":"https://track.example/click?aff=123&dest={url}","playup":"none"}},"updated_at":"2026-10-14T01:12:05Z","fields":{"source_url":"source_link","betslip_win_url":"betslip_win_link","betslip_place_url":"betslip_place_link","betslip_url":"betslip_link"},"endpoints":["/v1/racing/next-to-go","/v1/racing/best-odds","/v1/racing/markets","/v1/racing/changes"],"preview":[{"bookmaker":"ladbrokes_au","source_url":"https://www.bookmaker.example/racing/race/1","source_link":"https://track.example/click?aff=123&dest=https%3A%2F%2Fwww.bookmaker.example%2Fracing%2Frace%2F1"},{"bookmaker":"playup","source_url":"https://www.bookmaker.example/racing/race/1","source_link":null},{"bookmaker":"(every other book)","source_url":"https://www.bookmaker.example/racing/race/1","source_link":"https://www.bookmaker.example/racing/race/1"}]}}}},"403":{"description":"Stored link templates are a Plus entitlement, and only the account's primary key may change them."},"422":{"description":"A field is invalid; the message names it."},"503":{"description":"Link templates are not available yet."},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"tags":["Account"],"summary":"Remove your stored bookmaker link templates","description":"Cost: 0 credits. Remove this account's link templates. From your next request the racing\nresponses carry no `*_link` fields; the `*_url` fields are unchanged. Primary key only.","operationId":"delete_link_templates_v1_link_templates_delete","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"deleted":true,"links":null}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/billing/overage":{"get":{"tags":["Account"],"summary":"Overage: state, rate and ceiling","description":"Cost: 0 credits. Whether this account may run past its monthly allowance, what that\ncosts, where it stops, and how much of it this period has used.\n\n`ceiling` is the number the API actually enforces: when overage is off it equals your\ncredit limit, which is the hard 402 every plan has always had. `max_credits` is what the\nplan allows when it is on. `estimated_cents_this_period` is what would be invoiced if the\nmonth ended now — the same arithmetic the billing run uses, rounded up to whole 1,000s.\n\nA team key sees the POOL's figures: overage belongs to the account, and a team key's own\nmonthly cap is hard.","operationId":"get_overage_v1_billing_overage_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverageOut"},"example":{"ceiling":450000,"credits_limit":300000,"credits_over_this_period":12400,"credits_used":312400,"eligible":true,"enabled":true,"estimated_cents_this_period":780,"max_credits":150000,"plan":"pro_plus","rate_per_1000_cents":60}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["Account"],"summary":"Switch overage on or off","description":"Cost: 0 credits. Primary key only, commercial tiers only.\n\nSwitching it ON lets calls continue past your allowance, up to `ceiling`, and the excess\nis invoiced at `rate_per_1000_cents` per 1,000 credits on your next invoice. Switching it\nOFF restores the hard 402 at the allowance immediately — including mid-period, in which\ncase the overage already used is still invoiced, because it was already served.","operationId":"set_overage_v1_billing_overage_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverageIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverageOut"},"example":{"ceiling":450000,"credits_limit":300000,"credits_over_this_period":12400,"credits_used":312400,"eligible":true,"enabled":true,"estimated_cents_this_period":780,"max_credits":150000,"plan":"pro_plus","rate_per_1000_cents":60}}}},"403":{"description":"Team keys cannot change it, and it is available on the commercial tiers only."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/referral":{"get":{"tags":["Referrals"],"summary":"Your referral link and earnings","description":"Your referral code, the link to share, and what it has earned.\n\nThe code is minted on your first call to this endpoint and never changes afterwards, so the\nlink is safe to print. Sharing it costs nothing and referring a free signup earns nothing:\nthe commission is on what a referred customer actually pays.\n\n**What you earn.** 10% of every invoice a customer you referred pays us, for 12 months from\ntheir first payment, taken either as cash or as API credits — `terms` in the response\ncarries the live numbers rather than these words, which is what you should read from.\n\n**Credits settle immediately** and are added to your allowance for the current month.\n**Cash is held** until it clears the minimum payout in `terms.min_payout_aud`, then paid by\ntransfer. `owing_aud` is what is currently unpaid.","operationId":"my_referral_v1_referral_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"code":"PE-7QK2ZD","reward_type":"credits","payout_email":null,"active":true,"terms":{"commission_pct":10,"term_months":12,"credits_per_aud":1000,"min_payout_aud":50},"signups":14,"converted":3,"active_referrals":3,"expired_referrals":0,"earned_aud":26.1,"owing_aud":0.0,"credits_earned":26100,"recent":[{"at":"2026-09-19T04:12:08Z","amount_aud":9.9,"reward":"credits"}],"link":"https://puntersedge.online/api?ref=PE-7QK2ZD"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["Referrals"],"summary":"Choose how your referral reward is paid","description":"Switch between `cash` and `credits`, and set a payout address.\n\nThe switch applies to commission accrued from here on. Anything already in your ledger keeps\nthe form it was earned in — we do not retroactively convert a paid-out dollar into credits or\nthe other way round.","operationId":"set_reward_v1_referral_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}},{"name":"X-RapidAPI-Proxy-Secret","in":"header","required":false,"schema":{"type":"string","title":"X-Rapidapi-Proxy-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RewardIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{},"example":{"code":"PE-7QK2ZD","reward_type":"cash","payout_email":"example@example.com","active":true,"terms":{"commission_pct":10,"term_months":12,"credits_per_aud":1000,"min_payout_aud":50},"signups":14,"converted":3,"active_referrals":3,"expired_referrals":0,"earned_aud":26.1,"owing_aud":0.0,"credits_earned":26100,"recent":[{"at":"2026-09-19T04:12:08Z","amount_aud":9.9,"reward":"credits"}],"link":"https://puntersedge.online/api?ref=PE-7QK2ZD","note":"Switched to cash. Earnings already settled as credits stay as credits."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key. Send `X-API-Key`. See https://puntersedge.online/developers/errors","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Monthly credit allowance exhausted for this plan. Upgrade at https://puntersedge.online/api/pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this plan. Honour the `Retry-After` header (seconds) before retrying. See https://puntersedge.online/developers/rate-limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server error. Safe to retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}},"components":{"schemas":{"AcceptancesOut":{"properties":{"date":{"type":"string","title":"Date","description":"Meeting day, Australia/Sydney calendar date."},"date_status":{"type":"string","title":"Date Status","description":"'past', 'today' or 'future', relative to the Australia/Sydney date. On 'past' this card is a FROZEN SNAPSHOT: acceptances are collected for today and forward only, so a meeting still showing acceptances_published false will never fill in and the scratching set can never converge. On 'today'/'future' the next collection pass CAN add acceptances and scratchings, every two hours — but that is not a promise that a meeting converges. Racing Australia stops listing a meeting once it is UNDERWAY, so a meeting's rows stop advancing around 12:15-14:15 AEST while its own races are still running to 17:00-19:30. Log-verified 2026-09-09: the 02:15Z pass discovered all six of that day's meetings, the 04:15Z pass discovered one, the 06:15Z pass discovered none. The in-band signal is per meeting, not per day — watch its own as_of and scratching_sheet_as_of: a stamp that stops moving while later passes land on other meetings is a meeting that has gone underway, not a quiet one. From that point race-day movement is on /v1/racing/changes, not here."},"meeting_count":{"type":"integer","title":"Meeting Count"},"race_count":{"type":"integer","title":"Race Count"},"runner_count":{"type":"integer","title":"Runner Count","description":"Acceptance rows on this card — scratched runners and emergencies INCLUDED. A floor, not a guarantee, wherever a race reports runner_set_status other than 'checks_passed'."},"emergency_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Emergency Count","description":"Rows flagged as an emergency acceptance. NULL unless every meeting with rows on this card has emergency_data 'parsed' — see completeness. A FLOOR, not an exact figure, wherever a race reports runner_set_status other than 'checks_passed': it counts only the acceptance rows this card holds."},"declared_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Declared Count","description":"runner_count minus emergency_count: the DECLARED field, i.e. the acceptances that are not emergencies. NULL when emergency_count is. A FLOOR wherever a race reports runner_set_status other than 'checks_passed'."},"scratched_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Scratched Count","description":"Sheet rows MATCHED to an acceptance row this card holds — NOT every scratching Racing Australia lists. A sheet row with no acceptance row here is not counted; it appears in that race's missing_runners with scratched true. Measured 2026-09-08: Townsville R6 has scratching_data 'parsed' and scratched_count 0 while the sheet holds #4 Five Star King and #9 Kickoff, so the true figure is at least 2. Read it as a FLOOR wherever a race reports runner_set_status other than 'checks_passed'; that race's runner_set_note states the bound. NULL unless every meeting with rows on this card has scratching_data 'parsed' — a meeting with no sheet cannot be said to have nobody scratched."},"unscratched_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Unscratched Count","description":"runner_count minus scratched_count. A RECONCILIATION number against the sheet, NOT a field size: it still includes emergencies, which run only if promoted. A FLOOR wherever a race reports runner_set_status other than 'checks_passed'. NULL when scratched_count is."},"declared_scratched_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Declared Scratched Count","description":"Rows that are scratched AND not emergencies. Like scratched_count it counts only sheet rows MATCHED to an acceptance row held here, so it is a FLOOR wherever a race reports runner_set_status other than 'checks_passed'. NULL unless BOTH emergency_data and scratching_data are 'parsed' for every meeting with rows."},"declared_remaining_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Declared Remaining Count","description":"declared_count minus declared_scratched_count: the horses expected to jump barring an emergency promotion, which Racing Australia does not publish. It is the number to render as the field ONLY where every race behind it reports runner_set_status 'checks_passed'. Anywhere else it is a FLOOR — render it as 'at least N', never as an exact field: a runner Racing Australia lists that this card does not hold, and that is not on the scratching sheet, may well be jumping. Measured 2026-09-08: Townsville R5 is 'gap_detected' with saddlecloth #6 unaccounted for and published 11. The race's runner_set_note states the bound. NULL unless BOTH facts are known everywhere."},"as_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"As Of","description":"Newest RA scrape backing the meetings actually returned, UTC — computed after the venue filter."},"coverage_from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coverage From","description":"The first meeting day (Australia/Sydney) this store holds an acceptance card for, read from the tables on every call — never a constant. Every day from here up to three days ahead is served; an earlier `date` is a free 422 naming this same day. Until 2026-09-29 the reference tables were deleted three days after each meeting, so the store begins at the oldest day that survived that (2026-09-26) and grows by one day per day. Null only when the store could not be read, and then the older 3-day rule applies."},"completeness":{"type":"object","title":"Completeness","description":"What could and could not be verified about this card."},"meetings":{"items":{"type":"object"},"type":"array","title":"Meetings"},"note":{"type":"string","title":"Note"}},"type":"object","required":["date","date_status","meeting_count","race_count","runner_count","completeness","meetings","note"],"title":"AcceptancesOut","example":{"as_of":"2026-09-08T02:16:38.306760Z","completeness":{"counts_cover_every_published_meeting":true,"known_missing_runners":6,"meetings_acceptances_unpublished":0,"meetings_emergency_data_unknown":0,"meetings_scratching_data_unknown":0,"meetings_scratching_sheet_absent_for_whole_date":0,"meetings_withheld_acceptance_data_unknown":4,"races_checks_passed":24,"races_gap_detected":5,"races_unverified":0,"runners_without_emergency_data":0,"runners_without_scratching_data":0,"withheld_acceptance_slots":0,"withheld_acceptance_slots_unplaced":0,"withheld_acceptances":"unknown_date"},"coverage_from":"2026-09-05","date":"2026-09-08","date_status":"today","declared_count":334,"declared_remaining_count":272,"declared_scratched_count":62,"emergency_count":38,"meeting_count":4,"meetings":[{"acceptances_published":true,"as_of":"2026-09-08T02:16:38.306760Z","declared_count":89,"declared_remaining_count":59,"declared_scratched_count":30,"emergency_count":18,"emergency_data":"parsed","race_count":7,"races":[{"conditions":"Maiden, Set Weights, Three-Years-Old and Upwards, Apprentices can claim.","declared_count":12,"declared_remaining_count":6,"declared_scratched_count":6,"distance_m":1108,"emergency_count":5,"field_limit":"12 + 6 EM","missing_runners":[],"prize_breakdown":{"1st":17600,"2nd":5760,"3rd":2880},"prize_total":32000,"race_class":"Maiden","race_name":"Moe Denture Clinic Maiden Plate","race_number":1,"runner_count":17,"runner_set_status":"checks_passed","runners":[{"age":5,"barrier":14,"breeder":"Mr A Harvey","colour":"bay","dam":"Casino Rose","emergency":false,"horse_ref":"pe:backupbarbie","jockey":"Teo Nugent","last10":"2x3x4","number":2,"owner":"S S Racing, E Ralph, S Trovatello, A Wilson, C Taranto, A D Hore-Lacy, J Savage, A Williams, W Williams & V Wilson","raceentry_code":"MzE2ODU1MTEwOTY","runner_key":"backupbarbie","runner_name":"Back Up Barbie","runner_ref":"ra:MTA3MjIxMjU2MzI","scratched":true,"scratching":{"emergency":false,"late":false,"scratched_at":"2026-09-07T21:29:00Z"},"sex":"mare","sire":"Toronado (IRE)","trainer":"Ciaron Maher","trainer_code":"NDU4MDA0NzI","weight_kg":57.5}],"scratched_count":6,"start_time_local":"2:00PM","track_name":"Main","track_type":"Turf","unscratched_count":11}],"runner_count":107,"runner_set_status":"checks_passed","scratched_count":34,"scratching_data":"parsed","scratching_sheet_as_of":"2026-09-08T02:16:38.306760Z","track":{"dual_track":"N","going":"Heavy","going_rating":9,"penetrometer":"5.96","rail":"Out 3m Entire Circuit","track_condition":"Heavy 9","track_info":"Inspection 5:30AM 8/9;","track_type":"Turf","weather":"Fine"},"unscratched_count":73,"venue":"Moe","venue_id":"moe","venue_norm":"moe","venue_site":"moe","withheld_acceptance_data":"unknown","withheld_acceptance_slots_unplaced":0}],"note":"…","race_count":29,"runner_count":372,"scratched_count":72,"unscratched_count":300}},"AccountEventOut":{"properties":{"id":{"type":"integer","title":"Id"},"at":{"type":"string","title":"At","description":"When the change was written, ISO 8601 UTC."},"api_key_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Id","description":"The key the change applied to: this key, or one of this account's team keys."},"actor":{"type":"string","title":"Actor","description":"Who made it: stripe, console, admin, cron, internal_provision or system."},"action":{"type":"string","title":"Action","description":"What changed, e.g. plan_change, credits_override, key_rotate, key_mint, team_key_mint, team_key_revoke, overage_toggle, provision."},"source":{"type":"string","title":"Source","description":"The route, billing event or scheduled job that made the change."},"before":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Before","description":"The account's state before the change (plan, active, credit fields, label, IP rule count, billing ids, overage switch)."},"after":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"After","description":"The same fields after it."},"request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Id","description":"The X-Request-Id of the API call, or the billing event id, that made the change."},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"type":"object","required":["id","at","actor","action","source"],"title":"AccountEventOut"},"AccountEventsOut":{"properties":{"available":{"type":"boolean","title":"Available","description":"false while the change history is not yet being kept for this deployment; `events` is then empty."},"count":{"type":"integer","title":"Count"},"events":{"items":{"$ref":"#/components/schemas/AccountEventOut"},"type":"array","title":"Events"}},"type":"object","required":["available","count","events"],"title":"AccountEventsOut","example":{"available":true,"count":2,"events":[{"action":"plan_change","actor":"stripe","after":{"active":true,"credits_used":18210,"ip_whitelist":0,"overage_enabled":true,"plan":"pro_plus"},"api_key_id":"3f1c9a52-7d0e-4b8a-9c61-2e5f0d8b7a14","at":"2026-10-13T04:12:09.512331+00:00","before":{"active":true,"credits_used":18210,"ip_whitelist":0,"overage_enabled":false,"plan":"plus"},"id":1042,"request_id":"evt_1Q2w3E4r5T6y7U8i9O0p","source":"stripe:customer.subscription.updated"},{"action":"key_rotate","actor":"console","api_key_id":"3f1c9a52-7d0e-4b8a-9c61-2e5f0d8b7a14","at":"2026-10-02T22:40:51.004912+00:00","id":977,"note":"key_version 2","request_id":"6b0d8f3e-21c4-4a9e-b7d2-5e8c1a0f9b33","source":"POST /v1/keys/rotate"}]}},"ArchiveCoverageOut":{"properties":{"archive_from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Archive From"},"archive_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Archive To"},"rows":{"type":"integer","title":"Rows"},"races":{"type":"integer","title":"Races"},"bookmakers":{"type":"integer","title":"Bookmakers"},"is_closing_line_pct":{"type":"number","title":"Is Closing Line Pct"},"open_is_baseline_pct":{"type":"number","title":"Open Is Baseline Pct"},"resulted_races_pct":{"type":"number","title":"Resulted Races Pct"},"results_from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Results From"},"settleable_races":{"type":"integer","title":"Settleable Races"},"settleable_resulted_pct":{"type":"number","title":"Settleable Resulted Pct"},"results_scope":{"type":"string","title":"Results Scope"},"venue_split_suspect_rows":{"type":"integer","title":"Venue Split Suspect Rows"},"name_fragment_suspect_rows":{"type":"integer","title":"Name Fragment Suspect Rows"},"scratched_rows":{"type":"integer","title":"Scratched Rows","description":"Rows of runners the result marked scratched (flag from 2026-09-28; hidden from /closing-lines by default).","default":0},"consensus_close_pct":{"type":"number","title":"Consensus Close Pct","description":"Of the rows that are genuine closing lines (is_closing_line, not scratched), the share carrying consensus_close_prob — the market's margin-free probability stored at the close (columns from 2026-10-05, backfilled to the archive floor).","default":0.0},"finish_position_pct":{"type":"number","title":"Finish Position Pct","description":"Of the rows whose race has a result, excluding runners the result marked scratched, the share carrying a finish_position. Measured, not promised: AU thoroughbreds and greyhounds carry every finisher's position (from 2026-09-14), AU harness and NZ the full field from 2026-10-04 and the placegetters only before it.","default":0.0},"by_category":{"items":{"type":"object"},"type":"array","title":"By Category"},"window_days":{"type":"integer","title":"Window Days","description":"Days the by_bookmaker rates and the two result-join gaps are measured over, ending now.","default":30},"window_from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Window From","description":"Start of that window, UTC."},"by_bookmaker":{"items":{"type":"object"},"type":"array","title":"By Bookmaker","description":"One row per bookmaker over the window: {bookmaker_key, rows, closing_pct, baseline_pct}. closing_pct is the share of that book's rows with is_closing_line; baseline_pct the share with open_is_baseline. Books differ widely, so filter on the flags rather than trusting the aggregate.","default":[]},"settleable_races_in_window":{"type":"integer","title":"Settleable Races In Window","description":"AU and NZ archive races in the window that started at least 3 hours ago (the denominator of races_without_result).","default":0},"races_without_result":{"type":"integer","title":"Races Without Result","description":"Of settleable_races_in_window, races with no result row at all.","default":0},"results_in_window":{"type":"integer","title":"Results In Window","description":"AU and NZ results in the window that started at least 3 hours ago (the denominator of results_without_race).","default":0},"results_without_race":{"type":"integer","title":"Results Without Race","description":"Of results_in_window, results with no archive race (no closing line at any served book).","default":0},"definitions":{"additionalProperties":{"type":"string"},"type":"object","title":"Definitions","description":"One line per gap and rate above, saying exactly what is counted.","default":{}}},"type":"object","required":["archive_from","archive_to","rows","races","bookmakers","is_closing_line_pct","open_is_baseline_pct","resulted_races_pct","results_from","settleable_races","settleable_resulted_pct","results_scope","venue_split_suspect_rows","name_fragment_suspect_rows","by_category"],"title":"ArchiveCoverageOut"},"BackfillStatusOut":{"properties":{"job_id":{"type":"string","title":"Job Id"},"submitted_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Submitted At"},"total":{"type":"integer","title":"Total"},"done":{"type":"integer","title":"Done"},"queued":{"type":"integer","title":"Queued"},"failed":{"type":"integer","title":"Failed"},"credits_charged":{"type":"integer","title":"Credits Charged","description":"3 per horse delivered so far."},"credits_blocked":{"type":"boolean","title":"Credits Blocked","description":"True when your remaining credits are below the cost of one horse, which pauses collection until they reset or you upgrade."},"queue_position":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Queue Position","description":"Horses ahead of this job's oldest waiting row, across all jobs."},"estimated_ready":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Estimated Ready"},"ready_to_collect":{"type":"integer","title":"Ready To Collect","description":"Completed rows you have not yet read."},"upstream":{"allOf":[{"$ref":"#/components/schemas/UpstreamBlock"}],"description":"Whether Racing Australia is answering form reads at all. Added 2026-09-21 and purely additive: nothing else on this response, and nothing about billing or the queue, changed with it."}},"type":"object","required":["job_id","total","done","queued","failed","credits_charged","credits_blocked","ready_to_collect","upstream"],"title":"BackfillStatusOut"},"BackfillSubmitIn":{"properties":{"horses":{"items":{"type":"string"},"type":"array","title":"Horses","description":"Horse names, `pe:` horse_refs or `ra:` entry codes, in any mix. Max 500 per call. A name and its horse_ref resolve identically, against acceptances and archived results; an `ra:` code pins one meeting's entry."},"label":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Label","description":"Your own tag for this batch, echoed back on status."}},"type":"object","required":["horses"],"title":"BackfillSubmitIn"},"BackfillSubmitOut":{"properties":{"job_id":{"type":"string","title":"Job Id"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"queued":{"type":"integer","title":"Queued","description":"Horses accepted and waiting on the worker."},"already_stored":{"type":"integer","title":"Already Stored","description":"Horses we already hold a fresh copy of — matched on the HORSE, so a page collected under any of its Racing Australia codes counts. Collectable immediately, still billed at 3 credits each."},"duplicates":{"type":"integer","title":"Duplicates","description":"Already queued for you; not queued twice."},"not_found":{"items":{"type":"string"},"type":"array","title":"Not Found","description":"Names we have never seen on our own feeds.","default":[]},"ambiguous":{"items":{"type":"object"},"type":"array","title":"Ambiguous","description":"Names matching several horses. Re-submit with the runner_ref you meant.","default":[]},"estimated_ready":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Estimated Ready","description":"UTC estimate for the last horse, from the worker's measured pace."}},"type":"object","required":["job_id","queued","already_stored","duplicates"],"title":"BackfillSubmitOut"},"BestOddsEventOut":{"properties":{"id":{"type":"string","title":"Id"},"sport_key":{"type":"string","title":"Sport Key"},"home_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Team"},"away_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Away Team"},"commence_time":{"type":"string","title":"Commence Time"},"selections":{"items":{"type":"object"},"type":"array","title":"Selections"},"arb_exists":{"type":"boolean","title":"Arb Exists"},"arb_profit_pct":{"type":"number","title":"Arb Profit Pct"},"data_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Data Age Seconds"},"freshest_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Freshest Age Seconds"},"stale":{"type":"boolean","title":"Stale","default":false},"stale_bookmakers":{"items":{"type":"string"},"type":"array","title":"Stale Bookmakers","default":[]}},"type":"object","required":["id","sport_key","home_team","away_team","commence_time","selections","arb_exists","arb_profit_pct"],"title":"BestOddsEventOut","example":{"arb_exists":false,"arb_profit_pct":0.0,"away_team":"Storm","commence_time":"2026-06-07T09:30:00Z","home_team":"Broncos","id":"event_123","selections":[{"best_bookmaker":"sportsbet","best_price":2.1,"name":"Broncos"}],"sport_key":"nrl"}},"BestPricesOut":{"properties":{"id":{"type":"string","title":"Id"},"sport_key":{"type":"string","title":"Sport Key"},"home_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Team"},"away_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Away Team"},"commence_time":{"type":"string","title":"Commence Time"},"selections":{"items":{"type":"object"},"type":"array","title":"Selections"}},"type":"object","required":["id","sport_key","home_team","away_team","commence_time","selections"],"title":"BestPricesOut","example":{"away_team":"Storm","commence_time":"2026-06-07T09:30:00Z","home_team":"Broncos","id":"event_123","selections":[{"best_price":2.1,"name":"Broncos","price_spread":0.12}],"sport_key":"nrl"}},"BulkCatalogueOut":{"properties":{"datasets":{"items":{"$ref":"#/components/schemas/BulkDatasetOut"},"type":"array","title":"Datasets"},"formats":{"items":{"type":"string"},"type":"array","title":"Formats","default":["parquet","csv"]},"day_plans":{"type":"string","title":"Day Plans"},"month_plans":{"type":"string","title":"Month Plans"},"credits":{"additionalProperties":{"type":"integer"},"type":"object","title":"Credits"},"schedule":{"type":"string","title":"Schedule"},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","default":[]}},"type":"object","required":["datasets","day_plans","month_plans","credits","schedule"],"title":"BulkCatalogueOut"},"BulkDatasetOut":{"properties":{"name":{"type":"string","title":"Name"},"title":{"type":"string","title":"Title"},"description":{"type":"string","title":"Description"},"grain":{"type":"string","title":"Grain"},"schema_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Schema Version","description":"Bumped whenever the dataset's columns change. Compare it with the value your loader was written against."},"dataset_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dataset Version","description":"Date of the last change to the rules that decide this dataset's rows or values. Files generated before that date were made under the earlier rules."},"source_notes":{"items":{"type":"string"},"type":"array","title":"Source Notes","description":"What is withheld, quarantined or flagged in this dataset, and why.","default":[]},"columns":{"items":{"type":"string"},"type":"array","title":"Columns","description":"Column order of every file in this dataset, read from the newest file's sidecar.","default":[]},"available_from":{"type":"string","title":"Available From"},"days":{"items":{"$ref":"#/components/schemas/BulkFileOut"},"type":"array","title":"Days","description":"Daily files, oldest first. Business and above.","default":[]},"months":{"items":{"$ref":"#/components/schemas/BulkFileOut"},"type":"array","title":"Months","description":"Monthly bundles, oldest first. Platform and above.","default":[]}},"type":"object","required":["name","title","description","grain","available_from"],"title":"BulkDatasetOut"},"BulkFileOut":{"properties":{"period":{"type":"string","title":"Period"},"kind":{"type":"string","title":"Kind","description":"day (YYYY-MM-DD, Sydney meeting date) or month (YYYY-MM)."},"rows":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rows"},"generated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated At"},"bytes":{"additionalProperties":{"type":"integer"},"type":"object","title":"Bytes","description":"Size per format: parquet, csv (gzip).","default":{}},"sha256":{"additionalProperties":{"type":"string"},"type":"object","title":"Sha256","description":"Digest per format; also the ETag.","default":{}},"url":{"type":"string","title":"Url"},"schema_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Schema Version","description":"The column-set version this file was written under. Null on files written before versioning began (2026-10-13)."},"dataset_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dataset Version","description":"The rules date this file was written under (see the dataset's dataset_version)."},"restatements":{"type":"integer","title":"Restatements","description":"How many times this file has changed after it was first published; /v1/bulk/restatements lists each one with its reason.","default":0}},"type":"object","required":["period","kind","url"],"title":"BulkFileOut"},"ChangedRaceOut":{"properties":{"race_id":{"type":"string","title":"Race Id","description":"Stable race identifier — the same id /v1/racing/next-to-go, /v1/racing/price-history and /v1/racing/results use."},"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue"},"race_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Race Number"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"horse, greyhound or harness."},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country","description":"ISO country code, or null while the meeting is unresolved."},"start_time":{"type":"string","title":"Start Time","description":"Advertised start, UTC. It is revised as the meeting firms, and a revision bumps this race into the feed."},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Set when the race row is created and revised exactly once, if at all: to 'abandoned' when Racing Australia posts an official abandoned, postponed or transferred notice for the meeting — that revision bumps every runner of the race into this feed (2026-09-06). Otherwise it reads 'open' for every race here. It is not a live market-state signal — do not poll this endpoint for it."},"market_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Status","description":"Betting-market state (OPEN, SUSPENDED, CLOSED). Reported by PointsBet, which sends CLOSED when it shuts its book at the jump, and by the Betfair Exchange, which is withheld from customer responses pending a data licence — so a value last written by the exchange reads null for customers. Same shape as /v1/racing/events."},"inplay":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Inplay","description":"True once the market is in-play. Exchange-only — bookmakers do not run racing in-play — so it carries the same licence hold as `market_status` and reads null for customers."},"market_closed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Closed At","description":"UTC time the market was FIRST seen shut. Poll-observed and set once — see /v1/racing/events for the full semantics. Served when a BOOKMAKER reported the transition; null for customers when the exchange was the only feed that saw it. Null never means the market stayed open."},"inplay_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Inplay At","description":"UTC time the race was FIRST seen in-play — the actual off, as opposed to `start_time`, which is advertised and moves. Exchange-only, so null for customer plans."},"market_state_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market State Source","description":"Which feed's market state produced `market_closed_at` / `inplay_at` — 'pointsbetau' or 'betfair_ex_au'. Set once, with the first stamp, and null whenever those stamps are withheld."},"scratchings":{"items":{"type":"object"},"type":"array","title":"Scratchings","description":"The COMPLETE current scratchings for this race, merged across every book that reports one — not a delta, so a runner leaving the list is visible as an absence. Greyhound cards also carry box-vacancy placeholders here (a literal '....' or 'Vacant Box'), which name an empty box rather than a withdrawn runner; match on `number` and treat a name from that set as a vacant box."},"track_condition_changed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track Condition Changed At","description":"UTC instant of the most recent logged track-condition, weather or rail transition for this race's meeting (2026-09-05). Null when nothing has changed since we began watching it. Read it with `track_condition`: the value says WHAT, this says WHEN."},"track_condition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track Condition","description":"The race's CURRENT track condition, not a change log — a poller diffing against what it holds sees a condition change the same way it sees a scratching. Null where no source reports it."},"weather":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Weather","description":"Current weather for the meeting, same read-as-current rule as track_condition."},"changed_runners":{"items":{"$ref":"#/components/schemas/ChangedRunnerOut"},"type":"array","title":"Changed Runners","description":"Runner prices that moved inside the window. An empty list means this race arrived on the race-level arm alone — expected, and explained in the endpoint description."}},"type":"object","required":["race_id","start_time"],"title":"ChangedRaceOut"},"ChangedRunnerOut":{"properties":{"name":{"type":"string","title":"Name","description":"Runner name as the bookmaker publishes it."},"number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Number","description":"Saddlecloth / rug number, where the book supplies one."},"bookmaker_key":{"type":"string","title":"Bookmaker Key","description":"Bookmaker this price belongs to."},"win_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Win Price","description":"Current fixed win price at this bookmaker."},"place_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Place Price","description":"Current fixed place price. A place-only move does not advance `updated_at` — see the endpoint description."},"top2_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Top2 Price","description":"Current Top 2 (first-two) price at this bookmaker, where quoted."},"top3_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Top3 Price","description":"Current Top 3 price at this bookmaker, where quoted."},"top4_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Top4 Price","description":"Current Top 4 price at this bookmaker, where quoted."},"updated_at":{"type":"string","title":"Updated At","description":"UTC instant this runner's win or lay price last CHANGED at this bookmaker. Not the last poll: ingest carries this timestamp forward while the price is steady."},"betslip_win_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Betslip Win Url","description":"Opens this bookmaker's betslip with this runner's fixed WIN selected. Plus and above. Present only for the books with a runner-level route (unibet, tab, ladbrokes_au, neds) — see /v1/racing/next-to-go for the per-book table and the daily check behind it."},"betslip_place_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Betslip Place Url","description":"Same, fixed PLACE. Null when the book has a win link but is not quoting the place; on TAB it is the same one-shot URL as the win link."},"betslip_app_only":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Betslip App Only","description":"True for Ladbrokes and Neds: those URLs open the native app and redirect to the app-download page in a desktop browser."},"betslip_win_link":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Betslip Win Link","description":"Your own link for betslip_win_url, built through the link templates stored with PUT /v1/link-templates. Absent when your account stores none, or there is nothing to link."},"betslip_place_link":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Betslip Place Link","description":"Your own link for betslip_place_url, built the same way. Absent when there is nothing to link."}},"type":"object","required":["name","bookmaker_key","updated_at"],"title":"ChangedRunnerOut"},"ClosingLineRow":{"properties":{"race_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Id","description":"Race id; joins /v1/racing/results and /v1/racing/markets. Never null."},"start_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Time","description":"Advertised start, UTC, ISO 8601 with Z. Never null."},"meeting_date_aet":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meeting Date Aet","description":"Meeting date, YYYY-MM-DD, Australian Eastern calendar. Never null."},"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue","description":"Venue name as the books list it. Never null."},"race_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Race Number","description":"Race number at the meeting."},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"horse, harness or greyhound."},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country","description":"Two-letter country code."},"runner_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Key","description":"Normalised runner name; the runner's key within the race. Never null."},"runner_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Name","description":"Runner name as published. Never null."},"runner_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Runner Number","description":"Saddlecloth (box for greyhounds)."},"bookmaker_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bookmaker Key","description":"Bookmaker key. Never null."},"open_win_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Open Win Price","description":"Win price at the first observation of the series."},"open_secs_to_jump":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Open Secs To Jump","description":"Seconds to the jump at the first observation."},"open_is_baseline":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Open Is Baseline","description":"FLAG. True when the series began at the 60-minute window entry, so open_win_price is a market open; false when the book joined mid-window. Never null."},"close_win_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Close Win Price","description":"Win price at the last observation before the jump."},"close_lay_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Close Lay Price","description":"Exchange lay price at the close; null for customers (exchange rows are withheld)."},"close_secs_to_jump":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Close Secs To Jump","description":"Seconds to the jump at the last observation."},"is_closing_line":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Closing Line","description":"FLAG. True when the last observation was within 300 seconds of the jump: a genuine closing line. False rows carry a last-seen price. Never null."},"points_observed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Points Observed","description":"Price points in the series. Never null."},"finish_position":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Finish Position","description":"Finishing position where the result publishes one. Null does NOT mean the runner lost; read result_status."},"result_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Result Status","description":"The race's result status; null when the race has no result."},"venue_split_suspect":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Venue Split Suspect","description":"FLAG. The row may belong to a race the feed split across two venue spellings. Excluded by default. Never null."},"venue_split_peer_race_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue Split Peer Race Id","description":"FLAG companion. The other race_id of a suspected venue split."},"name_fragment_suspect":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Name Fragment Suspect","description":"FLAG. The runner name looks like a fragment of another runner's name. Excluded by default. Never null."},"venue_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue Id","description":"Canonical venue id from the venue registry."},"venue_site":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue Site","description":"Canonical venue site from the venue registry."},"runner_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Ref","description":"Registry id for the runner on race day (ra:, grv:, grsa:)."},"horse_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Horse Ref","description":"Horse identity across meetings (pe:<name>); thoroughbreds only."},"scratched":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Scratched","description":"FLAG. The result marked this runner scratched, yet a book kept pricing it. Excluded by default. On harness and NZ rows false means not known. Never null."},"open_place_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Open Place Price","description":"Place price at the first observation (from 2026-09-29)."},"close_place_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Close Place Price","description":"Place price at the last observation (from 2026-09-29)."},"scratched_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scratched At","description":"When the runner was scratched, UTC, as the board stamped it (from 2026-09-29)."},"open_top2_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Open Top2 Price","description":"Same Race Multi Top 2 price at the first observation."},"open_top3_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Open Top3 Price","description":"Same Race Multi Top 3 price at the first observation."},"open_top4_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Open Top4 Price","description":"Same Race Multi Top 4 price at the first observation."},"close_top2_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Close Top2 Price","description":"Same Race Multi Top 2 price at the last observation."},"close_top3_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Close Top3 Price","description":"Same Race Multi Top 3 price at the last observation."},"close_top4_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Close Top4 Price","description":"Same Race Multi Top 4 price at the last observation."},"consensus_close_prob":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Consensus Close Prob","description":"Market consensus probability at the close, margin removed."},"consensus_close_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Consensus Close Price","description":"The price consensus_close_prob corresponds to."},"books_in_consensus_close":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Books In Consensus Close","description":"Books the closing consensus rests on."},"consensus_open_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Consensus Open Price","description":"Consensus price at the 60-minute open."},"books_in_consensus_open":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Books In Consensus Open","description":"Books the opening consensus rests on."},"duplicate_suspect":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Duplicate Suspect","description":"FLAG. The row's race appears to duplicate another race record (the same race filed twice); duplicate_of_race_id names the other. The key is absent until the archive carries the flag."},"duplicate_of_race_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Duplicate Of Race Id","description":"FLAG companion. The race_id this row's race duplicates. Absent until the archive carries it."},"meeting_date_local":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meeting Date Local","description":"Meeting date in the venue's own time zone, YYYY-MM-DD. For GB and IE racing key on race_id or (venue, meeting_date_local, race_number). Absent until the archive carries it."}},"additionalProperties":true,"type":"object","title":"ClosingLineRow","description":"One archived series: a race, a runner and a bookmaker. Field order is the CSV column order.\n\nEvery field is nullable in the schema; the ones described as \"never null\" are always set on a\nserved row."},"ClosingLinesOut":{"properties":{"rows_returned":{"type":"integer","title":"Rows Returned"},"total_rows":{"type":"integer","title":"Total Rows","description":"Rows matching the filter before limit/offset"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"archive_from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Archive From","description":"Earliest start_time in the whole archive. The archive only grows; this is its true floor as of right now, not a promise."},"window_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Window Days","description":"Set when the caller's plan clamps the lookback. NULL means the full archive was searched."},"resulted_rows":{"type":"integer","title":"Resulted Rows","description":"Rows in the matching set that have a result_status. Published because result coverage is thin and growing (5.3% of archived races on 2026-08-18) — do not assume a NULL finish_position means the runner lost. It means no position was published for the runner: harness and NZ results named the placegetters only before 2026-10-04 and carry the full field from then, while AU thoroughbred and greyhound results position every finisher (from 2026-09-14)."},"rows":{"items":{"$ref":"#/components/schemas/ClosingLineRow"},"type":"array","title":"Rows"}},"type":"object","required":["rows_returned","total_rows","limit","offset","resulted_rows","rows"],"title":"ClosingLinesOut","example":{"archive_from":"2026-08-04T09:20:44Z","limit":500,"offset":0,"resulted_rows":640,"rows":[{"bookmaker_key":"sportsbet","books_in_consensus_close":12,"books_in_consensus_open":9,"category":"horse","close_place_price":2.3,"close_secs_to_jump":41,"close_top2_price":3.3,"close_top3_price":2.2,"close_top4_price":1.75,"close_win_price":7.0,"consensus_close_price":7.2,"consensus_close_prob":0.1388,"consensus_open_price":7.6,"country":"AU","finish_position":2,"horse_ref":"pe:aeolian","is_closing_line":true,"meeting_date_aet":"2026-08-15","name_fragment_suspect":false,"open_is_baseline":true,"open_place_price":2.2,"open_secs_to_jump":3595,"open_top2_price":3.1,"open_top3_price":2.1,"open_top4_price":1.7,"open_win_price":6.5,"points_observed":10,"race_id":"race_123","race_number":2,"result_status":"final","runner_key":"aeolian","runner_name":"Aeolian","runner_number":4,"runner_ref":"ra:2098765","scratched":false,"start_time":"2026-08-15T03:00:00Z","venue":"Caulfield","venue_id":"caulfield","venue_site":"caulfield","venue_split_suspect":false}],"rows_returned":1,"total_rows":12480}},"ClvBetIn":{"properties":{"ref":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Ref","description":"Your own id for this bet, echoed back unchanged."},"race_id":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Race Id","description":"The race_id from /v1/racing/next-to-go, /events, /results or the archive. The most reliable way to name a race; use venue + meeting_date + race_number only when you do not hold it."},"venue":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Venue","description":"Venue name as our feeds spell it (case-insensitive). Needs meeting_date and race_number."},"race_number":{"anyOf":[{"type":"integer","maximum":20.0,"minimum":1.0},{"type":"null"}],"title":"Race Number"},"meeting_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Meeting Date","description":"AET meeting date, YYYY-MM-DD."},"runner_ref":{"anyOf":[{"type":"string","maxLength":60},{"type":"null"}],"title":"Runner Ref","description":"Registry id as served on /results and the archive — ra:<horsecode> or grv:<dogId>. Exact match; the most reliable way to name a runner."},"runner_number":{"anyOf":[{"type":"integer","maximum":30.0,"minimum":1.0},{"type":"null"}],"title":"Runner Number","description":"Saddlecloth number."},"runner_name":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Runner Name","description":"Runner name; matched the same way the feeds match names (case, punctuation and country suffixes ignored)."},"bookmaker":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}],"title":"Bookmaker","description":"bookmaker_key the bet was struck with. When given, `clv_pct` is measured against THAT book's closing price and `close_book` is filled; when absent, against the market reference chosen by `reference`."},"price":{"type":"number","maximum":1000.0,"exclusiveMinimum":1.0,"title":"Price","description":"Decimal odds you took."},"stake":{"type":"number","maximum":1000000.0,"exclusiveMinimum":0.0,"title":"Stake","description":"Stake for the P&L line. Defaults to one flat unit.","default":1.0}},"type":"object","required":["price"],"title":"ClvBetIn"},"ClvBetOut":{"properties":{"ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ref"},"resolved":{"type":"boolean","title":"Resolved","description":"True when the race AND the runner were found in the archive. False rows carry `reason` and no numbers."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"race_not_found · race_ambiguous · runner_not_found · runner_ambiguous · bookmaker_withheld · no_closing_line · no_closing_line_for_book."},"race_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Id"},"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue"},"race_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Race Number"},"runner_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Key"},"runner_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Name"},"runner_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Runner Number"},"runner_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Ref"},"bookmaker":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bookmaker"},"price_taken":{"type":"number","title":"Price Taken"},"stake":{"type":"number","title":"Stake"},"close_book":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Close Book","description":"The named bookmaker's closing win price, when it recorded a closing line."},"close_best":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Close Best","description":"Highest closing win price across books."},"close_median":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Close Median","description":"Median closing win price across books."},"books_at_close":{"type":"integer","title":"Books At Close","description":"Books with a closing line for this runner.","default":0},"close_reference":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Close Reference","description":"The close `clv_pct` is measured against: close_book when a bookmaker was named and priced the close, else the market reference."},"clv_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Clv Pct","description":"(price_taken / close_reference − 1) × 100. Positive means you beat the close."},"beat_close":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Beat Close"},"finish_position":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Finish Position"},"result_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Result Status","description":"final · interim · null (not yet resulted)."},"won":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Won","description":"On a final result: true for the winner, false for every other runner — including one with no published position, since results list placegetters only. Null until final, and null when the result records no winner."},"pnl":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pnl","description":"stake × (price − 1) on a win, −stake on a loss, null until the result is final."}},"type":"object","required":["resolved","price_taken","stake"],"title":"ClvBetOut"},"ClvIn":{"properties":{"bets":{"items":{"$ref":"#/components/schemas/ClvBetIn"},"type":"array","maxItems":200,"minItems":1,"title":"Bets","description":"Up to 200 bets per request."},"reference":{"type":"string","pattern":"^(best|median)$","title":"Reference","description":"The market close used when a bet names no bookmaker, and reported as close_best / close_median on every bet regardless: `best` is the highest closing win price across books, `median` the median.","default":"best"}},"type":"object","required":["bets"],"title":"ClvIn","examples":[{"bets":[{"bookmaker":"sportsbet","price":4.2,"race_id":"au-flemington-2026-09-19-r5","ref":"bet-1","runner_name":"Example Horse","stake":50},{"meeting_date":"2026-09-19","price":6.0,"race_number":7,"ref":"bet-2","runner_number":3,"venue":"Randwick"}],"reference":"best"}]},"ClvOut":{"properties":{"reference":{"type":"string","title":"Reference"},"archive_floor":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Archive Floor","description":"Earliest race day the archive holds; bets before it cannot resolve."},"summary":{"$ref":"#/components/schemas/ClvSummaryOut"},"bets":{"items":{"$ref":"#/components/schemas/ClvBetOut"},"type":"array","title":"Bets"},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","default":[]}},"type":"object","required":["reference","summary","bets"],"title":"ClvOut"},"ClvSummaryOut":{"properties":{"bets":{"type":"integer","title":"Bets"},"resolved":{"type":"integer","title":"Resolved"},"unresolved":{"type":"integer","title":"Unresolved"},"scored":{"type":"integer","title":"Scored","description":"Bets with a closing line to measure against."},"avg_clv_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Clv Pct"},"median_clv_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Median Clv Pct"},"beat_close":{"type":"integer","title":"Beat Close","default":0},"beat_close_rate_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Beat Close Rate Pct"},"resulted":{"type":"integer","title":"Resulted","description":"Scored-or-resolved bets with a final result.","default":0},"wins":{"type":"integer","title":"Wins","default":0},"win_rate_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Win Rate Pct"},"staked":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Staked"},"pnl":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pnl"},"roi_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Roi Pct"}},"type":"object","required":["bets","resolved","unresolved","scored"],"title":"ClvSummaryOut"},"ConnectorHealthOut":{"properties":{"connector":{"type":"string","title":"Connector"},"last_ok":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Ok"},"last_poll":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Poll"},"status":{"type":"string","title":"Status"},"records_written":{"type":"integer","title":"Records Written"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["connector","last_ok","status","records_written"],"title":"ConnectorHealthOut","example":{"connector":"sportsbet","last_ok":"2026-06-07T02:20:00","records_written":128,"status":"ok"}},"ConsensusBookOut":{"properties":{"bookmaker":{"type":"string","title":"Bookmaker"},"runners_priced":{"type":"integer","title":"Runners Priced"},"overround_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Overround Pct","description":"Σ 1/price × 100 over the runners this book priced; null below two."},"age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Age Seconds"},"last_update":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Update"},"refresh_tier":{"type":"string","title":"Refresh Tier","default":"live"},"stale":{"type":"boolean","title":"Stale","default":false},"included":{"type":"boolean","title":"Included","description":"Whether this book entered the consensus."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Why not: stale · partial_field · single_runner."}},"type":"object","required":["bookmaker","runners_priced","included"],"title":"ConsensusBookOut"},"ConsensusExclusionOut":{"properties":{"bookmaker":{"type":"string","title":"Bookmaker"},"reason":{"type":"string","title":"Reason"},"age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Age Seconds"},"runners_priced":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Runners Priced"}},"type":"object","required":["bookmaker","reason"],"title":"ConsensusExclusionOut"},"ConsensusMarketOut":{"properties":{"median_overround_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Median Overround Pct"},"best_price_market_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Best Price Market Pct","description":"Σ 1/best_price × 100 over the runners with a consensus. Under 100 means the best prices across the included books beat the field."},"median_runner_spread_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Median Runner Spread Pct"},"consensus_sum_before_normalisation":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Consensus Sum Before Normalisation","description":"The sum of the per-runner medians before the field was scaled to one. Close to 1.0 when the books agree on the field; printed so the scaling is visible rather than silent."},"field_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Field Size","description":"Runners a complete-field book prices; books quoting fewer are excluded as partial_field."}},"type":"object","title":"ConsensusMarketOut"},"ConsensusMethodOut":{"properties":{"normalisation":{"type":"string","title":"Normalisation","description":"power (the default) or multiplicative.","default":"power"},"aggregation":{"type":"string","title":"Aggregation","default":"median_probability"},"renormalised":{"type":"boolean","title":"Renormalised","default":true},"stale_rule":{"type":"string","title":"Stale Rule","default":"refresh_tier"},"field_rule":{"type":"string","title":"Field Rule","default":"complete_field"},"min_books":{"type":"integer","title":"Min Books","default":2},"include_stale":{"type":"boolean","title":"Include Stale","default":false}},"type":"object","title":"ConsensusMethodOut"},"ConsensusOut":{"properties":{"race":{"$ref":"#/components/schemas/ConsensusRaceOut"},"method":{"$ref":"#/components/schemas/ConsensusMethodOut"},"books_quoting":{"type":"integer","title":"Books Quoting"},"books_in_consensus":{"type":"integer","title":"Books In Consensus"},"books_excluded":{"items":{"$ref":"#/components/schemas/ConsensusExclusionOut"},"type":"array","title":"Books Excluded","default":[]},"bookmakers":{"items":{"$ref":"#/components/schemas/ConsensusBookOut"},"type":"array","title":"Bookmakers","default":[]},"market":{"$ref":"#/components/schemas/ConsensusMarketOut"},"runners":{"items":{"$ref":"#/components/schemas/ConsensusRunnerOut"},"type":"array","title":"Runners","default":[]},"excluded_quotes":{"items":{"type":"object"},"type":"array","title":"Excluded Quotes","description":"Placeholder prices left out of the consensus quote by quote, on the rule /v1/racing/best-odds uses: a long price at least four times the median of three or more other books on the same runner.","default":[]},"scratchings":{"items":{"type":"object"},"type":"array","title":"Scratchings","default":[]},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"type":"object","required":["race","method","books_quoting","books_in_consensus","market"],"title":"ConsensusOut"},"ConsensusQuoteOut":{"properties":{"bookmaker":{"type":"string","title":"Bookmaker"},"price":{"type":"number","title":"Price"},"no_vig_prob":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"No Vig Prob","description":"This book's probability for the runner after its own margin is removed by the stated method."},"age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Age Seconds"}},"type":"object","required":["bookmaker","price"],"title":"ConsensusQuoteOut"},"ConsensusRaceOut":{"properties":{"race_id":{"type":"string","title":"Race Id"},"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue"},"race_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Race Number"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"start_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Time"},"secs_to_jump":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Secs To Jump","description":"Negative once the race has jumped."},"refresh_tier":{"type":"string","title":"Refresh Tier","description":"The race's own refresh rule — live inside three hours of the jump, card beyond it. Each book's tier is on its own entry.","default":"live"},"status":{"type":"string","title":"Status","description":"upcoming · jumped.","default":"upcoming"},"as_of":{"type":"string","title":"As Of"}},"type":"object","required":["race_id","as_of"],"title":"ConsensusRaceOut"},"ConsensusRunnerOut":{"properties":{"number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Number","description":"Saddlecloth (box for greyhounds), voted across the books."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"runner_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Ref","description":"Registry id where known: ra:<code> for a thoroughbred (Racing Australia's code, new each day), grv:/grsa: for a dog."},"horse_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Horse Ref","description":"pe:<name>, the stable thoroughbred identity. Null off AU thoroughbreds."},"scratched":{"type":"boolean","title":"Scratched","default":false},"books":{"type":"integer","title":"Books","description":"Included books quoting this runner.","default":0},"consensus_prob":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Consensus Prob","description":"The market's implied probability for this runner after margin is removed from every included book and the books are combined by median, renormalised so the field sums to one. Null when fewer than `min_books` included books quote the runner. It describes the market, not the outcome."},"consensus_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Consensus Price","description":"1 / consensus_prob."},"median_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Median Price"},"mean_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mean Price"},"best_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Best Price","description":"Highest price among the included books."},"best_bookmaker":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Best Bookmaker"},"worst_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Worst Price"},"price_spread_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Price Spread Pct","description":"best / worst - 1, in percent."},"best_vs_consensus_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Best Vs Consensus Pct","description":"best_price / consensus_price - 1, in percent. Negative is normal: every quoted price carries margin, so the best quote usually sits below the market's margin-free price (about -20 % on a 130 % greyhound market). It turns positive only when one book prices a runner above the panel's margin-free view. Not a value judgement."},"freshest_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Freshest Age Seconds"},"oldest_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Oldest Age Seconds"},"quotes":{"items":{"$ref":"#/components/schemas/ConsensusQuoteOut"},"type":"array","title":"Quotes","default":[]}},"type":"object","title":"ConsensusRunnerOut"},"ExoticsOut":{"properties":{"source":{"type":"string","title":"Source","default":"results"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"The result's own status: interim or final. An interim dividend can still move."},"dividends":{"items":{"type":"object"},"type":"array","title":"Dividends","default":[]}},"type":"object","title":"ExoticsOut"},"FieldFormOut":{"properties":{"race_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Id","description":"Our race_id. Null on an acceptance-only answer (`card_source: acceptances`): the race is on Racing Australia's card but no bookmaker has framed it yet, and every AU book frames a race 16 hours before its jump."},"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue"},"race_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Race Number"},"start_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Time","description":"Scheduled start, UTC. Null on an acceptance-only answer; see `start_time_local`."},"start_time_local":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Time Local","description":"Racing Australia's published start on the venue's own clock ('1:35PM'). Set on an acceptance-only answer."},"meeting_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meeting Date","description":"Sydney calendar date of the meeting; the London date for a British or Irish race."},"race_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Name"},"distance_m":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Distance M"},"track_condition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track Condition","description":"As the live card or the result carries it."},"race_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Status","description":"The live race's status while it is open; the result's (`interim`/`final`) once it has been run."},"card_source":{"type":"string","title":"Card Source","description":"`live` -- the field came from the live card; `results` -- the race has been run, its live rows are gone, and the field is the settled result's; `acceptances` -- no bookmaker has framed the race yet (or no live field is held), and the field is Racing Australia's acceptance card."},"acceptances_folded":{"type":"boolean","title":"Acceptances Folded","description":"True when Racing Australia's acceptance card for this race was found and folded in: every accepted runner (emergencies aside) is in `runners`, named as RA names it.","default":false},"form_as_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Form As At","description":"This race's start, UTC. Every run and every figure below is from BEFORE this instant, so a race already run answers with what was knowable at the jump and never with its own result. On an acceptance-only answer, Sydney midnight opening the meeting day."},"runs_per_runner":{"type":"integer","title":"Runs Per Runner","description":"The `runs` limit this answer was built with."},"runners_total":{"type":"integer","title":"Runners Total"},"runners_with_runs":{"type":"integer","title":"Runners With Runs","description":"Runners with at least one start held (`runs_total` of 1 or more). A runner whose only held rows are scratchings is not counted."},"runners":{"items":{"$ref":"#/components/schemas/FieldRunner"},"type":"array","title":"Runners"},"source":{"$ref":"#/components/schemas/RunsSourceBlock"}},"type":"object","required":["card_source","runs_per_runner","runners_total","runners_with_runs","runners","source"],"title":"FieldFormOut","example":{"acceptances_folded":true,"card_source":"live","distance_m":1300,"form_as_at":"2026-09-26T03:35:00Z","meeting_date":"2026-09-26","race_id":"1530bcdc-1cc9-4c97-816a-4423b663f788","race_name":"Mdn Plate","race_number":4,"race_status":"open","runners":[{"barrier":7,"held_rows_total":1,"horse_ref":"pe:pleasemizter","jockey":"Jason Li","last10":"1","last_start_date":"2026-09-12","name":"Please Mizter","number":3,"runner_ref":"ra:MjgwNjM0ODg2NDA","runs":[{"box":13,"date":"2026-09-20","dead_heat":false,"distance_m":1300,"field_size":12,"horse_ref":"pe:pleasemizter","jockey":"Jason Li","last_600m_s":34.61,"margin":"0.75L","number":14,"origin":"results","position":1,"prize_total":50000,"prize_won":27500.0,"race_class":"Maiden","race_name":"Mdn Plate","race_number":4,"race_time_s":77.12,"result_id":"res_9f1c…","runner_ref":"ra:MjgwNjM0ODg2NDA","sp":7.5,"start_time":"2026-09-20T03:35:00Z","status":"ran","time_s":77.12,"track_condition":"Good 4","trainer":"Peter Fernie","venue":"Rosehill","weight_kg":57.0}],"runs_total":1,"scratched":false,"stats":{"place_pct":100.0,"places":1,"starts":1,"win_pct":100.0,"wins":1},"trainer":"Peter Fernie","weight_kg":57.0}],"runners_total":1,"runners_with_runs":1,"runs_per_runner":10,"source":{"collectors":["racing_australia","tabtouch"],"coverage_from":"2026-09-01","coverage_to":"2026-09-20","feed":"puntersedge","refreshed_hourly":true,"region":"AU","sport":"thoroughbred"},"start_time":"2026-09-26T03:35:00Z","track_condition":"Good 4","venue":"Rosehill"}},"FieldRunner":{"properties":{"number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Number","description":"Saddlecloth number on this race's card."},"name":{"type":"string","title":"Name","description":"The horse's name as the card (or the result) spells it."},"horse_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Horse Ref","description":"`pe:<name>` -- the identity the runs were gathered on, the same value every thoroughbred surface publishes. Pass it to /v1/racing/horses/runs or /v1/racing/horses/form for the one-horse view."},"runner_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Ref","description":"The Racing Australia code this runner carries on the live card or the result -- on race day the join to /v1/racing/next-to-go, /v1/racing/acceptances and /v1/racing/results. Not a horse identity: Racing Australia issues a new code for the same horse each day. Null when the card did not carry one."},"barrier":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Barrier","description":"Barrier drawn for THIS race, as the card publishes it (the stall draw on a British or Irish card)."},"jockey":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jockey","description":"Rider declared for THIS race."},"trainer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Trainer"},"weight_kg":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Weight Kg","description":"Weight allotted for THIS race, as the card publishes it; a British or Irish card's stones and pounds converted to kg, one decimal."},"age":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Age","description":"The horse's age as the card prints it. British and Irish cards (William Hill, Betway) publish it; the Australian books do not, so it is null on an AU race."},"rating":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rating","description":"The rating the card prints: on a British or Irish race the Racing Post rating as William Hill's card carries it. Null when no card we read publishes one (every AU race)."},"scratched":{"type":"boolean","title":"Scratched","description":"True when the card has scratched the horse. It stays in the field so the runners match what the acceptances published.","default":false},"last10":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last10","description":"Racing Australia's last-ten figures for the horse, as its acceptance card prints them; on a British or Irish race the form figures the bookmaker's card prints (William Hill, Betway). Null when no card lists this runner, or lists no figures."},"last_start_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Start Date","description":"The horse's last start as Racing Australia's acceptance card gives it, YYYY-MM-DD, including starts older than `source.coverage_from`: a runner with `runs_total` 0 and a last start before that floor has history we do not hold (read it with /v1/racing/horses/form). Null with `last10` null: the card shows no previous start."},"runs_total":{"type":"integer","title":"Runs Total","description":"STARTS we hold for this horse before this race's start: runs with `status` `ran`, the same count as `stats.starts`. A scratching is not a start. 0 is honest: no start held -- a first starter, or a horse whose starts predate `source.coverage_from` (on an Australian race `last_start_date` tells the two apart). Before 2026-10-13 this counted every held row, scratchings included, so a horse scratched before its debut read 1 or more: on 2026-10-12, 442 of the 31,163 horses in the store held scratchings and no start. That count is `held_rows_total`."},"held_rows_total":{"type":"integer","title":"Held Rows Total","description":"Every row we hold for this horse before this race's start: its starts plus its scratchings (rows whose `status` is not `ran`). `runs` is drawn from these rows, so a horse with `runs_total` 0 can still carry `runs` entries -- its scratchings. Measured on 2026-10-12, 61,719 of the store's 282,639 rows (21.8%) were scratchings."},"stats":{"allOf":[{"$ref":"#/components/schemas/HorseRunsStats"}],"description":"Over every held run before this race, not just the `runs` returned; starts only."},"runs":{"items":{"$ref":"#/components/schemas/HorseRunRow"},"type":"array","title":"Runs","description":"Newest first, at most `runs` of them, every one before this race's start. Scratchings are included and read `status: scratched`."}},"type":"object","required":["name","runs_total","held_rows_total","stats","runs"],"title":"FieldRunner"},"ForgotPasswordIn":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"ForgotPasswordIn"},"GreyhoundFieldOut":{"properties":{"race_id":{"type":"string","title":"Race Id"},"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue"},"race_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Race Number"},"start_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Time","description":"Scheduled start, UTC."},"meeting_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meeting Date","description":"Sydney calendar date of the meeting; the London date for a British or Irish race."},"race_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Name"},"distance_m":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Distance M"},"track_condition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track Condition"},"race_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Status","description":"The live race's status while it is open; the result's (`interim`/`final`) once it has been run."},"card_source":{"type":"string","title":"Card Source","description":"`live` -- the field came from the live card; `results` -- the race has been run, its live rows are gone, and the field is the settled result's."},"form_as_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Form As At","description":"This race's start, UTC. Every run and figure below is from BEFORE this instant, so a race already run answers with what was knowable at the jump, never its own result."},"runs_per_runner":{"type":"integer","title":"Runs Per Runner"},"runners_total":{"type":"integer","title":"Runners Total"},"runners_with_runs":{"type":"integer","title":"Runners With Runs","description":"Runners with at least one start held (`runs_total` of 1 or more). A runner whose only held rows are scratchings or unused reserves is not counted."},"runners":{"items":{"$ref":"#/components/schemas/GreyhoundFieldRunner"},"type":"array","title":"Runners"},"source":{"$ref":"#/components/schemas/GreyhoundFieldSource"}},"type":"object","required":["race_id","card_source","runs_per_runner","runners_total","runners_with_runs","runners","source"],"title":"GreyhoundFieldOut","example":{"card_source":"live","distance_m":516,"form_as_at":"2026-09-19T07:22:00Z","meeting_date":"2026-09-19","race_id":"9b4e2b2e-1d7a-4b6a-9f1c-0f3c2a6e7d10","race_name":"Dubbo Grade 5","race_number":2,"race_status":"open","runners":[{"box":2,"form":"1","held_rows_total":1,"name":"Tommy Blue","name_key":"tommyblue","number":2,"runs":[{"box":2,"date":"2026-09-19","dead_heat":false,"distance_m":516,"field_size":5,"first_split_s":6.71,"grade":"M","number":2,"origin":"results","position":1,"race_name":"Dubbo Maiden","race_number":1,"result_id":"res_9f1c…","runner_ref":"grsa:441535","second_split_s":18.02,"source":"grsa","sp":1.33,"start_time":"2026-09-19T07:02:00Z","status":"ran","time_s":30.14,"trainer":"Raymond Tyquin","venue":"Dubbo","weight_kg":29.8,"win_time_s":30.14}],"runs_total":1,"scratched":false,"stats":{"place_pct":100.0,"places":1,"starts":1,"win_pct":100.0,"wins":1},"trainer":"Raymond Tyquin"}],"runners_total":1,"runners_with_runs":1,"runs_per_runner":10,"source":{"collectors":["fasttrack","grsa"],"coverage_from":"2026-06-23","coverage_to":"2026-09-20","feed":"puntersedge","refreshed_hourly":true,"region":"AU","results_from":"2026-09-13","sport":"greyhound"},"start_time":"2026-09-19T07:22:00Z","venue":"Dubbo"}},"GreyhoundFieldRunner":{"properties":{"number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Number","description":"Rug number on this race's card."},"name":{"type":"string","title":"Name","description":"The dog's name as the card (or the result) spells it."},"name_key":{"type":"string","title":"Name Key","description":"The identity the runs were grouped on (letters and digits). Pass it, or the name, as `dog` to /v1/racing/greyhounds/form or /stats for the one-dog view."},"box":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Box","description":"Trap drawn for THIS race: the book's draw where it gives one, else the rug number (on a British or Irish card the two are the same number)."},"trainer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Trainer"},"form":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Form","description":"The card's form figures as the bookmaker prints them (William Hill and PointsBet on British and Irish cards; PointsBet on Australian ones). Null on a race answered from its result."},"scratched":{"type":"boolean","title":"Scratched","description":"True when the card has scratched the dog, or it is an unused reserve. It stays in the field.","default":false},"runner_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Ref","description":"Registry id (grv:/grsa:) carried by a settled Australian result; null on a live card and on every British or Irish race."},"runs_total":{"type":"integer","title":"Runs Total","description":"STARTS we hold for this dog BEFORE this race's start: runs with `status` `ran`, the same count as `stats.starts`. A scratching or an unused reserve is not a start. 0 is honest: a first starter, or a record older than `source.coverage_from`. Before 2026-10-13 this counted every held row; that count is `held_rows_total`."},"held_rows_total":{"type":"integer","title":"Held Rows Total","description":"Every row we hold for this dog before this race's start: its starts plus its scratchings and unused reserves (rows whose `status` is not `ran`). `runs` is drawn from these rows, so a dog with `runs_total` 0 can still carry `runs` entries. Measured 2026-10-13: 6,247 of the AU store's 103,553 rows were not starts."},"stats":{"allOf":[{"$ref":"#/components/schemas/GreyhoundFieldStats"}],"description":"Over every held run before this race; starts only."},"runs":{"items":{"$ref":"#/components/schemas/GreyhoundRun"},"type":"array","title":"Runs","description":"Newest first, at most `runs` of them, every one before this race's start. Scratchings and unused reserves are included and say so in `status`."}},"type":"object","required":["name","name_key","runs_total","held_rows_total","stats","runs"],"title":"GreyhoundFieldRunner"},"GreyhoundFieldSource":{"properties":{"feed":{"type":"string","title":"Feed","description":"Where the data comes from: our own collection of published AU greyhound results, Victorian tracks via FastTrack (runner_ref grv:) and every other state via the national results pages (runner_ref grsa:). Runs on or after `results_from` are races we settled ourselves; earlier runs are the same pages read back by our history collector. Refreshed hourly. On a British or Irish race (/v1/racing/greyhounds/field, from 2026-10-12) `region` reads GB-IE and the runs are our own GB/IE results from PointsBet's settled race card, read live.","default":"puntersedge"},"region":{"type":"string","title":"Region","default":"AU"},"sport":{"type":"string","title":"Sport","default":"greyhound"},"coverage_from":{"type":"string","title":"Coverage From","description":"Sydney date of the OLDEST run in the store. A dog's record here starts here.","default":"2026-09-13"},"results_from":{"type":"string","title":"Results From","description":"Sydney date our own settled results begin. A run on or after it has `origin: results` and a `result_id` that joins to /v1/racing/results; an earlier one has `origin: history` and does not.","default":"2026-09-13"},"coverage_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coverage To","description":"Sydney date of the latest race in the store."},"collectors":{"items":{"type":"string"},"type":"array","title":"Collectors","description":"Which of our collectors can contribute a run: `fasttrack` = Victorian tracks, `grsa` = every other state.","default":["fasttrack","grsa"]},"refreshed_hourly":{"type":"boolean","title":"Refreshed Hourly","description":"The store is rebuilt from our results at :58 each hour, after both collectors have written.","default":true},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Set on a British or Irish race: GB/IE positions are placings, so a `ran` run with a null position was not placed. Null on an Australian race."}},"type":"object","title":"GreyhoundFieldSource","description":"The field view's source block: the per-dog one plus `note`. A subclass, so the per-dog routes'\npublished shape does not change."},"GreyhoundFieldStats":{"properties":{"starts":{"type":"integer","title":"Starts"},"wins":{"type":"integer","title":"Wins"},"places":{"type":"integer","title":"Places","description":"Finishes in the first three, wins included."},"win_pct":{"type":"number","title":"Win Pct"},"place_pct":{"type":"number","title":"Place Pct"}},"type":"object","required":["starts","wins","places","win_pct","place_pct"],"title":"GreyhoundFieldStats"},"GreyhoundFormOut":{"properties":{"dog":{"type":"string","title":"Dog","description":"The dog's name as our results spell it."},"name_key":{"type":"string","title":"Name Key","description":"The identity the runs were grouped on (letters and digits)."},"runner_refs":{"items":{"type":"string"},"type":"array","title":"Runner Refs","description":"Every registry id seen for this name, across both collectors."},"runs_total":{"type":"integer","title":"Runs Total","description":"STARTS we hold for this dog since `source.coverage_from`: runs with `status` `ran`, the same count as /v1/racing/greyhounds/stats `overall.starts`. A scratching or an unused reserve is not a start. Before 2026-10-13 this counted every held row; that count is `held_rows_total`."},"held_rows_total":{"type":"integer","title":"Held Rows Total","description":"Every row we hold for this dog since `source.coverage_from`: its starts plus its scratchings and unused reserves (rows whose `status` is not `ran`). `runs` is the newest `limit` of these rows. Measured 2026-10-13: 6,247 of the store's 103,553 rows were not starts, and 102 of its 12,191 dogs held rows but no start."},"runs":{"items":{"$ref":"#/components/schemas/GreyhoundRun"},"type":"array","title":"Runs"},"source":{"$ref":"#/components/schemas/SourceBlock"}},"type":"object","required":["dog","name_key","runner_refs","runs_total","held_rows_total","runs","source"],"title":"GreyhoundFormOut","example":{"dog":"Tommy Blue","held_rows_total":1,"name_key":"tommyblue","runner_refs":["grsa:441535"],"runs":[{"box":2,"date":"2026-09-19","dead_heat":false,"distance_m":516,"field_size":5,"first_split_s":6.71,"grade":"M","number":2,"origin":"results","position":1,"race_name":"Dubbo Maiden","race_number":1,"result_id":"res_9f1c…","runner_ref":"grsa:441535","second_split_s":18.02,"source":"grsa","sp":1.33,"start_time":"2026-09-19T07:02:00Z","status":"ran","time_s":30.14,"trainer":"Raymond Tyquin","venue":"Dubbo","weight_kg":29.8,"win_time_s":30.14}],"runs_total":1,"source":{"collectors":["fasttrack","grsa"],"coverage_from":"2026-06-23","coverage_to":"2026-09-20","feed":"puntersedge","refreshed_hourly":true,"region":"AU","results_from":"2026-09-13","sport":"greyhound"}}},"GreyhoundRun":{"properties":{"date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Date","description":"Sydney calendar date of the race; the London date on a British or Irish run."},"start_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Time","description":"Scheduled start, UTC."},"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue"},"race_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Race Number"},"distance_m":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Distance M"},"race_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Name"},"track_condition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track Condition"},"box":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Box"},"number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Number","description":"Rug number; equals box for a starter."},"position":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Position","description":"Finishing place. Null when the dog did not run (see `status`) or did not complete the course. On a British or Irish run the positions are placings, so null with `status: ran` means not placed (see `source.note` on /v1/racing/greyhounds/field)."},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"`ran`, `scratched`, or `reserve` (a rug 9/10 reserve that never got a box)."},"margin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Margin","description":"Lengths behind the winner as the source printed it; the winner's own row carries the winning margin on Victorian tracks and null elsewhere."},"time_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Time S","description":"The dog's own finish time in seconds."},"first_split_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"First Split S","description":"First sectional in seconds, as the source page printed it. Null where it printed none — a reserve that never started has none, and some tracks publish no sectionals at all."},"second_split_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Second Split S","description":"Second sectional in seconds. Null on every Victorian run: FastTrack publishes one split, not two."},"weight_kg":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Weight Kg","description":"Race-day weight in kilograms. Null where the page prints no weight — Western Australian meetings mostly print none (measured 2026-09-28 over 2026-09-13..28: Cannington 2.5%, Mandurah 15.4%, Northam 68.7% of runs)."},"pir":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pir","description":"Position in running as TEXT, one digit per in-running mark (\"21\" = 2nd then 1st at the marks; the finish is not a mark). Victorian runs: FastTrack's own PIR column. Other states, since 2026-09-29: the national page's In Race Positions card, which only some tracks print — measured 2026-09-28 over the 158 national races of 2026-09-27..28: NSW 79.6%, QLD 79.0%, WA 100% of runs, SA and TAS about 0% (no card at Angle Park, Gawler, Launceston, Rockhampton or Temora). The two sources' marks are not the same points on a track: compare PIR within one source. Null where the page printed none."},"grade":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Grade","description":"The race's grade in the SOURCE'S OWN WORDS — \"M\", \"4/5\", \"FFA\" on the national pages, a race type like \"Tier 3 - Maiden\" on FastTrack. The two vocabularies are NOT mapped onto each other and must not be compared across states. Null on runs whose field was stored before 2026-09-21."},"win_time_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Win Time S","description":"The winner's time in the same race, so `time_s - win_time_s` is the deficit."},"sp":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sp","description":"Official starting price, decimal return on $1. Null = unknown, never zero."},"dead_heat":{"type":"boolean","title":"Dead Heat","default":false},"trainer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Trainer"},"field_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Field Size","description":"Dogs that ran in that race."},"runner_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Ref","description":"Registry id of the dog as the collector for that track numbers it: grv:<id> or grsa:<id>."},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"`fasttrack` or `grsa` — which collector wrote the race; `pointsbetau` on a British or Irish run."},"origin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Origin","description":"`results` — the race is one we settled and serve on /v1/racing/results; `history` — the race was read back off the same official results page by our history collector and has no results row."},"result_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Result Id","description":"Id of the /v1/racing/results row this run came from. On an `origin: history` run it is a synthetic `hist:…` id that joins to nothing."}},"type":"object","title":"GreyhoundRun"},"GreyhoundStatRow":{"properties":{"track":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track","description":"Set when `by` includes track."},"distance_m":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Distance M","description":"Set when `by` includes distance."},"box":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Box","description":"Set when `by` includes box."},"grade":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Grade","description":"Set when `by` includes grade: the race's grade in the source's own words (\"5\", \"M\", \"4/5\" nationally; a race type like \"Grade 5\" or \"Mixed 4/5\" on FastTrack). The two vocabularies are NOT mapped onto each other, so compare rows within one source. A run stored with no grade groups under null."},"starts":{"type":"integer","title":"Starts","description":"Runs with status `ran` — scratchings and unused reserves are excluded from every figure here."},"wins":{"type":"integer","title":"Wins"},"places":{"type":"integer","title":"Places","description":"Finishes in the first three, wins included."},"win_pct":{"type":"number","title":"Win Pct"},"place_pct":{"type":"number","title":"Place Pct"},"avg_time_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Time S","description":"Over the runs with a recorded time."},"best_time_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Best Time S"},"avg_first_split_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg First Split S","description":"Mean first sectional over the runs in this group that carry one. Null when none does — the count behind it is not `starts`, because some tracks publish no sectionals."},"avg_sp":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Sp","description":"Mean official starting price over the runs that carry one."},"last_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Start","description":"Sydney date of the most recent run in the group."}},"type":"object","required":["starts","wins","places","win_pct","place_pct"],"title":"GreyhoundStatRow"},"GreyhoundStatsOut":{"properties":{"dog":{"type":"string","title":"Dog"},"name_key":{"type":"string","title":"Name Key"},"runner_refs":{"items":{"type":"string"},"type":"array","title":"Runner Refs"},"by":{"items":{"type":"string"},"type":"array","title":"By","description":"The dimensions the rows are grouped by, in the order given."},"rows":{"items":{"$ref":"#/components/schemas/GreyhoundStatRow"},"type":"array","title":"Rows","description":"One row per distinct combination, most starts first."},"overall":{"allOf":[{"$ref":"#/components/schemas/GreyhoundStatRow"}],"description":"The same figures over every run since `source.coverage_from`."},"source":{"$ref":"#/components/schemas/SourceBlock"}},"type":"object","required":["dog","name_key","runner_refs","by","rows","overall","source"],"title":"GreyhoundStatsOut","example":{"by":["track","grade"],"dog":"Tommy Blue","name_key":"tommyblue","overall":{"avg_first_split_s":6.71,"avg_sp":1.33,"avg_time_s":30.14,"best_time_s":30.14,"last_start":"2026-09-19","place_pct":100.0,"places":1,"starts":1,"win_pct":100.0,"wins":1},"rows":[{"avg_first_split_s":6.71,"avg_sp":1.33,"avg_time_s":30.14,"best_time_s":30.14,"grade":"M","last_start":"2026-09-19","place_pct":100.0,"places":1,"starts":1,"track":"Dubbo","win_pct":100.0,"wins":1}],"runner_refs":["grsa:441535"],"source":{"collectors":["fasttrack","grsa"],"coverage_from":"2026-06-23","coverage_to":"2026-09-20","feed":"puntersedge","refreshed_hourly":true,"region":"AU","results_from":"2026-09-13","sport":"greyhound"}}},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HorseCandidate":{"properties":{"runner_ref":{"type":"string","title":"Runner Ref","description":"A Racing Australia code for this horse. `candidates` is always empty: a name resolves to one horse. See `ambiguous`."},"runner_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Name"},"last_seen":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Seen","description":"Most recent date this ref appeared on our feeds (acceptance or result)."},"source":{"type":"string","title":"Source","description":"Where it was seen: acceptances or results."}},"type":"object","required":["runner_ref","source"],"title":"HorseCandidate"},"HorseCareer":{"properties":{"starts":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Starts"},"wins":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Wins"},"seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Seconds"},"thirds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Thirds"},"prizemoney":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Prizemoney","description":"Career prizemoney, whole AUD."},"bonus":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Bonus","description":"Total bonus (e.g. BOBS), whole AUD."},"min_dist_win_m":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Dist Win M"},"max_dist_win_m":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Dist Win M"},"splits":{"additionalProperties":{"$ref":"#/components/schemas/SplitRecord"},"type":"object","title":"Splits","description":"RA's own career splits, keyed first_up, second_up, firm, good, soft, heavy, synthetic — plus track, distance and track_distance when RA renders them. Each value is starts/wins/seconds/thirds."}},"type":"object","title":"HorseCareer"},"HorseFormOut":{"properties":{"horse_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Horse Name"},"horse_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Horse Ref","description":"The HORSE: `pe:<name>`, the registered name folded to letters and digits. The same value on /v1/racing/results runners and placings, the live board, the closing-line and price-path archives, /v1/racing/horses/runs and the form backfill — and accepted as `horse` here. This is the key to join a horse across meetings."},"runner_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Ref","description":"The Racing Australia code the page was read under — the most recent code we hold for the horse. Racing Australia issues a new code for the same horse each day, so it is not a horse identity — `horse_ref` is."},"ambiguous":{"type":"boolean","title":"Ambiguous","description":"ALWAYS FALSE since 2026-09-21, and kept only so that callers reading it do not break. A name now resolves to one horse: several ra: codes under one name are the ordinary case (they are entry codes), not two horses. `candidates` is empty with it."},"candidates":{"items":{"$ref":"#/components/schemas/HorseCandidate"},"type":"array","title":"Candidates","default":[]},"profile":{"anyOf":[{"$ref":"#/components/schemas/HorseProfile"},{"type":"null"}]},"career":{"anyOf":[{"$ref":"#/components/schemas/HorseCareer"},{"type":"null"}]},"runs":{"items":{"$ref":"#/components/schemas/HorseRun"},"type":"array","title":"Runs","default":[]},"source":{"anyOf":[{"$ref":"#/components/schemas/HorseSource"},{"type":"null"}]}},"type":"object","required":["ambiguous"],"title":"HorseFormOut","example":{"ambiguous":false,"candidates":[],"career":{"prizemoney":4875,"seconds":0,"splits":{"first_up":{"seconds":0,"starts":2,"thirds":1,"wins":0},"good":{"seconds":0,"starts":2,"thirds":1,"wins":0}},"starts":3,"thirds":1,"wins":0},"horse_name":"Zweigelt","horse_ref":"pe:zweigelt","profile":{"age_sex":"4yo Bay Gelding","colours":"Lime, Purple And Gold Braces And Armbands","dam":"MEDGRINDA","dob":"2022-11-01","last_gear_change":"20-Aug-2026, Blinkers, First Time","sire":"VINO ROSSO (USA)","trainer":"Matthew Dunn","trainer_location":"Murwillumbah"},"runner_ref":"ra:MTMwNDY2ODI0NA","runs":[{"barrier":9,"date":"2026-08-18","distance_m":1430,"field_size":9,"going":"Soft","going_rating":7,"jockey":"Andrew Mallyon","jockey_code":"MTQ5MjQ5OTIw","margin_l":0.76,"meet_code":"MTI2NzM2MDQwMA","pir":[{"at_m":800,"position":7},{"at_m":400,"position":7}],"placegetters":[{"name":"Sunset Trail","position":1,"runner_ref":"ra:MTMwMDQ4MDk2MA","weight_kg":58.0}],"position":4,"prices":[7.5,4.0,4.8,5.0],"prize_total":27000,"prize_won":1300,"race_class":"MDN","race_number":4,"result_raw":"4th of 9","sectional_distance_m":600,"sectional_time_s":37.16,"sp":5.0,"time_s":86.82,"track":"GRAF","trial":false,"weight_kg":58.5}],"source":{"feed":"racing_australia","fetched_at":"2026-09-01T11:20:00+00:00","region":"AU","sport":"thoroughbred","stale":false}}},"HorseProfile":{"properties":{"age_sex":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Age Sex","description":"As published, e.g. '4yo Bay Gelding'."},"dob":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dob"},"sire":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sire"},"dam":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dam"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"trainer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Trainer"},"trainer_location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Trainer Location"},"owners":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owners"},"colours":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Colours"},"last_gear_change":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Gear Change","description":"As published, date and gear, e.g. '20-Aug-2026, Blinkers, First Time, ...'."}},"type":"object","title":"HorseProfile"},"HorseRun":{"properties":{"date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Date"},"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue","description":"Venue name. Set when `source.feed` is 'puntersedge_results'; RA's own page gives the compressed `track` code instead and leaves this null."},"origin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Origin","description":"Only when `source.feed` is 'puntersedge_results': 'results' (our own settled results) or 'history' (collected afterwards from RA's results page)."},"track":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track","description":"RA's compressed track code as published ('W FM' is Warwick Farm, 'RAND' Randwick)."},"meet_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meet Code","description":"RA meeting code; every runner in the same meeting shares it."},"race_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Race Number"},"trial":{"type":"boolean","title":"Trial","description":"True for a barrier trial: $0 prize, 0kg weights and no market are how RA publishes them."},"position":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Position"},"field_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Field Size"},"result_raw":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Result Raw","description":"The position cell as published, kept for rows that don't parse to a number."},"distance_m":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Distance M"},"going":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Going","description":"Condition word: Firm, Good, Soft, Heavy, Synthetic."},"going_rating":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Going Rating","description":"The number after the condition (Soft7 -> 7). Null where RA prints none."},"race_class":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Class","description":"RA's official class string for that race: 'MDN-SW', 'CTRY MDN', 'BM64', '3Y MDN-TRL'."},"prize_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Prize Total"},"prize_won":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Prize Won","description":"This horse's cut, when RA shows it."},"jockey":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jockey"},"jockey_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jockey Code","description":"RA's stable jockey code, consistent across every page that names the same rider."},"trainer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Trainer","description":"Trainer of the day for THIS run, filled from our own results archive — RA's form table never names the trainer per run. Populated for runs from 2026-09-01 onward (the archive's full-runner floor) and NULL before that; `profile.trainer` is the horse's current trainer, which is usually but not always the same person."},"weight_kg":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Weight Kg"},"barrier":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Barrier"},"time_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Time S","description":"Official race time in seconds."},"sectional_distance_m":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sectional Distance M","description":"The published closing sectional's distance, usually 600."},"sectional_time_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sectional Time S"},"margin_l":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Margin L","description":"Margin in lengths. On this horse's own win it is the winning margin over the runner-up."},"pir":{"items":{"$ref":"#/components/schemas/RunPIR"},"type":"array","title":"Pir","description":"Position in running at the published marks (800m/400m)."},"prices":{"items":{"type":"number"},"type":"array","title":"Prices","description":"The published price path, opening quote through SP, oldest first."},"sp":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sp","description":"Starting price — the last entry of `prices`."},"placegetters":{"items":{"$ref":"#/components/schemas/RunPlacegetter"},"type":"array","title":"Placegetters","description":"The two nearest finishers RA lists (2nd and 3rd on this horse's win; the winner and runner-up otherwise), each with its own ra: code."}},"type":"object","required":["trial"],"title":"HorseRun"},"HorseRunRow":{"properties":{"date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Date","description":"Sydney calendar date of the race; the London date for a British or Irish race (/v1/racing/form, 2026-10-12)."},"start_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Time","description":"Scheduled start, UTC."},"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue"},"race_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Race Number"},"distance_m":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Distance M"},"race_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Name"},"track_condition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track Condition","description":"As published for the meeting, e.g. 'Good 4'."},"number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Number","description":"Saddlecloth number."},"box":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Box","description":"Barrier. Null on a scratching — a runner that never jumped has no barrier."},"position":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Position","description":"Finishing place. Null when the horse did not start (see `status`) or started and did not finish (see `abnormal`)."},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"`ran` or `scratched`, exactly as /v1/racing/results publishes it."},"abnormal":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Abnormal","description":"Racing Australia's own Finish cell whenever it is not a number, passed through verbatim (Fell, PulledUp, TailedOff, LR, FF, LP, and 'SB' for scratched at the barrier). THERE IS NO CLOSED LIST — an unseen code reaches you unchanged. Read it with `status`."},"margin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Margin","description":"Lengths behind the winner as RA printed it ('0.75L'); null where it prints none."},"time_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Time S","description":"The horse's own time in seconds, which RA publishes for the WINNER only: since 29 September 2026 a position-1 run carries the race time from RA's race header (the same value as `race_time_s`); every other run's is null, because RA's results table prints no per-runner time and a beaten horse's time is not the race time. Null on runs stored before then until that meeting is re-read."},"sp":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sp","description":"Official starting price, decimal return on $1. Null = unknown, never zero."},"sp_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sp Note","description":"RA's favouritism marker: 'F' favourite, 'EF' equal favourite; null otherwise."},"dead_heat":{"type":"boolean","title":"Dead Heat","default":false},"trainer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Trainer","description":"Trainer of the day for THIS run, as the result published it."},"jockey":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jockey","description":"Rider of the day for THIS run."},"field_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Field Size","description":"Horses that ran in that race."},"weight_kg":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Weight Kg","description":"Allotted weight in kg, as RA's results page prints it. Kept since 28 September 2026. On origin tabtouch runs, and on runs from 10 August to 27 September 2026 whose result lacked one, it is the weight TABtouch's race page printed for the starter. Null on a scratching and where neither source had one."},"carried_kg":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Carried Kg","description":"Weight the horse actually carried, printed by RA only when it differs from the allotted weight ('58kg (cd 56kg)'): usually an apprentice's claim (under), sometimes a rider's overweight (over), so do not assume the sign. Null when RA printed none; read coalesce(carried_kg, weight_kg) for what was carried."},"race_class":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Class","description":"The race's class as RA's own leading conditions term -- 'Maiden', 'BenchMark 64', 'Class 1', 'Group 1' -- mechanical, never inferred, on runs stored since 28 September 2026 and on origin history runs. On origin tabtouch runs, and on runs from 10 August to 27 September 2026, it is TABtouch's class line as printed -- 'BM58 HCP', 'MDN-SW', 'GROUP 1 3UP HCP' -- so normalise the two wordings before comparing classes across runs. Null where neither source had one."},"race_time_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Race Time S","description":"The race's winning time in seconds, from RA's race header ('Time: 0:58.69' -> 58.69). The same on every run of that race. Kept since 29 September 2026: null on a run stored before then until that meeting is re-read."},"last_600m_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Last 600M S","description":"The race's last-600m time in seconds, from the same header ('Last 600m: 0:34.49' -> 34.49) -- the leader's closing sectional as RA times it, a RACE figure, not this horse's own. Kept since 29 September 2026."},"prize_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Prize Total","description":"Total prize money of the race in AUD, from RA's prize line ('Of $30,000.'). Kept since 29 September 2026."},"prize_won":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prize Won","description":"Place money this horse won in AUD, read off the same prize line by its finishing place ('1st $15,000, 2nd $5,300, ...'); dead-heaters share the places they tie for equally. 0 for a finish outside the places RA lists as paid; null for a horse with no finishing place (scratched, fell) or a race whose prize line was not read. Excludes bonuses and welfare-fund amounts. Kept since 29 September 2026."},"runner_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Ref","description":"The Racing Australia code our results collector published for this run, from race day — the same value on the matching /v1/racing/results runner and in the archives for that race. Racing Australia issues a new code for the same horse each day, so it differs from run to run: group a horse's runs on `horse_ref`, not on this."},"horse_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Horse Ref","description":"The horse this run belongs to: `pe:<name>`, the registered name folded to letters and digits. Identical on every run of the same horse, and the same value /v1/racing/results runners and placings, the live board, the closing-line and price-path archives and /v1/racing/horses/form carry — the key that joins a horse across meetings, where `runner_ref` joins the rows about one race."},"result_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Result Id","description":"Id of the /v1/racing/results row this run came from. A run with origin 'history' or 'tabtouch' carries a synthetic 'hist:...' id that joins to /v1/racing/results on nothing."},"origin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Origin","description":"'results' -- from our own settled results (/v1/racing/results). 'history' -- collected afterwards from Racing Australia's results page for a race held before our live results began: same fields, no /v1/racing/results row behind it. 'tabtouch' -- collected afterwards from TABtouch's public race page for a race older than Racing Australia's results pages reach (about fifteen months back): number, rider, barrier, weight, trainer and the first four placings, so position null there means unplaced; no margin, time or sp."}},"type":"object","title":"HorseRunRow"},"HorseRunsOut":{"properties":{"horse":{"type":"string","title":"Horse","description":"The horse's name as our results spell it."},"name_key":{"type":"string","title":"Name Key","description":"The identity the runs were grouped on (letters and digits)."},"horse_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Horse Ref","description":"`name_key` as the identifier every thoroughbred surface publishes: `pe:<name_key>`. Pass it back here, or to /v1/racing/horses/form, and read it off /v1/racing/results runners and placings, the live board and the closing-line and price-path archives to gather one horse across meetings."},"runner_refs":{"items":{"type":"string"},"type":"array","title":"Runner Refs","description":"Every Racing Australia horsecode our results published under this name, sorted. Several is normal, not an error — see `runs[].runner_ref`."},"runs_total":{"type":"integer","title":"Runs Total","description":"STARTS held for this horse since `source.coverage_from`: runs with `status` `ran`, the same count as `stats.starts`. A scratching is not a start. Before 2026-10-13 this counted every held row, scratchings included; that count is `held_rows_total`."},"held_rows_total":{"type":"integer","title":"Held Rows Total","description":"Every row held for this horse since `source.coverage_from`, scratchings included. `runs` is the newest `limit` of THESE rows, so page on this, not on `runs_total`. Measured on 2026-10-12, 61,719 of the store's 282,639 rows (21.8%) were scratchings."},"runs":{"items":{"$ref":"#/components/schemas/HorseRunRow"},"type":"array","title":"Runs"},"stats":{"allOf":[{"$ref":"#/components/schemas/HorseRunsStats"}],"description":"Over EVERY held run, not just the ones returned."},"source":{"$ref":"#/components/schemas/RunsSourceBlock"}},"type":"object","required":["horse","name_key","runner_refs","runs_total","held_rows_total","runs","stats","source"],"title":"HorseRunsOut","example":{"held_rows_total":1,"horse":"Please Mizter","horse_ref":"pe:pleasemizter","name_key":"pleasemizter","runner_refs":["ra:MjgwNjM0ODg2NDA"],"runs":[{"box":13,"date":"2026-09-20","dead_heat":false,"distance_m":1300,"field_size":12,"horse_ref":"pe:pleasemizter","jockey":"Jason Li","last_600m_s":34.61,"margin":"0.75L","number":14,"origin":"results","position":1,"prize_total":50000,"prize_won":27500.0,"race_class":"Maiden","race_name":"Mdn Plate","race_number":4,"race_time_s":77.12,"result_id":"res_9f1c…","runner_ref":"ra:MjgwNjM0ODg2NDA","sp":7.5,"start_time":"2026-09-20T03:35:00Z","status":"ran","time_s":77.12,"track_condition":"Good 4","trainer":"Peter Fernie","venue":"Rosehill","weight_kg":57.0}],"runs_total":1,"source":{"collectors":["racing_australia","tabtouch"],"coverage_from":"2026-09-01","coverage_to":"2026-09-20","feed":"puntersedge","refreshed_hourly":true,"region":"AU","sport":"thoroughbred"},"stats":{"place_pct":100.0,"places":1,"starts":1,"win_pct":100.0,"wins":1}}},"HorseRunsStats":{"properties":{"starts":{"type":"integer","title":"Starts","description":"Runs with status `ran` — scratchings are excluded from every figure here. Equal to `runs_total` since 2026-10-13; `held_rows_total` counts the scratchings too, which is why it can be higher."},"wins":{"type":"integer","title":"Wins"},"places":{"type":"integer","title":"Places","description":"Finishes in the first three, wins included."},"win_pct":{"type":"number","title":"Win Pct"},"place_pct":{"type":"number","title":"Place Pct"}},"type":"object","required":["starts","wins","places","win_pct","place_pct"],"title":"HorseRunsStats"},"HorseSource":{"properties":{"feed":{"type":"string","title":"Feed","description":"'racing_australia' -- Racing Australia's own form page (profile, career, full run lines). 'puntersedge_results' -- RA was not serving this horse's page and no stored copy existed, so `runs` is the horse's record in OUR OWN collected results instead; `profile` and `career` are then null, and `note` says what the runs cover.","default":"racing_australia"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Set with feed 'puntersedge_results': what these runs are, and what they are not."},"region":{"type":"string","title":"Region","default":"AU"},"sport":{"type":"string","title":"Sport","default":"thoroughbred"},"fetched_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fetched At","description":"UTC time this horse's page was last read from RA."},"stale":{"type":"boolean","title":"Stale","description":"True when RA could not be reached just now and this is the stored copy served past its six-hour refresh window.","default":false}},"type":"object","title":"HorseSource"},"IntelOut":{"properties":{"race":{"$ref":"#/components/schemas/IntelRaceOut"},"window":{"type":"object","title":"Window","description":"Whether the 60-minute capture window is open for this race, when its first tick landed, and which marks could be evaluated.","default":{}},"method":{"type":"object","title":"Method"},"market":{"type":"object","title":"Market","description":"Race level: best-price market percentage now and at T-15 / T-60, the favourite now and at T-10, and the books behind the consensus.","default":{}},"runners":{"items":{"type":"object"},"type":"array","title":"Runners","description":"One object per runner, favourite first: consensus at each mark with high and low, time-anchored moves, breadth, first mover and the signal class.","default":[]},"scratchings":{"items":{"type":"object"},"type":"array","title":"Scratchings","default":[]},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"type":"object","required":["race","method"],"title":"IntelOut"},"IntelRaceOut":{"properties":{"race_id":{"type":"string","title":"Race Id"},"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue"},"race_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Race Number"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"start_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Time"},"secs_to_jump":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Secs To Jump"},"status":{"type":"string","title":"Status","description":"upcoming · jumped.","default":"upcoming"},"as_of":{"type":"string","title":"As Of"}},"type":"object","required":["race_id","as_of"],"title":"IntelRaceOut"},"KeyResendIn":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"KeyResendIn"},"LinesArbOut":{"properties":{"event_id":{"type":"string","title":"Event Id"},"sport_key":{"type":"string","title":"Sport Key"},"home_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Team"},"away_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Away Team"},"commence_time":{"type":"string","title":"Commence Time"},"market_type":{"type":"string","title":"Market Type"},"opportunities":{"items":{"type":"object"},"type":"array","title":"Opportunities"}},"type":"object","required":["event_id","sport_key","home_team","away_team","commence_time","market_type","opportunities"],"title":"LinesArbOut","example":{"away_team":"Magpies","commence_time":"2026-06-07T09:30:00Z","event_id":"event_123","home_team":"Lions","market_type":"totals","opportunities":[{"arb_pct":1.3,"is_arb":true,"is_middle":true,"optimal_stakes":[],"side_a":{"bookmaker":"sportsbet","name":"Over","point":38.5,"price":1.95},"side_b":{"bookmaker":"tab","name":"Under","point":39.5,"price":1.98}}],"sport_key":"afl"}},"LoginExchangeIn":{"properties":{"token":{"type":"string","title":"Token"}},"type":"object","required":["token"],"title":"LoginExchangeIn"},"LoginRequestIn":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"LoginRequestIn"},"MarketBestOut":{"properties":{"number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Number"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"runner_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Ref"},"price":{"type":"number","title":"Price"},"bookmaker":{"type":"string","title":"Bookmaker"}},"type":"object","required":["price","bookmaker"],"title":"MarketBestOut"},"MarketBookOut":{"properties":{"key":{"type":"string","title":"Key"},"runners_quoted":{"type":"integer","title":"Runners Quoted"},"overround_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Overround Pct","description":"Sum of 1/price across this book's quoted runners, as a percentage. Null when fewer than two runners are quoted, where the figure would describe nothing."},"observed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Observed At","description":"Newest price change this book made on this race, UTC."},"runners":{"items":{"$ref":"#/components/schemas/MarketRunnerOut"},"type":"array","title":"Runners","default":[]}},"type":"object","required":["key","runners_quoted"],"title":"MarketBookOut"},"MarketOut":{"properties":{"market":{"type":"string","title":"Market","description":"win · place · top2 · top3 · top4."},"places":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Places","description":"Runners the bet covers: 1 for win, the race's places_paid for place, 2/3/4 for the Same Race Multi legs. Null when the race never reported places_paid."},"bookmakers":{"items":{"$ref":"#/components/schemas/MarketBookOut"},"type":"array","title":"Bookmakers","default":[]},"best":{"items":{"$ref":"#/components/schemas/MarketBestOut"},"type":"array","title":"Best","default":[]}},"type":"object","required":["market"],"title":"MarketOut"},"MarketRunnerOut":{"properties":{"number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Number","description":"Saddlecloth number (box for greyhounds)."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"runner_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Ref","description":"Registry id — ra:<horsecode> or grv:<dogId> — where one is known."},"horse_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Horse Ref","description":"pe:<name>, the stable thoroughbred identity. Null off AU thoroughbreds."},"price":{"type":"number","title":"Price"},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url","description":"The book's own page for this race. Present while the market is open (source is live); null on a race answered from the stored close, which keeps the prices and the runner identities but not the links."},"source_link":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Link","description":"Your own link for this price: source_url built through the link templates stored with PUT /v1/link-templates (since 2026-10-14). Absent when your account stores none, or there is nothing to link."}},"type":"object","required":["price"],"title":"MarketRunnerOut"},"MarketSummaryOut":{"properties":{"rows_returned":{"type":"integer","title":"Rows Returned"},"total_rows":{"type":"integer","title":"Total Rows","description":"Rows matching the filter before limit/offset."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"summary_from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary From","description":"Earliest start_time in the table. The summary is built from the 45-day tick store, so it begins where that store stood when the job first ran."},"window_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Window Days","description":"Set when the caller's plan clamps the lookback. NULL means the whole table."},"rows":{"items":{"type":"object"},"type":"array","title":"Rows"}},"type":"object","required":["rows_returned","total_rows","limit","offset","rows"],"title":"MarketSummaryOut","example":{"limit":500,"offset":0,"rows":[{"best_close_bookmaker":"pointsbetau","best_close_price":7.5,"best_vs_consensus_pct":4.17,"books_close":12,"books_drifting":3,"books_firming":7,"books_t15":12,"books_t60":11,"books_unchanged":2,"books_with_history":12,"category":"horse","close_source":"archive","consensus_close":7.2,"consensus_high":7.8,"consensus_high_at":"2026-10-03T02:04:00Z","consensus_low":6.9,"consensus_low_at":"2026-10-03T02:51:00Z","consensus_t10":7.2,"consensus_t15":7.1,"consensus_t2":7.05,"consensus_t30":7.4,"consensus_t5":7.0,"consensus_t60":7.6,"country":"AU","finish_position":2,"first_mover_at":"2026-10-03T02:11:40Z","first_mover_bookmaker":"neds","first_mover_lead_s":95,"follower_median_lag_s":140,"horse_ref":"pe:aeolian","meeting_date_aet":"2026-10-03","move_from_t15_pct":1.41,"move_from_t60_pct":-5.26,"move_t5_close_pct":2.86,"race_id":"race_123","race_number":2,"result_status":"final","runner_key":"aeolian","runner_name":"Aeolian","runner_number":4,"runner_ref":"ra:2098765","signal":"steady","start_time":"2026-10-03T03:00:00Z","ticks_last_5m":9,"ticks_total":84,"venue":"Caulfield","venue_id":"caulfield","venue_site":"caulfield"}],"rows_returned":1,"summary_from":"2026-08-21T00:12:00Z","total_rows":1842}},"MarketingIn":{"properties":{"opt_in":{"type":"boolean","title":"Opt In"}},"type":"object","required":["opt_in"],"title":"MarketingIn"},"MarketsOut":{"properties":{"race":{"$ref":"#/components/schemas/RaceHeadOut"},"markets":{"items":{"$ref":"#/components/schemas/MarketOut"},"type":"array","title":"Markets","default":[]},"exotics":{"anyOf":[{"$ref":"#/components/schemas/ExoticsOut"},{"type":"null"}]},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"quote_detail":{"items":{"type":"object"},"type":"array","title":"Quote Detail","description":"Per book and runner, the fields the book publishes beside its prices (since 2026-09-29): open_price (the book's own opening price), flucs (its fluctuations, most recent first), tote_win / tote_place (its tote dividends by product code; PROV is an indicative pre-race dividend), boost_price / boost_type, and the exchange fields. Each entry carries bookmaker, number, name, runner_ref, seen_at (UTC, when the fields were read) and only the fields that book carries.","default":[]},"scratchings":{"items":{"type":"object"},"type":"array","title":"Scratchings","description":"The race's scratched runners: name, number, barrier, scratched_at (the scratching book's own time, UTC), vacant for an empty box. Read from the open race while it is live and from the capture taken at the jump once it has been purged (from 2026-09-29).","default":[]},"tote_series":{"items":{"type":"object"},"type":"array","title":"Tote Series","description":"Per book and runner, the book's tote approximates at fixed marks before the jump (since 2026-10-06): points at 3600, 1800, 900, 600, 300, 180, 120 and 60 seconds, each the first read the capture saw at or inside its mark: mark_seconds, read_at (UTC), secs_to_jump, and tote_win / tote_place by product code (PROV is an indicative pre-race dividend). The last read before the jump is tote_win / tote_place in quote_detail.","default":[]},"book_scratchings":{"items":{"type":"object"},"type":"array","title":"Book Scratchings","description":"Per book and scratched runner (since 2026-10-06): reported_at, the first time that book's own feed listed the scratching; book_scratched_at, the scratch time the book itself states, where it carries one; first_quoted_at / last_quoted_at, when that book's feed still carried a win price for the runner after the race's scratchings named it, to the minute, with last_quoted_win (for a book that flags the scratching itself, recorded once at the flag: its price at the scratching). All UTC. Never served as a price.","default":[]}},"type":"object","required":["race"],"title":"MarketsOut"},"MoverOut":{"properties":{"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue"},"race_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Race Number"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"start_time":{"type":"string","title":"Start Time"},"mins_to_jump":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Mins To Jump"},"runner":{"type":"string","title":"Runner"},"number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Number"},"direction":{"type":"string","title":"Direction"},"open_price":{"type":"number","title":"Open Price"},"current_price":{"type":"number","title":"Current Price"},"move_pct":{"type":"number","title":"Move Pct"},"books_quoting":{"type":"integer","title":"Books Quoting","description":"Books with a price for this runner, including ones we have never observed move."},"books_with_history":{"type":"integer","title":"Books With History","description":"Books with two or more captured prices — the only ones that can evidence a move. The consensus and the `min_books` gate use THIS count, not `books_quoting`."},"books_firming":{"type":"integer","title":"Books Firming"},"books_drifting":{"type":"integer","title":"Books Drifting"},"books_unchanged":{"type":"integer","title":"Books Unchanged","description":"Books whose captured price has not moved, including books seen only once. books_firming + books_drifting + books_unchanged == books_quoting."},"bookmakers":{"items":{"type":"object"},"type":"array","title":"Bookmakers"}},"type":"object","required":["venue","race_number","category","country","start_time","runner","direction","open_price","current_price","move_pct","books_quoting","books_with_history","books_firming","books_drifting","books_unchanged","bookmakers"],"title":"MoverOut","example":{"bookmakers":[{"counted_in_consensus":true,"current_price":9.0,"key":"betright","move_pct":-18.18,"open_price":11.0,"open_secs_to_jump":3597,"points":4}],"books_drifting":0,"books_firming":9,"books_quoting":10,"books_unchanged":1,"books_with_history":9,"category":"horse","country":"AU","current_price":9.17,"direction":"firming","mins_to_jump":3,"move_pct":-18.3,"number":10,"open_price":11.22,"race_number":8,"runner":"Magnaburma","start_time":"2026-08-15T08:52:00Z","venue":"Broome"}},"NextRaceOut":{"properties":{"race_id":{"type":"string","title":"Race Id"},"source_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Id"},"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue"},"venue_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue Id","description":"Stable slug for this venue spelling ('sandown-hillside'). Spelling variants of one venue share one id."},"venue_canonical":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue Canonical","description":"Canonical display name for venue_id ('Sandown Hillside')."},"venue_site":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue Site","description":"Physical-complex key: tracks sharing one venue complex share one site ('sandown' covers Sandown, Sandown Hillside, Sandown Lakeside and the Sandown Park greyhound track)."},"race_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Race Number"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"start_time":{"type":"string","title":"Start Time"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"race_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Name"},"distance_m":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Distance M"},"track_condition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track Condition"},"weather":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Weather"},"places_paid":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Places Paid","description":"Places the race pays, as the bookmaker supplying it states. For British and Irish races this is the each-way terms' place count (2 or 3, 4 on a 16-plus handicap) and the UK books' place_price is derived from the same terms; for Australian races it is the quoted place market's count. Null when no bookmaker on the race supplies it."},"track_condition_changed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track Condition Changed At","description":"UTC instant of the most recent logged track-condition, weather or rail transition for this race's meeting — Racing Australia's re-published rating or a bookmaker's race page moving between two of our polls. Null when nothing has changed since we began watching the meeting. /v1/racing/track-conditions serves the sequence."},"race_class":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Class","description":"The first term of RA's official conditions line for this race ('BenchMark 64', 'Maiden', 'Group 1'). AU thoroughbred only; null elsewhere."},"conditions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conditions","description":"RA's official conditions line, verbatim — class, race type, minimum weight, age/sex restrictions, claiming allowances."},"prize_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Prize Total","description":"Total advertised prize for the race, whole AUD."},"rail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rail","description":"Rail position for the meeting, as RA publishes it ('+3m Entire'). AU thoroughbred only."},"track_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track Name","description":"Racing Australia's own name for the course this race runs on, verbatim: 'Hillside' at Sandown, 'Randwick' vs the Kensington inner course, 'Main' elsewhere. Combine with venue_site to tell dual-course meetings apart. AU thoroughbred only; null elsewhere."},"market_closed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Closed At","description":"UTC time at which the market was FIRST seen shut — the book stopped taking bets on this race. Poll-observed: this is the first poll that saw the state, not the exact instant, so it lands up to one polling pass after it, and passes are most frequent in the minutes before the jump. Set once: a market that flickers SUSPENDED -> OPEN -> SUSPENDED keeps the FIRST suspension. TWO SOURCES report market state. PointsBet closes its book at the real jump — measured 2026-09-03 over 9 live races followed across their jump, 0.2 to 1.4 minutes AFTER the advertised start on AU/NZ cards — and the Betfair Exchange reports it while it quotes the market, which is until about two minutes before the jump. `market_state_source` names the one that produced this value. Exchange-sourced stamps are withheld from customer plans, so a race seen shut only by the exchange reads null. Bookmaker-sourced stamps are served to everybody. Null also means no feed observed a transition — it never means the market stayed open."},"inplay_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Inplay At","description":"UTC time at which the race was FIRST seen in-play — the actual off, as opposed to `start_time`, which is the ADVERTISED off and moves. Same poll-observed, set-once semantics as `market_closed_at`, and withheld on the same source rule. Reported by the Betfair Exchange only: bookmakers do not run racing in-play, so a bookmaker feed says when it CLOSED its book, not when the race went. For a race the exchange has stopped quoting, `market_closed_at` from a bookmaker is the closest measured instant to the off."},"market_state_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market State Source","description":"Which feed's market state produced `market_closed_at` / `inplay_at` on this race — 'pointsbetau' for the bookmaker, 'betfair_ex_au' for the exchange. Set once, alongside the first stamp. Null when no transition has been observed, and null for customer plans whenever the stamps themselves are withheld, so a non-null value here is always the provenance of values you can see."},"scratchings":{"items":{"type":"object"},"type":"array","title":"Scratchings","default":[]},"runners":{"items":{"type":"object"},"type":"array","title":"Runners"},"data_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Data Age Seconds","description":"Age in seconds of the OLDEST bookmaker quote in this race — the staleness you are exposed to if you act on any price here. Since 2026-08-15 this is the worst leg, not the freshest; `freshest_age_seconds` carries the best-case figure."},"freshest_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Freshest Age Seconds","description":"Age in seconds of the freshest bookmaker quote in this race."},"refresh_tier":{"type":"string","title":"Refresh Tier","description":"Which refresh rule this race is on, and therefore what the race-level `stale` means. `live` = inside 3 hours of the jump: books re-price it on every 8-20s poll, and `stale` fires past 120 seconds. `card` = further out: books re-price it about every 15 minutes so a full day fits inside their rate limits, and `stale` fires past 1800 seconds. Every published quote carries ITS OWN `refresh_tier`, and it can be `card` on a `live` race: Betr's upstream rate-limits us, so that book stops re-pricing every poll 30 minutes before the jump rather than 3 hours (PlayUp, on a 6-requests-a-minute budget, at 6 minutes), and its quotes between there and 3 hours are refreshed every 5-15 minutes, published as `card`, and judged against 1800 seconds (since 2026-09-07; before that they were marked stale). A book's tier is never tighter than its race's. `age_seconds` is the TRUE age in every case — only the threshold changes.","default":"live"},"stale":{"type":"boolean","title":"Stale","description":"True when the oldest contributing bookmaker is older than this race's `refresh_tier` threshold — 120s on `live`, 1800s on `card`. It does NOT mean the whole race is dead — read `stale_bookmakers` and each bookmaker's own `stale`/`age_seconds`.","default":false},"stale_bookmakers":{"items":{"type":"string"},"type":"array","title":"Stale Bookmakers","description":"Bookmaker keys in this race whose quote is older than the `refresh_tier` threshold (120s live, 1800s card)."},"cached":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Cached"},"cache_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cache Age Seconds"}},"type":"object","required":["race_id","source_id","venue","race_number","category","start_time","country","runners"],"title":"NextRaceOut","example":{"category":"horse","country":"AU","data_age_seconds":7,"race_id":"race_123","race_number":4,"refresh_tier":"live","runners":[{"bookmakers":[{"age_seconds":7,"key":"sportsbet","refresh_tier":"live","source_url":"https://www.sportsbet.com.au/horse-racing/australia-nz/eagle-farm/race-4-10861234","stale":false,"win_price":3.2},{"age_seconds":9,"betslip_app_only":false,"betslip_place_url":"https://www.unibet.com.au/racing?competitorKey=202609030600.T.AUS.mackay.4.emilia_jane&priceCode=FXD&marketName=Place&priceBetType=FixedPlace#/event/202609030600.T.AUS.mackay.4/Place/FixedPlace","betslip_win_url":"https://www.unibet.com.au/racing?competitorKey=202609030600.T.AUS.mackay.4.emilia_jane&priceCode=FXD&marketName=Win&priceBetType=FixedWin#/event/202609030600.T.AUS.mackay.4/Win/FixedWin","key":"unibet","place_price":1.55,"refresh_tier":"live","source_url":"https://www.unibet.com.au/racing#/event/202609030600.T.AUS.mackay.4","stale":false,"win_price":3.3},{"age_seconds":11,"betslip_app_only":true,"betslip_place_url":"https://www.ladbrokes.com.au/racing/casino/7d3bfac5-f5b2-4ad1-82dc-e00ae2220a36/1a51322a-65bf-4f96-88b2-c294faf9199c/fp","betslip_win_url":"https://www.ladbrokes.com.au/racing/casino/7d3bfac5-f5b2-4ad1-82dc-e00ae2220a36/1a51322a-65bf-4f96-88b2-c294faf9199c/fw","key":"ladbrokes_au","place_price":1.5,"refresh_tier":"live","source_url":"https://www.ladbrokes.com.au/racing/casino/7d3bfac5-f5b2-4ad1-82dc-e00ae2220a36","stale":false,"top2_price":1.9,"top3_price":1.5,"top4_price":1.3,"win_price":3.1}],"horse_ref":"pe:examplerunner","name":"Example Runner","number":1}],"source_id":"src_a1b2c3d4e5","stale":false,"start_time":"2026-06-07T04:10:00Z","venue":"Eagle Farm"}},"OverageIn":{"properties":{"enabled":{"type":"boolean","title":"Enabled","description":"true lets this account run past its monthly allowance, up to the ceiling, and be invoiced for the excess. false restores the hard 402 at the allowance."}},"type":"object","required":["enabled"],"title":"OverageIn"},"OverageOut":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"eligible":{"type":"boolean","title":"Eligible"},"rate_per_1000_cents":{"type":"integer","title":"Rate Per 1000 Cents"},"max_credits":{"type":"integer","title":"Max Credits"},"ceiling":{"type":"integer","title":"Ceiling"},"credits_over_this_period":{"type":"integer","title":"Credits Over This Period"},"estimated_cents_this_period":{"type":"integer","title":"Estimated Cents This Period"},"plan":{"type":"string","title":"Plan"},"credits_limit":{"type":"integer","title":"Credits Limit"},"credits_used":{"type":"integer","title":"Credits Used"}},"type":"object","required":["enabled","eligible","rate_per_1000_cents","max_credits","ceiling","credits_over_this_period","estimated_cents_this_period","plan","credits_limit","credits_used"],"title":"OverageOut","example":{"ceiling":450000,"credits_limit":300000,"credits_over_this_period":12400,"credits_used":312400,"eligible":true,"enabled":true,"estimated_cents_this_period":780,"max_credits":150000,"plan":"pro_plus","rate_per_1000_cents":60}},"PasswordLoginIn":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"PasswordLoginIn"},"PremiershipOut":{"properties":{"role":{"type":"string","title":"Role"},"season_start_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Season Start Year"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"Echo of the filter; null when a name search crossed all states."},"scope":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"rows":{"items":{"$ref":"#/components/schemas/PremiershipRow"},"type":"array","title":"Rows","default":[]},"source":{"$ref":"#/components/schemas/PremiershipSource"}},"type":"object","required":["role","source"],"title":"PremiershipOut","example":{"role":"trainer","rows":[{"fifths":14.0,"firsts":17.0,"fourths":22.0,"name":"Chris Waller","period_from":"2026-08-01","period_to":"2026-09-02","prize_money":2437350.0,"ra_code":"Njg0MjM2MA","rank":1,"scope":"all","seconds":23.5,"starts":147,"state":"NSW","strike_rate":11.6,"thirds":20.0}],"scope":"all","season_start_year":2026,"source":{"feed":"racing-australia-premierships","region":"AU","scraped_at":"2026-09-02T07:10:00+00:00","sport":"thoroughbred","top_n":20},"state":"NSW"}},"PremiershipRow":{"properties":{"state":{"type":"string","title":"State","description":"AUS is the national table — RA's own combined leaderboard, not a sum of the state rows."},"scope":{"type":"string","title":"Scope","description":"RA's split: all, metro, provincial, country or picnic."},"rank":{"type":"integer","title":"Rank","description":"Position in RA's table, 1-20."},"name":{"type":"string","title":"Name"},"ra_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ra Code","description":"RA's stable code from the row's engagement link — the same namespace as trainer_code on acceptance data and jockey_code on /v1/racing/horses/form runs, so rows join on identity, not name spelling."},"firsts":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Firsts","description":"Wins. REAL, not int: dead heats appear as halves in some tables (RA prints 23½)."},"seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Seconds"},"thirds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Thirds"},"fourths":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fourths"},"fifths":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fifths"},"prize_money":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prize Money","description":"Season prize money, AUD."},"strike_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Strike Rate","description":"RA's own figure, percent."},"starts":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Starts"},"period_from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period From","description":"Start of the window this row measures (RA seasons run 1 Aug to 31 Jul)."},"period_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period To","description":"End of the measured window. On the running season this is the day RA rendered the table, which makes it the per-row freshness stamp: rows from a table scraped two nights ago carry that day, not today."}},"type":"object","required":["state","scope","rank","name"],"title":"PremiershipRow"},"PremiershipSource":{"properties":{"feed":{"type":"string","title":"Feed","default":"racing-australia-premierships"},"region":{"type":"string","title":"Region","default":"AU"},"sport":{"type":"string","title":"Sport","default":"thoroughbred"},"scraped_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scraped At","description":"UTC time of the newest row served in this response. For the usual single-table lookup (a `state` + `scope`) every row comes from one scrape, so this IS that table's age. A `name` search spans tables scraped on different nights, so there read it as the freshest of them and use `period_to` for the age of each individual row."},"top_n":{"type":"integer","title":"Top N","description":"RA publishes each premiership as a top-20 table. Absence from `rows` means outside the top 20, never inactivity.","default":20}},"type":"object","title":"PremiershipSource"},"PriceHistoryOut":{"properties":{"race_id":{"type":"string","title":"Race Id"},"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue"},"venue_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue Id"},"venue_canonical":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue Canonical"},"venue_site":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue Site"},"race_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Race Number"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"start_time":{"type":"string","title":"Start Time"},"points_returned":{"type":"integer","title":"Points Returned"},"truncated":{"type":"boolean","title":"Truncated"},"history_from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"History From","description":"Start time of the earliest race this endpoint can answer for. Since 2026-09-01 races older than the ~45-day live window are served from the permanent archive, so this floor is fixed at capture start (2026-08-04) and history only deepens."},"runners":{"items":{"type":"object"},"type":"array","title":"Runners"}},"type":"object","required":["race_id","venue","race_number","category","country","start_time","points_returned","truncated","runners"],"title":"PriceHistoryOut","example":{"category":"horse","country":"AU","history_from":"2026-08-04T09:22:00Z","points_returned":2481,"race_id":"race_123","race_number":2,"runners":[{"bookmakers":[{"close_price":7.0,"close_secs_to_jump":41,"high":7.0,"key":"sportsbet","low":6.0,"move_pct":7.69,"open_is_baseline":true,"open_price":6.5,"open_secs_to_jump":3595,"points":[{"captured_at":"2026-08-15T02:00:04Z","secs_to_jump":3595,"win_price":6.5}],"points_count":10}],"name":"Aeolian","number":4}],"start_time":"2026-08-15T03:00:00Z","truncated":false,"venue":"Caulfield"}},"PricePathsOut":{"properties":{"rows_returned":{"type":"integer","title":"Rows Returned"},"total_points":{"type":"integer","title":"Total Points","description":"Points matching the filter before limit/offset"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"archive_from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Archive From"},"rows":{"items":{"type":"object"},"type":"array","title":"Rows"}},"type":"object","required":["rows_returned","total_points","limit","offset","rows"],"title":"PricePathsOut"},"RaceEventOut":{"properties":{"race_id":{"type":"string","title":"Race Id"},"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue"},"venue_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue Id"},"venue_canonical":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue Canonical"},"venue_site":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue Site"},"race_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Race Number"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"start_time":{"type":"string","title":"Start Time"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"race_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Name"},"distance_m":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Distance M"},"track_condition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track Condition"},"weather":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Weather"},"race_class":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Class","description":"The first term of RA's official conditions line ('BenchMark 64', 'Maiden', 'Group 1'). AU thoroughbred only."},"conditions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conditions","description":"RA's official conditions line, verbatim."},"prize_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Prize Total","description":"Total advertised prize, whole AUD."},"rail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rail","description":"Rail position for the meeting, as RA publishes it."},"track_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track Name","description":"RA's own name for the course this race runs on ('Hillside', 'Randwick', 'Main'). AU thoroughbred only; null elsewhere."},"track_condition_changed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track Condition Changed At","description":"UTC instant of the most recent logged track-condition, weather or rail transition for this race's meeting; null when nothing has changed since we began watching it. The sequence is on /v1/racing/track-conditions."},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Set when the race row is created and revised exactly once, if at all: to 'abandoned' when Racing Australia posts an official abandoned, postponed or transferred notice for the meeting (2026-09-06). Otherwise it reads 'open' for every race in this window. It is not a live market-state signal - /v1/racing/results carries the settled status (interim, final, abandoned)."},"scratchings":{"items":{"type":"object"},"type":"array","title":"Scratchings","description":"Scratched runners for this race: {name, number, barrier, scratched_at, late, emergency}. `scratched_at` (UTC) is the book's own stamp where it sends one (Ladbrokes, Neds, BetRight) and Racing Australia's official scratching sheet otherwise (AU thoroughbreds, read two-hourly); absent when neither knows. `late` marks a late scratching, `emergency` an emergency that did not gain a start. Merged across every book that reports one. Greyhound cards also carry box-vacancy placeholders here (a literal '....' or 'Vacant Box'), which are the reporting book naming an empty box rather than a withdrawn runner; match on `number` and treat a name from that set as a vacant box."},"market_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Status","description":"Betting-market state (OPEN, SUSPENDED, CLOSED). Reported by PointsBet, which sends CLOSED when it shuts its book at the jump, and by the Betfair Exchange, which is withheld from customer responses pending a data licence — so a value last written by the exchange reads null. Most races are quoted by both: the exchange holds the state until about two minutes before the jump, PointsBet reports the close."},"inplay":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Inplay","description":"True once the market is in-play. Exchange-only — bookmakers do not run racing in-play — so it carries the same licence hold as `market_status` and reads null for customers."},"market_closed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Closed At","description":"UTC time at which the market was FIRST seen shut — the book stopped taking bets on this race. Poll-observed: this is the first poll that saw the state, not the exact instant, so it lands up to one polling pass after it, and passes are most frequent in the minutes before the jump. Set once: a market that flickers SUSPENDED -> OPEN -> SUSPENDED keeps the FIRST suspension. TWO SOURCES report market state. PointsBet closes its book at the real jump — measured 2026-09-03 over 9 live races followed across their jump, 0.2 to 1.4 minutes AFTER the advertised start on AU/NZ cards — and the Betfair Exchange reports it while it quotes the market, which is until about two minutes before the jump. `market_state_source` names the one that produced this value. Exchange-sourced stamps are withheld from customer plans, so a race seen shut only by the exchange reads null. Bookmaker-sourced stamps are served to everybody. Null also means no feed observed a transition — it never means the market stayed open."},"inplay_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Inplay At","description":"UTC time at which the race was FIRST seen in-play — the actual off, as opposed to `start_time`, which is the ADVERTISED off and moves. Same poll-observed, set-once semantics as `market_closed_at`, and withheld on the same source rule. Reported by the Betfair Exchange only: bookmakers do not run racing in-play, so a bookmaker feed says when it CLOSED its book, not when the race went. For a race the exchange has stopped quoting, `market_closed_at` from a bookmaker is the closest measured instant to the off."},"market_state_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market State Source","description":"Which feed's market state produced `market_closed_at` / `inplay_at` on this race — 'pointsbetau' for the bookmaker, 'betfair_ex_au' for the exchange. Set once, alongside the first stamp. Null when no transition has been observed, and null for customer plans whenever the stamps themselves are withheld, so a non-null value here is always the provenance of values you can see."}},"type":"object","required":["race_id","venue","race_number","category","start_time","country"],"title":"RaceEventOut","example":{"category":"horse","country":"AU","market_closed_at":"2026-06-07T04:11:12Z","market_state_source":"pointsbetau","market_status":"CLOSED","race_id":"race_123","race_number":4,"scratchings":[{"barrier":"9","name":"Soul Of Spain","number":9}],"start_time":"2026-06-07T04:10:00Z","status":"open","venue":"Eagle Farm"}},"RaceHeadOut":{"properties":{"race_id":{"type":"string","title":"Race Id"},"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue"},"race_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Race Number"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"start_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Time"},"places_paid":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Places Paid"},"status":{"type":"string","title":"Status","description":"upcoming · jumped · resulted."},"source":{"type":"string","title":"Source","description":"live (read from the open market) or closes (read from the stored last observation before the jump)."}},"type":"object","required":["race_id","status","source"],"title":"RaceHeadOut"},"RaceResultOut":{"properties":{"race_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Id","description":"Race id, joinable to /v1/racing/price-history and (while the race is still open) /v1/racing/next-to-go. Null if the race cannot be resolved in the price-history store."},"coverage_from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coverage From","description":"Start time of the earliest result currently retained."},"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue"},"venue_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue Id"},"venue_canonical":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue Canonical"},"venue_site":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue Site"},"race_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Race Number"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"start_time":{"type":"string","title":"Start Time"},"race_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Name"},"distance_m":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Distance M"},"track_condition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track Condition"},"going":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Going","description":"Track condition word, normalised: Good, Soft, Heavy, Firm, Fast, Synthetic. Parsed from `track_condition` with the same helper /v1/racing/horses/form uses, so the two surfaces agree. Null when `track_condition` is null or unparseable; `track_condition` remains the raw string exactly as the source published it."},"going_rating":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Going Rating","description":"Numeric going rating 1-12 (the 4 in 'Good (4)'), or null. NULL IS THE MAJORITY CASE, not an exception for two rare words: most stored conditions are a bare word carrying no number at all. Measured 2026-09-10 across all 5,579 stored results, a bare 'Good' is 3,954 rows (71%) and yields null, so a rating is present on roughly 29% of rows — greyhound and other non-thoroughbred meetings publish an unrated word as a rule. A rating is never guessed from the word ('Synthetic', 67 rows, and 'Firm', 0 rows, never carry one). Separately, 205 rows store the four-character STRING 'None' in `track_condition`: `going` and `going_rating` are both null on those, while `track_condition` still serves that literal string, because `track_condition` is the raw source value passed through untouched."},"weather":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Weather"},"status":{"type":"string","title":"Status","description":"interim | final | abandoned. `interim` until the placings have been stable for 30 minutes, then `final`. `abandoned` (2026-09-06) is a race that did NOT run: Racing Australia's official Abandoned & Transferred Meetings notice for that venue and date, applied to every race of the meeting — placings are empty and runners null. Settle only against `final`; treat `abandoned` as a void for settlement purposes."},"status_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Note","description":"Only on `abandoned` rows: RA's own word for the non-run — \"abandoned\", \"postponed\", or \"transferred to <venue>\". A postponed or transferred meeting did not run at THIS venue on THIS date; if it ran elsewhere or later it appears as its own result."},"placings":{"items":{"type":"object"},"type":"array","title":"Placings","description":"The placegetters: {position, number, name, win_price, place_price, sp, sp_note, horse_ref}. `win_price` and `place_price` are the SETTLING BOOKMAKER'S FIXED prices. `horse_ref` (added 2026-09-21) is the same `pe:<name>` horse identity the `runners` entries carry, derived at serve time from the name on the placing and present on every placing — null off thoroughbred. It is the key that joins this placing to the same horse on another meeting; `runner_ref` on the matching `runners` entry joins the rows about this race and will not, because Racing Australia issues a new code for the same horse each day. `sp` is the OFFICIAL STARTING PRICE as a decimal return on a $1 stake (4.6 means $4.60), null where it is unknown — never 0. It is the settling market's own price, not our consensus and not a bookmaker's fixed price: thoroughbred SP comes from the Racing Australia results table, greyhound SP from our own collection of published results (Victorian tracks via FastTrack, every other state via the national results pages, both from 2026-09-13), and HARNESS SP IS ALWAYS NULL — no source we hold publishes it. `sp_note` carries Racing Australia's favouritism marker on thoroughbred runners ('F' favourite, 'EF' equal favourite) and is null everywhere else. Coverage RE-MEASURED 2026-09-08 over the whole retained store, counting only rows whose `status` is `ran` (a barrier scratching is not a starter and its price is not in this denominator): 88.6% of thoroughbred starters carry `sp` (2,105 of 2,375); on the greyhound races collected since 2026-09-13, 2,054 of 2,077 Victorian starters do (98.9%) and 4233 of 4596 of the other states' starters do (92.1%), both measured 2026-09-20. The Racing Australia block that held thoroughbred SP at 0% when this was last measured on 2026-09-03 has lifted and the collector has been landing meetings since. A null `sp` is unknown, never zero — RA prints no SP at all on some rows. On placings, `sp` is copied from the matching `runners` entry when the response is built, so the two can never disagree. On a race with no `runners` payload the `sp` and `sp_note` KEYS ARE ABSENT from each placing — not present and null. The copy step is what creates them and it is skipped when there is nothing to copy from, so a schema-validating consumer sees a different object shape, not a null: read them with `.get()`. The keys are deliberately NOT normalised in, because adding them here would change the shipped payload of every COMPLETE race too, and nothing may move under a customer mid-integration. `field_status` tells you which case you are in before you look."},"runners":{"anyOf":[{"items":{"type":"object"},"type":"array"},{"type":"null"}],"title":"Runners","description":"One entry per runner: {number, box, name, position, status, abnormal, margin, time_s, dead_heat}. Presence in this list is NOT proof the runner started. `status` is what says so, and `status` is what you settle on. ⚠️ COVERAGE. Read `field_status` on the row FIRST — it is the per-race answer, and it is what separates a field that has not been filled yet from one that never will be. Two different facts live here. (1) WHETHER THERE IS A FIELD AT ALL. Measured 2026-09-10 across the whole retained store — 5,577 results, 2,902 of them serving `runners: null`: AU greyhound carried a field on 2,360 of 3,219 races, AU thoroughbred on 315 of 1,104, and AU harness, NZ thoroughbred and NZ harness on none, as no collector covered those three codes then. Since 2026-10-04 TAB New Zealand fills them (`field_source: tabnz`, hourly, seven-day window) and has replaced the board copies described next wherever it could match the race; /v1/racing/results/coverage gives the current share per day, code and venue. FROM 2026-09-29 harness and NZ fields come from the pre-race board (origin: board): driver, trainer, barrier, form; positions only for placegetters; no times, SP or margins. Such a field is copied from the live board when the result is first written, before the board is purged 10 minutes after the jump, so a race whose result landed after the purge still has none. On these entries `\"origin\": \"board\"` is set on every entry, `box` holds the barrier (as on thoroughbred entries), `jockey` is the harness DRIVER, `weight_kg` and `form` are what the board showed, `status` is `scratched` for a runner the race's scratchings or `deductions` name and `ran` otherwise, and `position` is set for placegetters ONLY — a null `position` on an `origin: board` entry means 'not a placegetter, or not known', never 'did not finish'. Inside the two supported codes it is not a clean ramp either: each is filled by a scheduled pass, and where that pass cannot match a race with certainty it writes nothing rather than another race's field, so past the fill window 104 of 2,201 AU greyhound races (4.7%) and 44 of 288 AU thoroughbred races (15.3%) keep `runners: null` for good — `field_status: unavailable`, and polling them will not help. `runners` is JSON null in every one of these cases and has never once been an empty array (0 empty arrays in the whole store, measured the same day), so `runners is None` is a safe test and an empty field is not a shape you have to handle. (2) THE SHAPE OF A FIELD THAT IS PRESENT, which is where the 2026-09-08 date belongs. An AU thoroughbred field WRITTEN from 2026-09-08 carries every starter plus every runner Racing Australia printed as scratched; one written before that date carries STARTERS ONLY, because the collector dropped RA's scratched rows and a stored field is never rewritten. Measured 2026-09-08, all 244 thoroughbred fields stored at that point were the old shape and 198 of them publish a `deductions` row for a horse absent from `runners`. So on an older race `deductions` is the list of scratchings and `runners` is the starters; do not read a short `runners` array as a small field. Greyhound fields have carried the whole field since greyhound collection began on the 2026-08-21 Sydney card — but 859 AU greyhound races carry no field at all, which is question (1) and not this one. `position` is the finishing position for every runner that completed the race — not just the placegetters — and is null whenever there is none: the runner did not start, or it started and did not finish (except on an `origin: board` entry, which carries placegetters' positions only — see above). `status` is ran | scratched | late_scratching | reserve (an emergency that never got a start). `abnormal` is the official reason cell, passed through VERBATIM from the source — on AU thoroughbred it is Racing Australia's own Finish cell whenever that cell is not a number. THERE IS NO CLOSED LIST OF CODES and you should not write a match statement that assumes one: the source invents them, and a code nobody here has seen before will reach you unchanged rather than be dropped or renamed. Everything stored to date, with counts as at 2026-09-14: Fell (101), TailedOff (32), SB (25), PulledUp (8), FF (1, Rosehill R4 on 2026-09-12), LP (1). Treat `abnormal` as the authoritative explanation for a missing `position`, and treat an unfamiliar value as a did-not-finish unless `status` says otherwise — which is exactly what this API does: only codes PROVEN to mean the runner never left the barrier flip `status` to `scratched`, and that list currently holds one entry, 'SB'. Anything else stays `ran`, deliberately, because misreading a starter as a non-starter turns a losing bet into a refund. It carries TWO families that settle in OPPOSITE directions, so read it with `status` and never alone. (1) A DID-NOT-FINISH reason on a row whose `status` is `ran` — Fell, PulledUp, Disqualified, TailedOff, StayedInBox, FF and LP are the ones seen so far: the runner started and simply has no finishing position, and a bet on it lost. (2) A DID-NOT-START reason on a row whose `status` is `scratched` — 'SB', Racing Australia's code for scratched at the barrier: the runner was taken out of the market before the jump, so `position` is null, it is in no `placings` entry, and bets on it were refunded while the rest of the race was deducted. An 'SB' row may still carry `sp` — the last price Racing Australia printed for it, kept rather than blanked because it is a real market fact about the withdrawn runner. It is NOT the basis of the deduction, and you must never derive one from the other: `sp` comes from RA's results table while a deduction is struck by the settling bookmaker, and they do not agree. Measured 2026-09-08 across all five stored 'SB' rows that carry a price, 1/`sp` equals the published win deduction on NONE of them — Varejao, Sunshine Coast R4 2026-09-06, saddlecloth 5, withdrawn about 2 minutes 20 before the jump: `sp` 3.70, so 1/3.70 is 0.27 in the dollar, against a published `deductions` row of 0.18 win and 0.15 place; Dubai Dancer, Warrnambool R5: 1/5.50 is 0.18 against a published 0.10. Settle from `deductions`, which is authoritative. None of the five carries a `position` or a `margin`. Telling a barrier scratching from one made days earlier is CODE-SPECIFIC, and the two collectors do not spell it the same way — do not write one rule across categories. On AU thoroughbred (Racing Australia) both are `status: scratched`, and `abnormal` is the discriminator ONLY in one direction: a barrier scratching carries 'SB'; every other scratching carries `abnormal` null. Both carry their saddlecloth `number`, both carry `box` null (the runner has no barrier it jumped from — RA prints 0 there and we do not pass that sentinel on), and an ordinary scratching carries `sp` null while an 'SB' may carry one. So `abnormal == 'SB'` identifies a barrier scratching; `abnormal` null tells you only that it was not one, not how early it happened — use `deductions[].scratched_at` for that. On greyhound (our own collection since 2026-09-13: FastTrack for Victorian tracks, the national results pages for every other state) `abnormal` is null on every scratching and `status` carries three values: `ran`, `scratched` (merged in from our own `deductions`, so it never says how late), and `reserve` for a rug 9/10 reserve that never got a box. `late_scratching` was the retired interim feed's greyhound-only value — a thoroughbred barrier scratching does NOT use it — and it is no longer emitted; `scratched` does not carry identical meaning in the two codes. `deductions` carries `scratched_at` for either, and is the one cross-code way to see how close to the jump a runner came out. `dead_heat` is true on each runner sharing a position. IDENTITY. `runner_ref` (`ra:<code>`) is the code Racing Australia published for this runner on race day. It is the same value on the closing-line, price-path and markets archives, on the live board on race day, and on /v1/racing/acceptances once that day's field has been read, so it joins this runner across every endpoint for this race. Racing Australia issues a new code for the same horse each day, so it is not a horse identity: the same horse carries a different `ra:` code at each meeting. `horse_ref` (`pe:<name>`) is: the registered name folded to letters and digits (trailing country parenthetical off), the same value on results, the live board, the closing-line and price-path archives, /v1/racing/horses/form, /v1/racing/horses/runs and the backfill, and it is the key to join a horse across meetings. It is derived at serve time from the name on the row, never stored, so every historical row carries it; null on greyhound and harness runners. On GREYHOUNDS `runner_ref` IS the dog: `grv:<id>` for Victorian tracks as FastTrack numbers the dog and `grsa:<id>` elsewhere as the national results pages number it — per-dog registry ids that DO persist across meetings, in two namespaces, so a dog racing in both is two refs until mapped. `runner_ref` is null while a code's registry source is not yet wired. `trainer` and `jockey` come from the same official results source and may be null where that source is not yet live for the code. GREYHOUND-ONLY, ADDITIVE FROM 2026-09-21, five measured columns the results pages print. `first_split_s` is the first sectional in seconds exactly as the source page printed it, null where the page printed none. `second_split_s` is the second sectional, and is null on EVERY Victorian run because FastTrack publishes one split and not two. `weight_kg` is the greyhound's race-day weight in kilograms, null where the page carries no weight column at all — Western Australian meetings carry none (0 of 36 runner rows sampled 2026-09-21). `pir` is position-in-running text passed through as a STRING and never a number (\"21\" means 2nd at the first mark then 1st at the second): FastTrack's own column on Victorian runs and, since 2026-09-29, the national page's In Race Positions card elsewhere, which only some tracks print (see /v1/racing/greyhounds/form `pir` for the per-state rates) — the marks are each site's own, so compare PIR within one source; null where the page printed none. `weather` (added 2026-09-29) is the meeting's weather in the national page's OWN words (\"Fine\", \"Light showers\", \"Overcast\"), stamped on every entry of the field exactly as `grade` is; null on Victorian (FastTrack) fields, which print none, and ABSENT on fields written before it existed — measured 2026-09-29, 170 of the 2,012 stored greyhound fields carry it, from the 2026-09-27 card on. It is not on /v1/racing/greyhounds/form. `grade` is the race's grade in the SOURCE'S OWN WORDS — \"M\", \"4/5\" or \"FFA\" on the national pages, a race type such as \"Tier 3 - Maiden\" on FastTrack — and the two vocabularies are NOT mapped onto each other, so do not compare a Victorian grade with an interstate one. ⚠️ A STORED FIELD IS NEVER REWRITTEN, so a greyhound field written before 2026-09-21 carries none of these five keys AT ALL — absent, not null — and no thoroughbred field carries them in any era: read them with `.get()`. Measured on 46 freshly read race pages on 2026-09-21, a grade appeared on 46/46 races and 362/362 runner rows, a first split on 288/362 and a weight on 295/362; the rows without them are overwhelmingly rug 9/10 reserves, which never started and for which the page prints nothing. `sp` is the OFFICIAL STARTING PRICE as a decimal return on a $1 stake (4.6 means $4.60), null where it is unknown — never 0. It is the settling market's own price, not our consensus and not a bookmaker's fixed price: thoroughbred SP comes from the Racing Australia results table, greyhound SP from our own collection of published results (Victorian tracks via FastTrack, every other state via the national results pages, both from 2026-09-13), and HARNESS SP IS ALWAYS NULL — no source we hold publishes it. `sp_note` carries Racing Australia's favouritism marker on thoroughbred runners ('F' favourite, 'EF' equal favourite) and is null everywhere else. Coverage RE-MEASURED 2026-09-08 over the whole retained store, counting only rows whose `status` is `ran` (a barrier scratching is not a starter and its price is not in this denominator): 88.6% of thoroughbred starters carry `sp` (2,105 of 2,375); on the greyhound races collected since 2026-09-13, 2,054 of 2,077 Victorian starters do (98.9%) and 4233 of 4596 of the other states' starters do (92.1%), both measured 2026-09-20. The Racing Australia block that held thoroughbred SP at 0% when this was last measured on 2026-09-03 has lifted and the collector has been landing meetings since. A null `sp` is unknown, never zero — RA prints no SP at all on some rows. Null `runners` means the full field is not available for this race — never an empty field; every AU and NZ code carries one (AU thoroughbred, AU greyhound, AU harness, NZ harness, NZ thoroughbred). `placings` remains the source for the settling bookmaker's own fixed prices, and now also carries `sp` copied from here."},"field_status":{"type":"string","title":"Field Status","description":"Why `runners` is or is not there, per race — the field a caller was missing when `runners: null` was the only signal and it collapsed several different futures into one. SIX values. `complete`: `runners` is present. It is set whenever a field is stored, including on an abandoned race that unexpectedly carries one, so `field_status == 'complete'` is a safe gate before parsing. An AU harness result settled between 2026-09-29 and 2026-10-04 that the TAB New Zealand pass could not match may still hold the PRE-RACE BOARD copy — every entry `origin: board`, `field_source` null, positions for placegetters only and no times, SP or margins — so check `origin` before treating it like a collected field. On GB and IE thoroughbred (from 2026-10-06) `complete` means Betway's field (`field_source: betway_uk`, every entry `origin: betway_uk`): every runner with its starting price in `sp` and `sp_fraction` and a `position` wherever Betway publishes one — every finisher on the British races measured; on the Irish ones Betway ranks the first four first and the rest of the order within about 25 minutes of the off, which the interim result picks up, so until then an unplaced Irish starter is `ran` with `position` null — and non-runners as `scratched`; no margins, times, barriers, jockeys or dividends, and no Rule 4 figure on the deductions. On AU harness, NZ harness and NZ thoroughbred (from 2026-10-04) `complete` means the TAB New Zealand field (`field_source: tabnz`, every entry `origin: tabnz`): the full finishing order with margins in lengths, each runner's time (NZ racing only — TAB New Zealand publishes no times on Australian races), barrier and gate, driver in `jockey` (gallops add `weight_kg`, the total carried), trainer, scratchings with `scratched_at`, and TAB New Zealand's own pool win and place dividends on the placegetters; `sp` is null because TAB NZ publishes no official starting price. A board copy written before 2026-10-04 is replaced by the first pass. On ZA thoroughbred (from 2026-10-15) `complete` means the Ambernet settled race card (`field_source: easybet_za`; each entry's `origin` is the book it was read from, easybet_za or topbet_za): every runner, with a `position` on the runners the book ranks (the placed runners; unplaced starters stay `ran` with `position` null), jockey and trainer, non-runners as `scratched`; no margins, times, SP or dividends, and no deduction figure on the deductions. `pending`: a collector exists for this code and the pass that NORMALLY fills this race has not been yet; `field_note` names the UTC instant by which it will have run. `overdue`: that pass has been and gone without writing a field, but the collector still selects this row as a candidate and keeps re-attempting it — this state is NOT terminal. For AU greyhound the hourly pass for its state (FastTrack for Victoria, the national results pages elsewhere) re-reads every field-less race inside its seven-day window, and so does the hourly TAB New Zealand pass for AU harness, NZ harness and NZ thoroughbred; for AU thoroughbred a bounded re-fetch reaches six days but is run by hand rather than on a schedule. Most rows that reach `overdue` stay null, so do not build on a field arriving — but do not write the race off either, and `field_note` gives the horizon. `unavailable`: no scheduled pass still selects this row. Its collector's own candidate window closed at the instant in `field_note` and no field was written — the pass either never matched the race or refused an ambiguous match, which it does rather than write another race's field. `unsupported`: no collector exists for this category/country. Since 2026-10-04 that is only a foreign race that appears because an AU book listed the meeting: AU harness, NZ harness and NZ thoroughbred (1,254 of the 5,577 stored results on 2026-09-10) all gained their TAB New Zealand collector that day — and no amount of waiting will produce one. `not_applicable`: the race did not run (abandoned, postponed or transferred) and carries no field, so there is nothing to collect. TRANSITIONS, stated explicitly because acting on the wrong one costs you data. The scheduled progression is pending -> overdue -> unavailable, one direction only. It is bounded at every step by a collector's own window rather than by hope: a race can sit matched-and-never-resulted forever (The Gardens, 2026-09-05, 12 races), and an open-ended `pending` would have you polling it forever. Any of those three becomes `complete` the moment a field is written, AND THAT INCLUDES `unavailable` — nothing un-writes a `runners` array, so a collector re-run by hand, a widened window or a late upstream publication can still fill a row we have stopped scheduling passes for. So `unavailable` means 'nothing further is scheduled', never 'this can never arrive'; if you reconcile, re-read rather than caching it as final. `unsupported` and `not_applicable` are properties of the racing code and of the race itself, not of a schedule, and do not change on their own.","default":"unsupported"},"field_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Field Source","description":"Which collector owns `runners` for this race: `racing_australia` (AU thoroughbred), `fasttrack` (AU greyhound at Victorian tracks) and `grsa` (AU greyhound everywhere else), the last two our own collection, or null where no collector exists — including a `complete` AU harness row settled between 2026-09-29 and 2026-10-04 whose `runners` is still the pre-race board copy (`origin: board`), which no collector owns; `tabnz` (TAB New Zealand) owns AU harness, NZ harness and NZ thoroughbred; `betway_uk` (Betway's settlement feed) owns GB and IE thoroughbred from 2026-10-06; `pointsbetau` (PointsBet's settled race card) owns GB and IE greyhound from 2026-10-12. Set on `pending`, `overdue`, `unavailable` and `complete` rows alike — it names who WOULD fill the field, not who did."},"field_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Field Note","description":"Prose for the non-`complete` states: which pass fills this code, when its normal pass for this race has been and gone, how long the collector keeps re-attempting the row, or that the code has no source at all. Null on `complete`. Written for a human reading a response — branch on `field_status`, never on this text."},"deductions":{"items":{"type":"object"},"type":"array","title":"Deductions","description":"Scratched runners and the deduction applied to bets already struck. `win` and `place` are fractions of the dollar in the range 0-1 (0.34 = 34 cents in the dollar), as published by the settling bookmaker."},"dividends":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Dividends","description":"Dividends declared for the race. `straight` holds one row per placegetter per product: {position, number, name, market ('WIN' or 'PLC'), product, amount}. `exotics` holds one row per exotic product: {type, code ('QN', 'EX', 'TF', 'FF'), selection, product, amount}, where `selection` is the winning saddlecloth numbers in finishing order, e.g. '5,1'. Every `amount` is the return on a $1 stake, the same convention as the odds fields — a 'FIXED' straight dividend equals that runner's own settled fixed price. `product` names the settling pool: 'FIXED' is the bookmaker's own price, the rest are tote products (SP, MIDDIV, VIC, RD+, BT+SP). A product that declared no dividend is omitted rather than returned as 0. Exotic dividends are only declared once the race fully settles, a few minutes after the placings, so a just-resulted race can carry `straight` while `exotics` is still empty — it fills in on a later poll. `multi_race` (from 2026-09-29) holds the meeting-level products — Quaddie, Giant 6, Daily Double and the like — on the race that is the product's FINAL leg and on no other race: one row per declared pool, {code (the book's own, e.g. 'RC-QUADDIE'), type ('Quaddie'), product ('VIC', 'BT', ...), selection (the winning saddlecloth in each leg, in leg order, e.g. '1,4,2,11'), amount (the return on $1), legs (the race numbers the product spans, e.g. [7, 8, 9, 10])}. Each row is published as the settling book declares it: Rosehill R10 on 2026-09-26 closed the Quaddie (R7-R10, $128.40 VIC and BT), a Giant 6 (R5-R10, $544.40 VIC) and a second Giant 6 row whose selection names five legs, at $1.00. A result stored before 2026-09-29 carries NO `multi_race` key at all — read it with `.get()` — and on a newer row it is a list, empty when the race closes no product. `straight_types` is the book's raw list of product codes, retained for backwards compatibility. `note` (rare, from 2026-10-03) is a plain-text caveat on the dividends, absent on almost every race: e.g. Randwick R1 on 2026-10-03, where the settling book paid on the first horse past the post and a protest then changed the official placings, so these dividends name a different winner from `placings`. Read it with `.get()`."},"sectionals":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Sectionals","description":"NSW harness sectional times, or null. Present on AU harness races at NSW tracks once Harness Racing NSW has published the meeting's Sectional Times sheet, usually a few days after the meeting, and it has been matched to this race by venue, Sydney date, race number and the winner's saddlecloth. Race level: `gross_time_s` (the winning time), `mile_rate_s`, `lead_time_s` (null where none was taken), `quarters_s` (the leader's four quarters, in order), `distance_m`. `runners` lists every starter by saddlecloth `number` with `name`, `position` (the official placing; dead heats share it and set `dead_heat`), `margin_m` (metres behind the winner), `time_s` (the runner's own time), `behind_800m_m` and `wide_800m`, `behind_400m_m` and `wide_400m` (metres behind the leader and how many horses wide, at the 800 m and the 400 m), and `q3_s` and `q4_s` (the runner's own third and fourth quarters). `source` is `hrnsw` and `source_url` is the sheet it was read from. Times are in seconds and distances in metres. Read it with `.get()`."},"resulted_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resulted At"},"market_closed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Closed At","description":"UTC time at which the market was FIRST seen shut — the book stopped taking bets on this race. Poll-observed: this is the first poll that saw the state, not the exact instant, so it lands up to one polling pass after it, and passes are most frequent in the minutes before the jump. Set once: a market that flickers SUSPENDED -> OPEN -> SUSPENDED keeps the FIRST suspension. TWO SOURCES report market state. PointsBet closes its book at the real jump — measured 2026-09-03 over 9 live races followed across their jump, 0.2 to 1.4 minutes AFTER the advertised start on AU/NZ cards — and the Betfair Exchange reports it while it quotes the market, which is until about two minutes before the jump. `market_state_source` names the one that produced this value. Exchange-sourced stamps are withheld from customer plans, so a race seen shut only by the exchange reads null. Bookmaker-sourced stamps are served to everybody. Null also means no feed observed a transition — it never means the market stayed open. Copied from the live race row when the result is written; that row is deleted 10 minutes after the jump and results land a median 4.9 minutes after the start, so a late result keeps null."},"inplay_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Inplay At","description":"UTC time at which the race was FIRST seen in-play — the actual off, as opposed to `start_time`, which is the ADVERTISED off and moves. Same poll-observed, set-once semantics as `market_closed_at`, and withheld on the same source rule. Reported by the Betfair Exchange only: bookmakers do not run racing in-play, so a bookmaker feed says when it CLOSED its book, not when the race went. For a race the exchange has stopped quoting, `market_closed_at` from a bookmaker is the closest measured instant to the off. Copied from the live race row when the result is written; that row is deleted 10 minutes after the jump and results land a median 4.9 minutes after the start, so a late result keeps null."},"market_state_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market State Source","description":"Which feed's market state produced `market_closed_at` / `inplay_at` on this race — 'pointsbetau' for the bookmaker, 'betfair_ex_au' for the exchange. Set once, alongside the first stamp. Null when no transition has been observed, and null for customer plans whenever the stamps themselves are withheld, so a non-null value here is always the provenance of values you can see."}},"type":"object","required":["venue","race_number","category","country","start_time","status"],"title":"RaceResultOut","example":{"category":"horse","country":"AU","coverage_from":"2026-08-15T05:53:00Z","deductions":[{"name":"Acapulco Girl","number":5,"place":0.0,"scratched_at":"2026-08-14T07:06:29Z","win":0.01}],"distance_m":1200,"dividends":{"exotics":[{"amount":14.4,"code":"QN","product":"VIC","selection":"6,3","type":"Quinella"},{"amount":77.7,"code":"TF","product":"VIC","selection":"6,3,9","type":"Trifecta"}],"straight":[{"amount":4.6,"market":"WIN","name":"Grinzinger Champ","number":6,"position":1,"product":"FIXED"},{"amount":5.0,"market":"WIN","name":"Grinzinger Champ","number":6,"position":1,"product":"MIDDIV"},{"amount":1.8,"market":"PLC","name":"Grinzinger Champ","number":6,"position":1,"product":"FIXED"}],"straight_types":["FIXED","MIDDIV"]},"field_source":"racing_australia","field_status":"complete","going":"Good","going_rating":4,"market_closed_at":"2026-08-15T05:54:31Z","market_state_source":"pointsbetau","placings":[{"horse_ref":"pe:grinzingerchamp","name":"Grinzinger Champ","number":6,"place_price":1.8,"position":1,"sp":4.4,"sp_note":"F","win_price":4.6}],"race_id":"race_123","race_name":"TAB ROSEBUD","race_number":8,"resulted_at":"2026-08-15T06:01:12Z","runners":[{"box":3,"dead_heat":false,"horse_ref":"pe:grinzingerchamp","jockey":"R Maloney","name":"Grinzinger Champ","number":6,"position":1,"runner_ref":"ra:AB12CD34","sp":4.4,"sp_note":"F","status":"ran","trainer":"T Gollan"},{"dead_heat":false,"horse_ref":"pe:acapulcogirl","name":"Acapulco Girl","number":5,"status":"scratched"}],"start_time":"2026-08-15T05:53:00Z","status":"final","track_condition":"Good (4)","venue":"Doomben","weather":"Light Rain"}},"RacingBestOddsOut":{"properties":{"race_id":{"type":"string","title":"Race Id"},"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue"},"race_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Race Number"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"start_time":{"type":"string","title":"Start Time"},"race_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Name"},"track_condition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track Condition"},"market_percentage":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Market Percentage"},"books_compared":{"type":"integer","title":"Books Compared"},"scratchings":{"items":{"type":"object"},"type":"array","title":"Scratchings","default":[]},"data_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Data Age Seconds","description":"Age in seconds of the OLDEST bookmaker quote behind this race."},"freshest_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Freshest Age Seconds"},"refresh_tier":{"type":"string","title":"Refresh Tier","description":"Which refresh rule this race is on, and therefore what the race-level `stale` means. `live` = inside 3 hours of the jump: books re-price it on every 8-20s poll, and `stale` fires past 120 seconds. `card` = further out: books re-price it about every 15 minutes so a full day fits inside their rate limits, and `stale` fires past 1800 seconds. Every published quote carries ITS OWN `refresh_tier`, and it can be `card` on a `live` race: Betr's upstream rate-limits us, so that book stops re-pricing every poll 30 minutes before the jump rather than 3 hours (PlayUp, on a 6-requests-a-minute budget, at 6 minutes), and its quotes between there and 3 hours are refreshed every 5-15 minutes, published as `card`, and judged against 1800 seconds (since 2026-09-07; before that they were marked stale). A book's tier is never tighter than its race's. `age_seconds` is the TRUE age in every case — only the threshold changes.","default":"live"},"stale":{"type":"boolean","title":"Stale","default":false},"stale_bookmakers":{"items":{"type":"string"},"type":"array","title":"Stale Bookmakers"},"cached":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Cached"},"cache_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cache Age Seconds"},"runners":{"items":{"type":"object"},"type":"array","title":"Runners"}},"type":"object","required":["race_id","venue","race_number","category","country","start_time","books_compared","runners"],"title":"RacingBestOddsOut","example":{"books_compared":11,"cache_age_seconds":0,"cached":false,"category":"horse","country":"AU","data_age_seconds":41,"freshest_age_seconds":3,"market_percentage":106.4,"race_id":"race_123","race_name":"TAB ROSEBUD","race_number":8,"refresh_tier":"live","runners":[{"barrier":3,"best_place":{"age_seconds":4,"betslip_url":"https://www.tab.com.au/racing/2026-09-03/REDCLIFFE/RED/R/4?runner=5","bookmaker":"tab","last_update":"2026-08-15T05:44:21Z","price":2.15,"source_url":"https://www.tab.com.au/racing/2026-09-03/REDCLIFFE/RED/R/4","stale":false},"best_tote":{"bookmaker":"betright","price":7.1,"product":"RD+"},"best_win":{"age_seconds":6,"bookmaker":"betright","last_update":"2026-08-15T05:44:19Z","price":6.5,"refresh_tier":"live","source_url":"https://...","stale":false},"books_quoting":11,"books_quoting_place":8,"books_quoting_win":11,"horse_ref":"pe:chillygirl","jockey":"J Mcdonald","name":"Chilly Girl","number":7,"price_spread_pct":18.2,"trainer":"C Waller"}],"scratchings":[],"stale":false,"stale_bookmakers":[],"start_time":"2026-08-15T05:45:00Z","track_condition":"Good (4)","venue":"Rosehill"}},"RacingChangesOut":{"properties":{"server_time":{"type":"string","title":"Server Time","description":"Send this back as `since` on your next poll. Deliberately set back 30 seconds from the server clock so no in-flight write is skipped — the feed is at-least-once, not exactly-once."},"races":{"items":{"$ref":"#/components/schemas/ChangedRaceOut"},"type":"array","title":"Races"}},"type":"object","required":["server_time"],"title":"RacingChangesOut"},"ResetPasswordIn":{"properties":{"token":{"type":"string","title":"Token"},"new_password":{"type":"string","title":"New Password"}},"type":"object","required":["token","new_password"],"title":"ResetPasswordIn"},"RestatementOut":{"properties":{"id":{"type":"integer","title":"Id"},"dataset":{"type":"string","title":"Dataset"},"period":{"type":"string","title":"Period"},"period_kind":{"type":"string","title":"Period Kind","description":"day or month."},"at":{"type":"string","title":"At","description":"When the changed file was published (UTC)."},"reason":{"type":"string","title":"Reason","description":"orphan_merge: the archive folded a duplicate race record onto the race that was run, which moved or removed rows in this period. regeneration: any other change, most often late results or late closing lines inside the three-night settle window."},"rows_removed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rows Removed","description":"Rows in the earlier file and not in this one."},"rows_changed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rows Changed","description":"Rows present in both whose values changed. Null where the dataset has no row key or the file was too large to diff; rows_removed and rows_added are then the net row delta."},"rows_added":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rows Added","description":"Rows in this file and not in the earlier one."},"sha256_before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha256 Before","description":"csv sha256 of the file replaced."},"sha256_after":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha256 After","description":"csv sha256 of the file that replaced it."},"detail":{"type":"object","title":"Detail","description":"Row counts before and after, the diff method, and for orphan_merge the merges counted.","default":{}}},"type":"object","required":["id","dataset","period","period_kind","at","reason"],"title":"RestatementOut"},"RestatementsOut":{"properties":{"restatements":{"items":{"$ref":"#/components/schemas/RestatementOut"},"type":"array","title":"Restatements"},"count":{"type":"integer","title":"Count"},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","default":[]}},"type":"object","required":["restatements","count"],"title":"RestatementsOut","example":{"count":1,"notes":["Newest first. A restatement is recorded when a regenerated file's csv sha256 differs from the file it replaces."],"restatements":[{"at":"2026-10-13T15:31:07Z","dataset":"closing_lines","detail":{"merges":1,"method":"keyed","previous_generated_at":"2026-10-12T15:30:44Z","rows_after":31221,"rows_before":31230,"store_rows_deleted":9,"store_rows_repointed":0,"within_settle_window":true},"id":412,"period":"2026-10-11","period_kind":"day","reason":"orphan_merge","rows_added":0,"rows_changed":0,"rows_removed":9,"sha256_after":"3f1c92…","sha256_before":"a07e5c…"}]}},"ResultsCoverageOut":{"properties":{"as_of":{"type":"string","title":"As Of","description":"When these counts were computed (UTC)."},"days":{"type":"integer","title":"Days","description":"How many Sydney days the window spans, ending today."},"from_day":{"type":"string","title":"From Day","description":"First Sydney date counted (inclusive)."},"to_day":{"type":"string","title":"To Day","description":"Last Sydney date counted — today in Sydney, so it is a PART day and its races are mostly `pending`."},"coverage_from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coverage From","description":"Start time of the earliest result retained anywhere in the store, the same value /v1/racing/results puts on every row."},"field_floors":{"items":{"type":"object"},"type":"array","title":"Field Floors","description":"Machine-readable twin of `field_scope` (2026-10-15): one row per country and category that has a full-field collector — `country`, `category`, `field_source` (the machine value `field_source` carries on /v1/racing/results), `source_name` and `from` (the earliest Sydney card a stored field exists for, measured). Codes outside this list hold placegetters only."},"races":{"type":"integer","title":"Races","description":"Every stored result in the window, all codes and all states. races == complete + pending + overdue + unavailable + unsupported + not_applicable, always."},"complete":{"type":"integer","title":"Complete","description":"Races carrying a `runners` field."},"pending":{"type":"integer","title":"Pending","description":"A collector exists and the pass that normally fills the race has not been yet."},"overdue":{"type":"integer","title":"Overdue","description":"That pass has been and gone with no field written, but the collector still re-attempts the row. NOT a terminal state — see `field_status` on /v1/racing/results for the horizons."},"unavailable":{"type":"integer","title":"Unavailable","description":"Past the collector's own retry horizon: no scheduled pass still selects the row."},"unsupported":{"type":"integer","title":"Unsupported","description":"No collector has ever existed for the code."},"not_applicable":{"type":"integer","title":"Not Applicable","description":"The race did not run (abandoned, postponed or transferred) and has no field — nothing to collect, not a gap."},"collectable":{"type":"integer","title":"Collectable","description":"races - unsupported - not_applicable: the races in this window that could carry a field at all. It is the denominator of `supported_complete_pct`, published so that a null percentage is readable as 0-of-0 rather than guessed at."},"complete_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Complete Pct","description":"complete / races as a percentage, across the whole window including codes that can never carry a field. NULL — not 0.0 — when `races` is 0. An empty window has no coverage figure, and 0.0 there is a fabricated number that reads as total failure: a `days=1` window called between Sydney midnight and the day's first result is empty on most days (the median first result of a Sydney day lands at 10:50 local), so this is an ordinary case and not a corner."},"supported_complete_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Supported Complete Pct","description":"complete / collectable: the figure to read if you only care about the codes we collect fields for. The raw `complete_pct` is dragged down by stored races in codes that had no collector before 2026-10-04 (AU harness and both NZ codes, 1,254 rows as at 2026-09-10). An AU harness result still carrying the 2026-09-29..10-04 pre-race board copy (`origin: board`) counts as complete AND collectable here, so on such a window separate them with `by_category`. NULL — not 0.0 — when `collectable` is 0, which happens on an empty window or one holding only foreign races. If you alert on this figure, treat null as 'no data' and never as 'zero coverage'."},"by_category":{"items":{"type":"object"},"type":"array","title":"By Category","description":"One row per country + category over the window: {country, category, field_source, races, complete, pending, unavailable, unsupported, not_applicable}."},"by_day":{"items":{"type":"object"},"type":"array","title":"By Day","description":"One row per Sydney day + country + category, newest first, with the same counts. This is the day-by-day answer to 'is yesterday finished yet'."},"venues_incomplete":{"items":{"type":"object"},"type":"array","title":"Venues Incomplete","description":"Venue-days still missing a field in a code we DO collect: {day, venue, venue_id, category, country, settled} plus every counter above (races, complete, pending, overdue, unavailable, unsupported, not_applicable), which sum to `races` exactly. Ordered worst first: most `unavailable`, then the biggest outstanding shortfall, then oldest day. `settled` is true when nothing on that venue-day can still fill (no `pending`, no `overdue`) — that is the difference between a genuine terminal gap and a card simply still in progress, and without it a day mid-fill outranks a real one. A venue-day is listed when complete + not_applicable < races: an abandoned card has nothing to collect and is NOT a shortfall. A venue-day whose only gap is `unsupported` is deliberately absent — it would be most of the list and it is already counted above. Capped at 200 entries; `venues_incomplete_truncated` says when the cap bit."},"venues_incomplete_truncated":{"type":"boolean","title":"Venues Incomplete Truncated","default":false},"field_scope":{"type":"string","title":"Field Scope","description":"Prose: which codes are collected, from when, and what the never-collected codes are."},"archive_races_in_window":{"type":"integer","title":"Archive Races In Window","description":"AU and NZ closing-line archive races that started in this window at least 3 hours ago.","default":0},"archive_races_without_result":{"type":"integer","title":"Archive Races Without Result","description":"Of archive_races_in_window, races with no result row at all: a price history with nothing to settle it against. Not counted in `races`, which counts stored results only.","default":0}},"type":"object","required":["as_of","days","from_day","to_day","races","complete","pending","overdue","unavailable","unsupported","not_applicable","collectable","by_category","by_day","venues_incomplete","field_scope"],"title":"ResultsCoverageOut","example":{"as_of":"2026-09-10T03:40:12Z","by_category":[{"category":"greyhound","complete":220,"country":"AU","field_source":"fasttrack","not_applicable":0,"overdue":8,"pending":6,"races":742,"unavailable":0,"unsupported":508},{"category":"horse","complete":108,"country":"AU","field_source":"racing_australia","not_applicable":0,"overdue":21,"pending":2,"races":137,"unavailable":6,"unsupported":0},{"category":"harness","complete":0,"country":"AU","not_applicable":0,"overdue":0,"pending":0,"races":131,"unavailable":0,"unsupported":131}],"by_day":[{"category":"greyhound","complete":5,"country":"AU","day":"2026-09-10","field_source":"fasttrack","not_applicable":0,"overdue":0,"pending":1,"races":19,"unavailable":0,"unsupported":13},{"category":"horse","complete":42,"country":"AU","day":"2026-09-09","field_source":"racing_australia","not_applicable":0,"overdue":7,"pending":0,"races":49,"unavailable":0,"unsupported":0}],"collectable":879,"complete":806,"complete_pct":75.54,"coverage_from":"2026-08-15T05:53:00Z","days":7,"field_scope":"Full fields are collected for AU thoroughbred (Racing Australia, from the 2026-08-15 card, the first day of results) and AU greyhound (FastTrack for Victoria, the national results pages elsewhere, from 2026-09-13), NZ harness (TAB New Zealand, from the 2026-09-30 Sydney card), NZ thoroughbred (TAB New Zealand, from the 2026-09-30 card) and AU harness (TAB New Zealand, from the 2026-09-27 card). No pass reaches back beyond its own floor.","from_day":"2026-09-04","not_applicable":0,"overdue":52,"pending":21,"races":1067,"supported_complete_pct":91.7,"to_day":"2026-09-10","unavailable":0,"unsupported":188,"venues_incomplete":[{"category":"horse","complete":0,"country":"AU","day":"2026-09-09","not_applicable":0,"overdue":7,"pending":0,"races":7,"settled":false,"unavailable":0,"unsupported":0,"venue":"Randwick","venue_id":"randwick"}],"venues_incomplete_truncated":false}},"RevealIn":{"properties":{"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password"}},"type":"object","title":"RevealIn"},"RewardIn":{"properties":{"reward_type":{"type":"string","title":"Reward Type","description":"`cash` or `credits`."},"payout_email":{"anyOf":[{"type":"string","maxLength":254},{"type":"null"}],"title":"Payout Email","description":"Where a cash remittance should go, if it differs from the account email. Ignored for `credits`."},"code":{"anyOf":[{"type":"string","maxLength":24},{"type":"null"}],"title":"Code","description":"Requested referral code, first call only. 3-24 characters, lowercase letters, digits and hyphens. Ignored once a code has been issued — a code that changes is a dead link on somebody's blog."}},"type":"object","required":["reward_type"],"title":"RewardIn"},"RunPIR":{"properties":{"at_m":{"type":"integer","title":"At M","description":"Metres from the finish, e.g. 800."},"position":{"type":"integer","title":"Position"}},"type":"object","required":["at_m","position"],"title":"RunPIR"},"RunPlacegetter":{"properties":{"position":{"type":"integer","title":"Position"},"name":{"type":"string","title":"Name"},"runner_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Ref","description":"That horse's ra: code from that meeting. Racing Australia issues a new code each day, so to look the horse up here pass its name."},"weight_kg":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Weight Kg"}},"type":"object","required":["position","name"],"title":"RunPlacegetter"},"RunsSourceBlock":{"properties":{"feed":{"type":"string","title":"Feed","description":"Where the data comes from: our own collection of published AU thoroughbred results (Racing Australia's results pages, read hourly by our results collector), flattened to one row per horse per race and refreshed hourly. Not Racing Australia's form page — that is /v1/racing/horses/form.","default":"puntersedge"},"region":{"type":"string","title":"Region","default":"AU"},"sport":{"type":"string","title":"Sport","default":"thoroughbred"},"coverage_from":{"type":"string","title":"Coverage From","description":"The first complete card in the store. A horse's record here starts here — it is not a career.","default":"2026-09-01"},"coverage_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coverage To","description":"Sydney date of the latest race in the store."},"collectors":{"items":{"type":"string"},"type":"array","title":"Collectors","description":"Which of our collectors can contribute a run. AU thoroughbred results come from one: the Racing Australia results pages.","default":["racing_australia","tabtouch"]},"refreshed_hourly":{"type":"boolean","title":"Refreshed Hourly","description":"The store is rebuilt from our own results at :59 each hour, after the results pass.","default":true}},"type":"object","title":"RunsSourceBlock"},"ScanOut":{"properties":{"as_of":{"type":"string","title":"As Of"},"window_minutes":{"type":"integer","title":"Window Minutes"},"races_scanned":{"type":"integer","title":"Races Scanned"},"filters":{"type":"object","title":"Filters"},"method":{"type":"object","title":"Method"},"results":{"items":{"type":"object"},"type":"array","title":"Results","description":"Runners that cleared the filters, largest move first, each with its race header.","default":[]},"cached":{"type":"boolean","title":"Cached","default":false}},"type":"object","required":["as_of","window_minutes","races_scanned","filters","method"],"title":"ScanOut"},"SetPasswordIn":{"properties":{"new_password":{"type":"string","title":"New Password"},"current_password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Password"}},"type":"object","required":["new_password"],"title":"SetPasswordIn"},"SignupRequest":{"properties":{"email":{"type":"string","title":"Email"},"plan":{"type":"string","title":"Plan","default":"free"},"label":{"type":"string","title":"Label","default":"My Key"},"use_case":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Use Case"},"use_case_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Use Case Note"},"referral":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Referral"},"referral_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Referral Code"},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"},"billing":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Billing"},"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password"},"terms_accepted":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Terms Accepted"},"marketing_opt_in":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Marketing Opt In"}},"type":"object","required":["email"],"title":"SignupRequest"},"SourceBlock":{"properties":{"feed":{"type":"string","title":"Feed","description":"Where the data comes from: our own collection of published AU greyhound results, Victorian tracks via FastTrack (runner_ref grv:) and every other state via the national results pages (runner_ref grsa:). Runs on or after `results_from` are races we settled ourselves; earlier runs are the same pages read back by our history collector. Refreshed hourly. On a British or Irish race (/v1/racing/greyhounds/field, from 2026-10-12) `region` reads GB-IE and the runs are our own GB/IE results from PointsBet's settled race card, read live.","default":"puntersedge"},"region":{"type":"string","title":"Region","default":"AU"},"sport":{"type":"string","title":"Sport","default":"greyhound"},"coverage_from":{"type":"string","title":"Coverage From","description":"Sydney date of the OLDEST run in the store. A dog's record here starts here.","default":"2026-09-13"},"results_from":{"type":"string","title":"Results From","description":"Sydney date our own settled results begin. A run on or after it has `origin: results` and a `result_id` that joins to /v1/racing/results; an earlier one has `origin: history` and does not.","default":"2026-09-13"},"coverage_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coverage To","description":"Sydney date of the latest race in the store."},"collectors":{"items":{"type":"string"},"type":"array","title":"Collectors","description":"Which of our collectors can contribute a run: `fasttrack` = Victorian tracks, `grsa` = every other state.","default":["fasttrack","grsa"]},"refreshed_hourly":{"type":"boolean","title":"Refreshed Hourly","description":"The store is rebuilt from our results at :58 each hour, after both collectors have written.","default":true}},"type":"object","title":"SourceBlock"},"SplitRecord":{"properties":{"starts":{"type":"integer","title":"Starts"},"wins":{"type":"integer","title":"Wins"},"seconds":{"type":"integer","title":"Seconds"},"thirds":{"type":"integer","title":"Thirds"}},"type":"object","required":["starts","wins","seconds","thirds"],"title":"SplitRecord"},"SportOddsEventOut":{"properties":{"id":{"type":"string","title":"Id"},"sport_key":{"type":"string","title":"Sport Key"},"sport_title":{"type":"string","title":"Sport Title"},"commence_time":{"type":"string","title":"Commence Time"},"home_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Team"},"away_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Away Team"},"competition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Competition"},"bookmakers":{"items":{"type":"object"},"type":"array","title":"Bookmakers"},"odds_format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Odds Format"},"fetched_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fetched At"},"data_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Data Age Seconds"},"freshest_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Freshest Age Seconds"},"stale":{"type":"boolean","title":"Stale","default":false},"stale_bookmakers":{"items":{"type":"string"},"type":"array","title":"Stale Bookmakers","default":[]},"cached":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Cached"},"cache_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cache Age Seconds"},"canonical_event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Canonical Event Id"},"data_quality":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Data Quality"}},"type":"object","required":["id","sport_key","sport_title","commence_time","home_team","away_team","bookmakers"],"title":"SportOddsEventOut","example":{"away_team":"Collingwood","bookmakers":[{"key":"sportsbet","markets":[{"key":"h2h","outcomes":[{"name":"Brisbane Lions","price":1.9}]}],"title":"Sportsbet"}],"commence_time":"2026-06-07T09:30:00Z","data_age_seconds":15,"fetched_at":"2026-06-07T09:29:45+00:00","home_team":"Brisbane Lions","id":"event_123","sport_key":"afl","sport_title":"AFL","stale":false}},"SportOut":{"properties":{"key":{"type":"string","title":"Key"},"title":{"type":"string","title":"Title"},"group":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Group"},"active":{"type":"boolean","title":"Active"},"cached":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Cached"},"cache_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cache Age Seconds"},"canonical_event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Canonical Event Id"},"data_quality":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Data Quality"}},"type":"object","required":["key","title","group","active"],"title":"SportOut","example":{"active":true,"group":"Australian Rules","key":"afl","title":"AFL"}},"SportsArbOut":{"properties":{"event_id":{"type":"string","title":"Event Id"},"sport_key":{"type":"string","title":"Sport Key"},"home_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Team"},"away_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Away Team"},"commence_time":{"type":"string","title":"Commence Time"},"is_arb":{"type":"boolean","title":"Is Arb"},"arb_pct":{"type":"number","title":"Arb Pct"},"optimal_stakes":{"items":{"type":"object"},"type":"array","title":"Optimal Stakes"},"max_overlay_pct":{"type":"number","title":"Max Overlay Pct"},"selections":{"items":{"type":"object"},"type":"array","title":"Selections"}},"type":"object","required":["event_id","sport_key","home_team","away_team","commence_time","is_arb","arb_pct","optimal_stakes","max_overlay_pct","selections"],"title":"SportsArbOut","example":{"arb_pct":2.4,"away_team":"Magpies","commence_time":"2026-06-07T09:30:00Z","event_id":"event_123","home_team":"Lions","is_arb":true,"max_overlay_pct":5.1,"optimal_stakes":[],"selections":[{"best_bookmaker":"sportsbet","best_price":2.05,"name":"Lions"}],"sport_key":"afl"}},"TeamKeyIn":{"properties":{"label":{"type":"string","maxLength":80,"minLength":1,"title":"Label","description":"What this key is for — 'results worker', 'Jo's laptop'. Shown in the pool view and in your usage breakdown."},"monthly_cap":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Monthly Cap","description":"Optional. The most credits THIS key may spend per month, hard, inside the account's shared pool — between 1 and the plan's own monthly allowance. Omit it (or send null) and the key may spend the whole pool. Caps reset with the pool on the 1st."}},"type":"object","required":["label"],"title":"TeamKeyIn"},"TeamKeyListOut":{"properties":{"plan":{"type":"string","title":"Plan"},"allowance":{"type":"integer","title":"Allowance"},"keys_in_use":{"type":"integer","title":"Keys In Use"},"pool_credits_used":{"type":"integer","title":"Pool Credits Used"},"pool_credits_limit":{"type":"integer","title":"Pool Credits Limit"},"pool_credits_remaining":{"type":"integer","title":"Pool Credits Remaining"},"max_monthly_cap":{"type":"integer","title":"Max Monthly Cap","default":0},"primary":{"$ref":"#/components/schemas/TeamKeyOut"},"team":{"items":{"$ref":"#/components/schemas/TeamKeyOut"},"type":"array","title":"Team"}},"type":"object","required":["plan","allowance","keys_in_use","pool_credits_used","pool_credits_limit","pool_credits_remaining","primary","team"],"title":"TeamKeyListOut"},"TeamKeyMintOut":{"properties":{"key_id":{"type":"string","title":"Key Id"},"api_key":{"type":"string","title":"Api Key","description":"The team key. Shown ONCE; it is stored hashed and cannot be recovered — only revoked and re-minted."},"label":{"type":"string","title":"Label"},"plan":{"type":"string","title":"Plan"},"allowance":{"type":"integer","title":"Allowance","description":"Keys this account may hold, primary included."},"keys_in_use":{"type":"integer","title":"Keys In Use"},"pool_credits_limit":{"type":"integer","title":"Pool Credits Limit"},"pool_credits_used":{"type":"integer","title":"Pool Credits Used"},"monthly_cap":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Monthly Cap"},"max_monthly_cap":{"type":"integer","title":"Max Monthly Cap","description":"The largest cap this plan accepts — its own monthly allowance.","default":0}},"type":"object","required":["key_id","api_key","label","plan","allowance","keys_in_use","pool_credits_limit","pool_credits_used"],"title":"TeamKeyMintOut"},"TeamKeyOut":{"properties":{"key_id":{"type":"string","title":"Key Id"},"key_hint":{"type":"string","title":"Key Hint"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"plan":{"type":"string","title":"Plan"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used At"},"period_calls":{"type":"integer","title":"Period Calls","default":0},"period_credits":{"type":"integer","title":"Period Credits","default":0},"monthly_cap":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Monthly Cap","description":"This key's hard per-month credit cap, or null for no cap."},"key_credits_used":{"type":"integer","title":"Key Credits Used","description":"Credits charged against this key's own counter this period. That is the number the cap is checked against; it stays 0 on an uncapped team key, whose spending is only ever charged to the pool row. Use period_credits for what an uncapped key has spent.","default":0}},"type":"object","required":["key_id","key_hint","label","plan"],"title":"TeamKeyOut"},"TeamKeyPatchIn":{"properties":{"label":{"anyOf":[{"type":"string","maxLength":80,"minLength":1},{"type":"null"}],"title":"Label","description":"New label for this key. Omit to leave it alone."},"monthly_cap":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Monthly Cap","description":"Optional. The most credits THIS key may spend per month, hard, inside the account's shared pool — between 1 and the plan's own monthly allowance. Omit it (or send null) and the key may spend the whole pool. Caps reset with the pool on the 1st. Send null to REMOVE an existing cap; omit the field entirely to leave the cap as it is."}},"type":"object","title":"TeamKeyPatchIn","description":"Both fields are optional; only the ones PRESENT in the body are written.\n\n`monthly_cap: null` is meaningful (remove the cap) and must not read as \"unchanged\", so\nthe handler asks model_fields_set which keys the caller actually sent rather than\ninferring intent from a None that pydantic would produce either way."},"TrackConditionsOut":{"properties":{"date":{"type":"string","title":"Date","description":"Meeting day, Australia/Sydney calendar date."},"meetings":{"items":{"type":"object"},"type":"array","title":"Meetings"},"note":{"type":"string","title":"Note"},"coverage_from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coverage From","description":"The first meeting day (Australia/Sydney) this store holds anything for -- the earlier of the change log's first day and Racing Australia's meeting block's first day, computed from the tables on every call, never a constant. Any `date` from here up to three days ahead is served; an earlier one is a free 422 naming this same day. Null only if the store could not be read, and then the pre-2026-09-29 rule (7 days back) applies."},"official_block_from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Official Block From","description":"The first meeting day Racing Australia's own meeting block (the `racing_australia` values in `current`: rail, track type, penetrometer, the official rating) is held for. Earlier than this the change log alone answers: until 2026-09-29 the block was deleted three days after each meeting, so a day before this date is NOT one on which RA published nothing -- its block is simply gone. Computed like coverage_from."}},"type":"object","required":["date","meetings","note"],"title":"TrackConditionsOut","example":{"coverage_from":"2026-09-07","date":"2026-09-10","meetings":[{"category":"horse","changes":[{"category":"horse","going":"Good","going_rating":4,"observed_at":"2026-09-09T04:16:29Z","rail":"True","source":"racing_australia","track_condition":"Good 4","weather":"Windy"},{"category":"horse","going":"Good","going_rating":4,"observed_at":"2026-09-10T02:36:58Z","prev_going":"Good","prev_going_rating":3,"prev_track_condition":"Good3","race_id":"race_123","source":"betgold","track_condition":"Good4"}],"current":{"as_of":"2026-09-10T02:17:24Z","field_as_of":{"penetrometer":"2026-09-10T02:17:24Z","rail":"2026-09-10T02:17:24Z","track_condition":"2026-09-10T02:17:24Z","track_type":"2026-09-10T02:17:24Z","weather":"2026-09-10T02:17:24Z"},"going":"Good","going_rating":4,"penetrometer":"5.63","rail":"True","source":"racing_australia","sources":{"penetrometer":"racing_australia","rail":"racing_australia","track_condition":"racing_australia","track_type":"racing_australia","weather":"racing_australia"},"track_condition":"Good 4","track_type":"Turf","weather":"Windy"},"merged_from":["picklebet park warwick","warwick"],"official_venue_norm":"picklebet park warwick","venue":"picklebet park warwick","venue_id":"picklebet-park-warwick","venue_norm":"picklebet park warwick","venue_site":"picklebet-park-warwick"}],"note":"…","official_block_from":"2026-09-07"}},"UpstreamBlock":{"properties":{"state":{"type":"string","title":"State","description":"`ok` when Racing Australia is answering form reads, `walled` when it is serving its bot-protection challenge to every fresh read and nothing can be collected."},"walled_since":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Walled Since","description":"UTC time of the first refusal in the current streak, null when `state` is `ok`. This is the field that tells you a stalled queue is hours old rather than minutes."},"walled_hours":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Walled Hours","description":"How long the current streak has run, in hours, null when `state` is `ok`."},"note":{"type":"string","title":"Note","description":"One sentence saying what that means for this job, in plain words."}},"type":"object","required":["state","note"],"title":"UpstreamBlock"},"UsageOut":{"properties":{"plan":{"type":"string","title":"Plan"},"account_email_masked":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Email Masked"},"password_set":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Password Set"},"billing_attached":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Billing Attached"},"credits_used":{"type":"integer","title":"Credits Used"},"credits_limit":{"type":"integer","title":"Credits Limit"},"credits_remaining":{"type":"integer","title":"Credits Remaining"},"reset_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reset At","description":"When this key's credit counter was LAST set to zero, in UTC, written without an offset (read it as ending in Z). Despite the name this is the start of the current period, not the next reset; it equals period_start, which is the clearer field to use."},"period_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period Start","description":"Start of the current credit period, in UTC, written without an offset (read it as ending in Z). For a key created part-way through a month this is the moment the key was created; after that it is the monthly reset."},"next_reset_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Reset At","description":"00:00 UTC on the 1st of next month, written without an offset (read it as ending in Z). Calculated, not stored: the reset job starts at that time and finishes a few seconds later, so a request in the first seconds of a month can still count against the old period."},"burn_rate_per_day":{"type":"number","title":"Burn Rate Per Day","default":0.0},"projected_month_end_credits":{"type":"integer","title":"Projected Month End Credits","default":0},"projected_exhaustion_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Projected Exhaustion Date"},"warning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Warning"},"upgrade":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Upgrade"},"all_time":{"type":"object","title":"All Time","default":{}},"all_time_by_endpoint":{"items":{"type":"object"},"type":"array","title":"All Time By Endpoint","default":[]},"usage_by_endpoint":{"items":{"type":"object"},"type":"array","title":"Usage By Endpoint","default":[]},"recent_activity":{"items":{"type":"object"},"type":"array","title":"Recent Activity","default":[]},"usage_by_endpoint_period":{"items":{"type":"object"},"type":"array","title":"Usage By Endpoint Period","default":[]},"recent_activity_period":{"items":{"type":"object"},"type":"array","title":"Recent Activity Period","default":[]},"pool":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Pool"},"overage":{"type":"object","title":"Overage","default":{}},"credits_remaining_with_overage":{"type":"integer","title":"Credits Remaining With Overage","default":0},"plans":{"additionalProperties":{"type":"object"},"type":"object","title":"Plans","default":{}}},"type":"object","required":["plan","credits_used","credits_limit","credits_remaining","reset_at"],"title":"UsageOut","example":{"all_time":{"calls":310,"credits":402,"endpoints":1},"all_time_by_endpoint":[{"calls":310,"credits":402,"endpoint":"/v1/sports"}],"burn_rate_per_day":6.0,"credits_limit":1000,"credits_remaining":958,"credits_used":42,"next_reset_at":"2026-08-01T00:00:00","period_start":"2026-07-01T00:00:00","plan":"free","plans":{"free":{"credits_per_month":500,"price_aud":0}},"projected_month_end_credits":186,"recent_activity":[{"credits":1,"endpoint":"/v1/sports","ts":"2026-06-07T02:20:00"}],"recent_activity_period":[{"credits":1,"endpoint":"/v1/sports","ts":"2026-06-07T02:20:00"}],"reset_at":"2026-07-01T00:00:00","upgrade":{"checkout":{"body":{"email":"you@example.com","plan":"hobby"},"endpoint":"https://api.puntersedge.online/v1/signup","method":"POST"},"covers_projected_need":true,"credits_per_month":7500,"price_aud":9,"recommended_plan":"hobby","sized_for_credits_per_month":186,"url":"https://puntersedge.online/api/pricing?plan=hobby&utm_source=api_usage"},"usage_by_endpoint":[{"calls":310,"credits":402,"endpoint":"/v1/sports"}],"usage_by_endpoint_period":[{"calls":42,"credits":42,"endpoint":"/v1/sports"}]}},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VenueOut":{"properties":{"venue_id":{"type":"string","title":"Venue Id"},"venue_canonical":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue Canonical"},"venue_site":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue Site"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"categories":{"items":{"type":"string"},"type":"array","title":"Categories"},"spellings":{"items":{"type":"string"},"type":"array","title":"Spellings"}},"type":"object","required":["venue_id","venue_canonical","venue_site","country","categories","spellings"],"title":"VenueOut","example":{"categories":["horse"],"country":"AU","spellings":["Sandown - HILLSIDE"],"venue_canonical":"Sandown Hillside","venue_id":"sandown-hillside","venue_site":"sandown"}},"WebhookAttempt":{"properties":{"attempt":{"type":"integer","title":"Attempt"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"finished_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Finished At"},"status_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Status Code"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"latency_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Latency Ms"}},"type":"object","required":["attempt","started_at","finished_at","status_code","error","latency_ms"],"title":"WebhookAttempt"},"WebhookCreate":{"properties":{"url":{"type":"string","title":"Url","description":"HTTPS endpoint that receives the POST, on port 443 or 8443. Plain http://, a host that does not resolve, and a host that resolves to a private, loopback, link-local or other non-public address are rejected with 422. At most 5 subscriptions per API key (409 beyond that).","examples":["https://example.com/hooks/puntersedge"]},"events":{"items":{"type":"string"},"type":"array","title":"Events","description":"Events to subscribe to. Three events have live producers: `arb.opportunity` (a cross-book arb opened), `race.odds_open` and `race.result`. `race.odds_open`: the first bookmaker prices for a race just landed — fires once per race, so you can fetch the card on push instead of schedule-polling. Since the early-market lanes of 2026-09-01 it fires HOURS ahead, not minutes: measured over the 48h to 2026-09-03 11:00 UTC, the first price landed a median 10.1h before the jump on AU thoroughbreds, 5.3h on greyhounds, 6.8h on harness - typically overnight or early morning for the day's card, from one or two early books; the full board fills inside about an hour of the jump. `race.result` (from 2026-10-13): a race's result just went `final` — fires once per race, carrying the race (`race_id`, `result_id`, venue, race number, start, country, category), the placings ({position, name, number, horse_ref}), the `dividends` where the settling source declares them, and `source`. `race_id` is the id race.odds_open and /v1/racing/next-to-go carried for the race, and is null on the rare result we could not link to a priced race (10 of 2,038 final results in the 7 days to 2026-10-12); `result_id` is always set. A result goes final once its placings have been stable for 30 minutes, and the event follows within about five minutes of that: measured over the 3 days to 2026-10-12, results went final a median 36 minutes after the advertised start on AU and NZ races (p90 39), 38 on British and Irish thoroughbreds, and about 4 on British and Irish greyhounds, whose source publishes the result already final. It is the same row /v1/racing/results serves, so settle on it exactly as you would on that endpoint. `odds.change` is delivered solely by POST /v1/webhooks/{id}/test. `market.suspend` was withdrawn on 2026-10-13 — it never had a producer — and is now refused with 422.","examples":[["arb.opportunity"],["race.odds_open"],["race.odds_open","race.result"]]},"min_edge_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Edge Pct","description":"Only deliver arb.opportunity when the theoretical edge is at least this percentage. Omit for every opportunity.","examples":[1.5]},"sports":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Sports","description":"Only deliver events for these sports. Sport keys as returned by GET /v1/sports; racing events (arbs, race.odds_open and race.result) arrive as racing_horse, racing_greyhound or racing_harness. Omit for all sports.","examples":[["nrl","afl"],["racing_horse","racing_greyhound"]]}},"type":"object","required":["url"],"title":"WebhookCreate"},"WebhookDeliveryResponse":{"properties":{"id":{"type":"string","title":"Id"},"webhook_id":{"type":"string","title":"Webhook Id"},"event_type":{"type":"string","title":"Event Type"},"payload":{"anyOf":[{},{"type":"null"}],"title":"Payload"},"status":{"type":"string","title":"Status","description":"delivered (receiver answered 2xx) or failed (every attempt refused or the answer was final, see `error`). Rows written before 2026-09-03 read `pending`.","default":"pending"},"attempt_count":{"type":"integer","title":"Attempt Count","description":"POST attempts made for this row: at most 2, or at most 6 when the delivery queue is enabled.","default":0},"status_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Status Code"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"delivered_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Delivered At"},"attempts":{"anyOf":[{"items":{"$ref":"#/components/schemas/WebhookAttempt"},"type":"array"},{"type":"null"}],"title":"Attempts","description":"Per-attempt detail, only with `include=attempts`."}},"type":"object","required":["id","webhook_id","event_type","payload","status_code","error","delivered_at"],"title":"WebhookDeliveryResponse"},"WebhookReplayResponse":{"properties":{"id":{"type":"string","title":"Id"},"requeued":{"type":"integer","title":"Requeued","description":"Events queued for re-delivery by this call."},"since":{"type":"string","title":"Since"},"status":{"type":"string","title":"Status"},"max_per_call":{"type":"integer","title":"Max Per Call"}},"type":"object","required":["id","requeued","since","status","max_per_call"],"title":"WebhookReplayResponse"},"WebhookResponse":{"properties":{"id":{"type":"string","title":"Id"},"url":{"type":"string","title":"Url"},"events":{"items":{"type":"string"},"type":"array","title":"Events"},"min_edge_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Edge Pct"},"sports":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Sports"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"last_delivery_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Delivery At"},"failure_count":{"type":"integer","title":"Failure Count"},"secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Secret"},"delivery_entitled":{"type":"boolean","title":"Delivery Entitled","default":true},"delivery_paused_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Delivery Paused Reason"},"paused_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Paused At"},"paused_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Paused Reason"}},"type":"object","required":["id","url","events","is_active","created_at","last_delivery_at","failure_count"],"title":"WebhookResponse"},"WebhookResumeResponse":{"properties":{"id":{"type":"string","title":"Id"},"resumed":{"type":"boolean","title":"Resumed"},"probe_event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Probe Event Id","description":"The `odds.change` probe queued to confirm the endpoint answers."}},"type":"object","required":["id","resumed"],"title":"WebhookResumeResponse"},"WebhookRotateResponse":{"properties":{"id":{"type":"string","title":"Id"},"secret":{"type":"string","title":"Secret"},"previous_secret_expires_at":{"type":"string","title":"Previous Secret Expires At","description":"Until then every delivery is also signed with the previous secret."}},"type":"object","required":["id","secret","previous_secret_expires_at"],"title":"WebhookRotateResponse"},"WidgetIn":{"properties":{"label":{"type":"string","maxLength":80,"minLength":1,"title":"Label","description":"What this widget is for — 'homepage next-to-go', 'AFL page'. Shown in GET /v1/widgets."},"config":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Config","description":"Theme, links, allowed_origins, defaults, title, show_updated and max_fills_per_hour. Every field is optional; the response carries the normalised document that is actually stored."}},"type":"object","required":["label"],"title":"WidgetIn"},"WidgetPatchIn":{"properties":{"label":{"anyOf":[{"type":"string","maxLength":80,"minLength":1},{"type":"null"}],"title":"Label"},"config":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Config","description":"FULL replacement after validation, not a merge — send the whole config back with your change in it, or omit the field to leave the stored one alone."}},"type":"object","title":"WidgetPatchIn"},"_ReauthIn":{"properties":{"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password"}},"type":"object","title":"_ReauthIn"},"Problem":{"type":"object","properties":{"type":{"type":"string","format":"uri","description":"Stable URI identifying the error class.","example":"https://puntersedge.online/errors/rate-limit"},"title":{"type":"string","description":"Short human-readable summary."},"status":{"type":"integer","description":"HTTP status code.","example":429},"detail":{"description":"Explanation of this occurrence. A string for most errors; an array of field errors for 422; an object for 429."}},"required":["type","title","status"]}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"**Get a free key** at [puntersedge.online/api](https://puntersedge.online/api).\n\nPaste your key in the **Authorize** dialog (lock icon, top right of Swagger UI) to try all endpoints live."}}},"tags":[{"name":"Racing","description":"Racing endpoints for next-to-go races, race events, runners and bookmaker prices. Every country is served by default — pass country=AU (or AU,NZ) to filter; a race outside AU/NZ carries a median of one bookmaker. AU carries thoroughbred, harness and greyhound; NZ carries thoroughbred and harness — there are no New Zealand greyhounds in the feed."},{"name":"Best Odds","description":"Best available price per selection, bookmaker comparison and arbitrage indicators."},{"name":"Sports","description":"Sports catalogue and market odds endpoints for AFL, NRL, NBA, tennis, cricket, rugby and more. Racing is NOT here — horse, harness and greyhound live under the Racing tag at /v1/racing/next-to-go and /v1/racing/events. There is no racing sport_key."},{"name":"Arb & Value","description":"Sports arbitrage scanners and overlay/value price comparison. Everything under this tag is SPORTS-ONLY and takes a sport_key — there is no racing arb scanner here. For cross-book racing value use GET /v1/racing/best-odds under the Racing tag: a market_percentage under 100 means the best prices across the Australian bookmakers beat the field."},{"name":"Account","description":"Authenticated usage, credit balance, connector health and developer key utilities. Subscribers manage billing here too — GET /v1/billing/portal returns a Stripe portal link for changing the card, pulling invoices or cancelling, authenticated by the API key itself, so there is no separate account to create. It costs no credits."},{"name":"Webhooks","description":"Subscribe to real-time events via HTTP webhook. Payloads are HMAC-SHA256 signed. Creating a subscription and sending a test fire require Plus, Business, Platform or Scale; a Standard or Starter key issued before 20 September 2026 keeps them, and legacy Pro and Growth keys qualify too. Free and Hobby get 403. Listing your own subscriptions, reading their delivery log and deleting them stay open on every plan, so a downgraded key can still see and stop a subscription it owns."},{"name":"Signup","description":"Developer signup and API key creation endpoints."}],"security":[{"ApiKeyAuth":[]}]}