# FormFav Racing API — Full Reference for AI Crawlers > Extended documentation for LLMs and AI agents. For the concise index see https://formfav.com/llms.txt --- ## Overview FormFav is a free-to-start JSON REST API for horse racing: race form, meetings and career stats are free (1,000 requests/day, no credit card, key in one step at https://formfav.com/get-api-key?cta=llms-full). It covers thoroughbred, harness, and greyhound racing across Australia, New Zealand, Hong Kong, the United Kingdom, USA, and Europe. Pro adds results, predictions and statistics; Premium adds live bookmaker prices and real-time results. It is a pull-based horse racing data feed. Base URL: https://api.formfav.com/v1 Authentication: X-API-Key header on every request OpenAPI spec: https://api.formfav.com/v1/openapi.json Interactive docs: https://api.formfav.com/v1/docs --- ## Authentication Every request requires an API key in the X-API-Key header. ``` X-API-Key: your_api_key_here ``` Get a free key in one step at https://formfav.com/get-api-key?cta=llms-full (sign in with Google, GitHub or email via Replit and the key dialog opens straight away) — no credit card required. --- ## Subscription Tiers ### Free (1,000 requests/day) - Race meetings list - Full race form: runner name, number, barrier, jockey, trainer, weight, form string - Form history: last 5 starts (form) and last 20 starts (last20Starts) - Career stats: overall, by track, distance, condition, track+distance, first-up, second-up - Win % and place % ### Pro (20,000 requests/day, 30-day free trial) - Everything in Free - Race results: finishing positions, margins, official starting price (batch-ingested every 2–3 hours) - ML win/place probability predictions - Jockey performance statistics - Trainer performance statistics - Track and barrier bias analysis - Runner career profiles - Speed maps and pace analysis - Form badges (contextual runner insights) ### Premium (100,000 requests/day) - Everything in Pro - Live win/place prices per bookmaker via GET /prices, and inline on GET /results as placings[].prices - Real-time results as races settle, with per-runner resultStage ("interim" | "correct_weight") while the live feed is ahead of the batch pipeline - Live scratchings, track condition and weather on GET /form ### Enterprise (unlimited requests) - Everything in Premium - Commercial use and republishing rights - Priority support (4-hour SLA) - Coming soon --- ## Endpoints Timezones — two distinct concepts, easy to conflate: - The `timezone` REQUEST parameter scopes which day `date` refers to (default Australia/Sydney). Passing timezone=America/New_York changes which meetings and races come back. It does NOT shift any time in the response. - The `timezone` RESPONSE field is the venue's own zone, supplied for display. - `startTime` in every response is always UTC, ISO 8601, with a literal Z suffix (e.g. "2026-07-25T02:30:00Z") — parsing on the Z is safe. To show a local post time, convert startTime into the response `timezone`. ### GET /form/meetings List race meetings for a given date. Parameters: - date (required): YYYY-MM-DD format, Australian Eastern time - race_code: "gallops" (default), "harness", "greyhounds" - country: "au" (default), "nz", "hk", "gb" - timezone: IANA timezone string (e.g. "Australia/Sydney") — scopes which day `date` means; does not shift response times Example: ``` curl -H "X-API-Key: YOUR_KEY" \ "https://api.formfav.com/v1/form/meetings?date=2026-04-04&race_code=gallops" ``` Response shape: ```json { "date": "2026-04-04", "raceType": "gallops", "meetings": [ { "track": "Randwick", "slug": "randwick", "state": "NSW", "country": "au" }, { "track": "Flemington", "slug": "flemington", "state": "VIC", "country": "au" } ] } ``` --- ### GET /form Full race form for a specific race, including all runners and career stats. Tip: call GET /form/meetings first to get the valid track slugs and race numbers for a date, then pass a slug and raceNumber from that response into the track and race parameters below. Requesting a track or race number that isn't running returns 404 — this is the most common cause of 404 errors. Parameters: - date (required): YYYY-MM-DD - track (required): venue slug (e.g. "randwick", "flemington") — use /form/venues to look up - race (required): race number (integer) - race_code: "gallops" (default), "harness", "greyhounds" - country: "au" (default), "nz", "hk", "gb" - timezone: IANA timezone string — scopes which day `date` means; does not shift response times Example: ``` curl -H "X-API-Key: YOUR_KEY" \ "https://api.formfav.com/v1/form?date=2026-04-04&track=randwick&race=3" ``` Response includes per-runner: - name, number, barrier, jockey, trainer, weight, claim, age, sex, sire, dam, country - form: short form string, last 5 starts (e.g. "32141" — digits are finishing positions, 0 means 10th or worse, X denotes a spell) - last20Starts: extended form string, last 20 starts, same encoding. `form` is its trailing 5 characters. Both are available on every tier. - Career stats breakdown: overall, by track, distance, condition, plus firstUp/secondUp spell stats - speedMap, classProfile, raceClassFit and decorators (form badges) — Pro tier Note: fields with no value are omitted from the JSON entirely, never returned as null. Pro-tier fields are absent on Free rather than null. Date range: 7 days in the past to 7 days in the future. --- ### GET /results Finishing positions and margins for completed races. Pro tier required. Takes the same parameters as GET /form, over the same 7-days-back to 7-days-forward window, so a race you fetched form for can be looked up with the identical query string. Parameters: - date (required): YYYY-MM-DD - track (required): venue slug - race (required): race number — omit on /results/meeting - race_code: "gallops" (default), "harness", "greyhounds" - country: "au" (default), "nz", "hk" - timezone: IANA timezone string — scopes which day `date` means; does not shift response times Market coverage: au (gallops, harness, greyhounds), nz (gallops, harness), hk (gallops). Any other market returns 400 — race form for those markets is still available via /form. Cadence is tier-dependent. On Free and Pro, results are ingested every 2–3 hours, not streamed. On Premium and Enterprise, results arrive as races settle: a runner placed by the live feed carries placings[].resultStage ("interim" while stewards are still to confirm, "correct_weight" once they have), and the field is dropped once the batch result lands — so an absent resultStage means settled, not unconfirmed. A race that exists but has no result yet returns 200 with resultStatus "pending", never 404 — do not poll in a tight loop. Race-level resultStatus: "final" (every unscratched runner resulted), "interim" (partial), "pending" (nothing yet), "abandoned" (no result will ever exist). Per-runner reading: two fields tell you everything. A runner with a position ran and finished in it, and carries a margin. A runner with scratched: true was withdrawn and never has a position. A runner with neither either did not finish or is not resulted yet — use the race-level resultStatus to tell those apart. There is no outcome field; earlier responses carried one and it has been removed. To find finishers use position != null; for placegetters use position <= 3. Dead heats share a position, so it is not unique within a race. Fields with no value are omitted from the JSON entirely, never returned as null — check for presence ('margin' in runner) rather than for null. Runners may also carry startingPrice: the official starting price as decimal odds from the racing body's published result, settled after the race. It is not a live or pre-race price and there is no price movement or per-bookmaker breakdown. Coverage is partial — the key is omitted for runners with no price held, so treat absence as unknown rather than as zero or an error. For live and per-bookmaker prices see GET /prices below; on Premium keys the same array also appears here as placings[].prices. Do not average startingPrice with a bookmaker price — they answer different questions. Example: ``` curl -H "X-API-Key: YOUR_KEY" \ "https://api.formfav.com/v1/results?date=2026-07-25&track=caulfield&race=1" curl -H "X-API-Key: YOUR_KEY" \ "https://api.formfav.com/v1/results/meeting?date=2026-07-25&track=caulfield" ``` Response shape: ```json { "date": "2026-07-25", "track": "Caulfield", "raceNumber": 1, "distance": "1200m", "condition": "Good 4", "startTime": "2026-07-25T02:30:00Z", "timezone": "Australia/Melbourne", "numberOfRunners": 10, "resultStatus": "final", "placings": [ { "number": 3, "name": "Custom", "jockey": "J. McDonald", "barrier": 4, "position": 1, "margin": "0.0", "scratched": false, "startingPrice": 3.4 } ] } ``` The /results/meeting variant wraps a races array in { date, track, slug, country, raceType, abandoned, races }. --- ### GET /prices Live win and place prices for every runner in a race, one entry per bookmaker and market type. Premium tier required — Free and Pro keys receive 403. Takes the same parameters as GET /form (date, track, race, race_code, country, timezone), so a race you fetch form or results for can be priced with the identical query string. Designed for polling: a deliberately small payload (number, name, scratched, prices) with no form or stats. Responses are cached for 5 seconds at the edge; poll every 5–10 seconds through the last minutes before the jump and as slowly as you like before that. Per-quote fields (runners[].prices[]): - bookmaker: which book is quoting, e.g. "tab", "ladbrokes" - marketType: "fixed" or "tote" — one book can quote both, as two entries - win, place: current decimal odds. OMITTED (not null) when the book is not pricing that market, and whenever stale is true - stale: always present. The quote is older than the freshness allowance (which tightens toward the jump). A stale quote is served with its live fields dropped and its historical fields kept - open, close, high, low: win-market fluctuation record; each optional - startingPrice: THIS bookmaker's settled SP — per book, not the official figure on /results - movements: win-price history, oldest first, most recent 20; empty means no movement is known, not that the price has not moved - capturedAt: when the source observed the quote (UTC) A race with no market up yet returns 200 with empty prices arrays, not 404. After the jump every quote reads stale with win/place withheld — that is correct, not a coverage gap. Example: ``` curl -H "X-API-Key: YOUR_KEY" \ "https://api.formfav.com/v1/prices?date=2026-08-25&track=Gloucester%20Park&race=9&race_code=harness&country=au" ``` Full reference: https://formfav.com/docs/endpoints/prices ### GET /predictions ML win/place probability predictions for a race. Pro tier required. Parameters: - date (required): YYYY-MM-DD - track (required): venue slug - race (required): race number - race_code: "gallops" (default), "harness", "greyhounds" - country: "au" (default), "nz", "hk", "gb" Note: winProb values across runners in a race sum to approximately 1.0. These are model probabilities, not arbitrary confidence scores. --- ### GET /form/venues Look up venue names and slugs. Use this to resolve "flemington" from "Flemington Racecourse". Parameters: - q: search query (partial name OK) - country: filter by country code - race_code: filter by race type --- ### GET /stats/jockey/{name} Jockey performance statistics by track. Pro tier required. Path param: name — jockey name (partial match supported, e.g. "james mcdonald") Parameters: - race_code: "gallops" (default), "harness" - window: rolling window in days (14–365) Returns: win %, place %, rides, wins, strike rate by track and overall. --- ### GET /stats/trainer/{name} Trainer performance statistics. Pro tier required. Path param: name — trainer name (partial match supported, e.g. "chris waller") Parameters: - race_code: "gallops" (default), "harness", "greyhounds" - window: rolling window in days (14–365) --- ### GET /stats/track-bias/{track} Barrier and surface bias analysis for a venue. Pro tier required. Path param: track — venue slug Parameters: - race_code: "gallops" (default), "harness", "greyhounds" - country: "au" (default), "nz", "hk", "gb" - window: rolling window in days (14–365) - condition: track condition filter ("Good", "Soft", "Heavy") --- ### GET /stats/runner/search Search for a horse or greyhound by name. Pro tier required. Parameters: - q (required): runner name, minimum 3 characters - race_code: "gallops" (default), "harness", "greyhounds" - country: country filter Returns: list of matching runners with IDs, country, race type. --- ### GET /stats/runner/{runner_id} Full career statistics for a specific runner. Pro tier required. Path param: runner_id — integer ID from search endpoint Parameters: - window: rolling window in days (14–365) Returns: career record, distance analysis, condition performance, spell/weight/barrier patterns, form trend. --- ## MCP Server FormFav exposes all endpoints as MCP (Model Context Protocol) tools, accessible to AI assistants with no code required. ### Hosted endpoint (recommended) URL: https://api.formfav.com/mcp/ Transport: streamable-http Auth: X-API-Key header (same key as REST API) Each user authenticates with their own key — tier and rate limits apply per user automatically. Claude Desktop config: ```json { "mcpServers": { "formfav": { "type": "http", "url": "https://api.formfav.com/mcp/", "headers": { "X-API-Key": "your_api_key_here" } } } } ``` ### Available MCP tools - get_meetings — list meetings for a date (Free) - get_race_card — full race form for a race (Free) - get_predictions — win/place probabilities (Pro) - get_jockey_stats — jockey stats by track (Pro) - get_trainer_stats — trainer stats (Pro) - get_track_bias — barrier/surface bias (Pro) - search_runner — find runner by name (Pro) - get_runner_profile — career stats for a runner (Pro) ### Standalone mode (local process) Run the MCP server locally via uv. All tool calls share a single API key set in the environment. Claude Desktop config: ```json { "mcpServers": { "formfav": { "command": "uv", "args": ["run", "python", "-m", "mcp_server"], "cwd": "/path/to/formfav-mcp/src", "env": { "FORMFAV_API_KEY": "your_api_key_here" } } } } ``` Setup: install uv (https://docs.astral.sh/uv/), clone the formfav-mcp repo, update "cwd" to point to the cloned repo's src directory, and set your API key. Note: In standalone mode a single API key is used for all tool calls. If multiple people share the server, they share one rate limit and one subscription tier. Full setup guide: https://formfav.com/docs/mcp --- ## CLI FormFav data is accessible from the terminal via a reference Python CLI or any custom client built from the OpenAPI spec. ### Build your own Point your AI agent at the OpenAPI spec and ask it to generate a CLI in any language: ``` I need a CLI tool to query the FormFav horse racing API. Here's the OpenAPI spec: https://api.formfav.com/v1/openapi.json Auth is via X-API-Key header, key is in FORMFAV_API_KEY env var. Build me a [Python CLI / Node script / shell wrapper] that covers meetings, race form, predictions, and jockey stats. ``` ### Reference Python CLI A single-file Python script (~190 lines) covering every endpoint. Dependencies: httpx, typer. ``` pip install httpx typer export FORMFAV_API_KEY=your_api_key_here python formfav.py meetings 2026-04-04 python formfav.py race-card 2026-04-04 flemington 5 python formfav.py predictions 2026-04-04 randwick 3 ``` ### Available commands - meetings DATE — list race meetings (Free) - race-card DATE TRACK RACE — full race form (Free) - predictions DATE TRACK RACE — ML win/place probabilities (Pro) - jockey-stats NAME [--window N] — jockey performance (Pro) - trainer-stats NAME [--window N] — trainer performance (Pro) - track-bias TRACK [--condition X] — barrier/surface bias (Pro) - search-runner NAME — find runner by name (Pro) - runner-profile RUNNER_ID — career statistics (Pro) All output is JSON and can be piped to jq or other tools: ``` python formfav.py race-card 2026-04-04 flemington 5 | jq '.runners[] | .name' ``` Free-tier commands (meetings, race-card) return basic runner data and full form history. With a Pro key, these same commands add form badges, speed maps and full statistical breakdowns — plus all Pro-only commands become available. Full CLI guide: https://formfav.com/docs/cli --- ## Form Badges (Pro) Form badges are contextual, at-a-glance insights attached to each runner in a race. They are automatically computed from each runner's full racing history against the specific conditions of today's race. Each badge has a sentiment: + (positive), / (neutral), or - (negative). Badges are returned in the `decorators` array on each runner object: ```json { "type": "track_specialist", "label": "Track Specialist", "shortLabel": "Track", "category": "specialization", "sentiment": "+", "description": "Strong record at this track", "detail": "5 wins from 12 starts (42%)" } ``` ### Badge fields - type: stable identifier for filtering (e.g. "last_start_winner") - label: human-readable label - shortLabel: compact version for tight layouts - category: one of form, specialization, conditions, fitness, running_style, class, barrier, connections - sentiment: "+" positive, "/" neutral, "-" negative - description: general explanation (same for all runners) - detail: runner-specific context with stats ### Eight badge categories 1. Current Form — recent results, winning streaks, form trajectory 2. Track & Distance — proven affinity for today's course and distance 3. Conditions — preference for today's track surface and weather 4. Fitness & Preparation — fresh from spell, building fitness, or race-hardened 5. Running Style — how the runner's race pattern fits today's pace scenario 6. Class & Grading — stepping up, dropping back, or rising through grades 7. Barrier Draw — whether today's barrier is historically favourable at this track 8. Connections — jockey and trainer performance at this venue and in combination Runners typically carry 2–5 badges per race. Only the most relevant insights appear. Full guide: https://formfav.com/docs/form-badges --- ## Speed Map & Pace Analysis (Pro) A speed map predicts how a race will unfold in the early stages — where each runner is likely to settle and how much early pressure there will be. ### Per-runner fields Each runner includes a `speedMap` object (null on Free tier): ```json { "speedMap": { "runningStyle": "P", "earlySpeedIndex": 7.2, "settlingPosition": 3.5 } } ``` - runningStyle: L (Leader), P (Presser), M (Midfield), B (Back), X (Unknown) - earlySpeedIndex: float 0–10, higher = faster starter - settlingPosition: average position at first call point (1.0 = leads, 8.0 = back) ### Pace scenario (race-level) Returned at the top level of the form response: - SLOW — no genuine speed, leaders dictate unchallenged - MODERATE — one runner likely leads without pressure - FAST — runners competing for the lead, tempo lifts - VERY_FAST — multiple speed runners, hot tempo favours closers ### Using the speed map - VERY_FAST pace: look for B (back) runners with strong finishing records - SLOW pace: look for L (leader) runners who can control the race from front - Combine with barrier draw for the strongest analysis Full guide: https://formfav.com/docs/speed-map --- ## Class Profile & Race Fit (Pro) Each runner includes a `classProfile` object and a `raceClassFit` object (null on Free tier). ### Class profile ```json { "classProfile": { "currentRating": 72, "peakRating": 85, "highestClassWon": 75, "optimalRangeMin": 65, "optimalRangeMax": 85, "trend": "stable" } } ``` - currentRating: class level of most recent race (0–100 scale) - peakRating: highest class ever competed at - highestClassWon: highest class where runner has won - optimalRangeMin/Max: class range where runner performs best - trend: "rising", "stable", or "dropping" ### Rating scale (0–100) - 100: Group 1 - 95: Group 2 - 90: Group 3 - 85: Listed - 75: Open/WFA - 70: BM90 / NR 70+ / Grade 1 - 58: Class 1 - 30: Maiden ### Race class fit ```json { "raceClassFit": { "raceClassRating": 72, "classDifference": 0, "withinOptimalRange": true, "assessment": "comfort_zone" } } ``` - raceClassRating: class rating for today's race - classDifference: race class minus runner's current rating (positive = stepping up) - withinOptimalRange: whether race falls in runner's best performance range - assessment: "comfort_zone", "slight_rise", "big_rise", "slight_drop", "big_drop" Full guide: https://formfav.com/docs/class-profile --- ## Common Agent Workflows ### Workflow 1: Daily meeting scan 1. Call GET /form/meetings with today's date and race_code=gallops 2. Iterate meetings array to get track slugs 3. For each meeting, call GET /form with race=1 to get the race form 4. Extract runner names, form strings, and career stats ### Workflow 2: Prediction sweep for a meeting 1. Call GET /form/meetings to get the track slug 2. Loop race numbers 1–N (typical meeting has 8–10 races) 3. Call GET /predictions for each race 4. Compare top prediction (highest winProb) across races ### Workflow 3: Jockey research at a track 1. Call GET /stats/jockey/{name} with the jockey's name 2. Filter results by the target track slug 3. Compare track-specific strike rate to overall strike rate ### Workflow 4: Track bias check before betting 1. Call GET /stats/track-bias/{track} with relevant condition filter 2. Review barrier win % to identify advantaged and disadvantaged barriers --- ## Known Limitations - Stats data window: jockey, trainer, and runner statistics cover approximately 2019 onwards - Live data is tier-dependent: Free and Pro have no live odds and results batch-ingested every 2–3 hours; Premium and Enterprise add live bookmaker prices (GET /prices), results as races settle, and live scratchings, track condition and weather. No tier provides in-running positions - Predictions are probabilities: winProb values sum to ~1.0 across all runners in a race - Coverage: AU, NZ, HK, GB, US, and EU thoroughbred, harness, and greyhound racing --- ## Rate Limits Limits are rolling 24-hour windows, not midnight resets. - Free: 1,000 requests/day - Pro: 20,000 requests/day - Premium: 100,000 requests/day - Enterprise: unlimited Limits are per user, not per API key — all of a user's keys draw on the same daily allowance. Requests beyond the limit return HTTP 429 with a Retry-After header. --- ## Error Codes - 400 Bad Request — missing or invalid parameters - 401 Unauthorized — invalid or missing API key - 403 Forbidden — the endpoint needs a higher tier: Pro for results, predictions and stats; Premium for /prices - 404 Not Found — race or meeting not found; call GET /form/meetings to confirm the track slug and race number running on that date - 429 Too Many Requests — rate limit exceeded; check Retry-After header - 500 Internal Server Error — server-side error; retry after a short delay --- ## Links - Homepage: https://formfav.com - Horse Racing Data Feed (form, results, live prices): https://formfav.com/racing-data-feed - Racing Results API: https://formfav.com/racing-results-api - Live Prices reference: https://formfav.com/docs/endpoints/prices - Documentation: https://formfav.com/docs - AI Agents guide: https://formfav.com/docs/ai-agents - MCP Server guide: https://formfav.com/docs/mcp - CLI guide: https://formfav.com/docs/cli - AI Agent landing page: https://formfav.com/for-ai-agents - OpenAPI spec: https://api.formfav.com/v1/openapi.json - Pricing: https://formfav.com/pricing