API Reference
Version 1 · Initial detailed API reference, reconciled against urls.py/views.py/queries.py (adds /guide/ endpoints missing from the older API docx)
Runner Form — API Reference
Base path: /form/ (mounted by config/urls.py). All routes below are relative to that.
A prior
Runner_Form_API_Documentation.docx(repo root) documents a shared dev/staging host athttp://138.226.220.168:8081/form. It also predates the/guide/feature below — confirm which host and route set applies before relying on that file.
Authentication
POST /form/with form fieldsusernameandpassword— dev credentials areadmin/ a hard-coded password checked inviews.py(value intentionally not reproduced here — see source, or ask a maintainer). Success setsrequest.session['is_authenticated'] = Trueand redirects (302) to/form/runner/. Failure re-renders the login page (200) with "Invalid username or password".GET /form/logout/flushes the session and redirects to the login page.- Every route below except
/form/api/search/requires an authenticated session (redirects 302 to/form/otherwise). SESSION_ENGINE = signed_cookies— sessions live in a signed browser cookie, no DB session table.
HTML page endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /form/ |
Public | Login page |
| POST | /form/ |
Public | Authenticate |
| GET | /form/logout/ |
Session | Log out |
| GET | /form/runner/ |
Required | Landing page — redirects to WINX's brief form |
| GET | /form/runner/<horse_id>/ |
Required | Runner form page. ?tab=brief|detailed (default brief), ?date=YYYY-MM-DD, ?race=<race_id> pre-select the toolbar |
| GET | /form/jockey/<jockey_id>/ |
Required | Jockey profile + ride history |
| GET | /form/trainer/<trainer_id>/ |
Required | Trainer profile + entry history |
| GET | /form/guide/ |
Required | Form guide — today's (or ?date=) meetings/races, optional ?country=, ?race=<race_id> shows runners inline |
FEATURES.md / Runner_Form_Features.docx describe the form guide as a page still to be
built. It is implemented today (views.form_guide, template form-guide.html, backed by
get_form_guide, get_form_guide_dates, get_form_guide_countries in queries.py) — those
planning docs are stale on this point.
JSON API endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /form/api/dates/ |
Required | All available race dates, newest first |
| GET | /form/api/meetings/?date= |
Required | Venues racing on a date |
| GET | /form/api/races/?date=&meeting= |
Required | Races for a date, optionally filtered to one venue |
| GET | /form/api/runners/?race_id= |
Required | Runners nominated in a race |
| GET | /form/api/search/?q= |
Public | Search horses by name (autocomplete). Minimum 3 characters |
GET /api/dates/
{ "dates": ["2026-06-25", "2026-06-24", "2026-06-23"] }
GET /api/meetings/?date=2026-06-25
{ "meetings": [ { "meeting_id": "RANDWICK", "meeting_name": "RANDWICK" } ] }
Empty/missing date → { "meetings": [] }.
GET /api/races/?date=2026-06-25&meeting=RANDWICK
{ "races": [
{ "race_id": 4833570, "race_number": 6, "race_name": "Tennant Creek Cup",
"course_name": "TENNANT CREEK", "distance": "1200", "class_level": "Open" }
] }
GET /api/runners/?race_id=4833570
{ "runners": [
{ "horse_id": 1194902, "tab_number": 3, "horse_name": "EQUAL BALANCE",
"jockey": "Jane Smith", "trainer": "Tom Brown", "barrier": 4 }
] }
Missing/invalid race_id → { "runners": [] }.
GET /api/search/?q=winx
{ "horses": [ { "id": 989258, "runner_name": "WINX", "country": "AUS" } ],
"query": "winx", "count": 1 }
Query under 3 characters → { "horses": [], "query": "wi", "min_chars": 3 }.
Template context (page endpoints)
| Endpoint | Key context variables |
|---|---|
/runner/<id>/ |
horse, runs (≤200, normalised), stats, trial_runs, sales, meeting_races, selected_date, selected_race_id, selected_race_runners, active_tab |
/jockey/<id>/ |
jockey, runs, stats, active_tab |
/trainer/<id>/ |
trainer, runs, stats, active_tab |
/guide/ |
selected_date, selected_country, dates, countries, meetings (each with nested races), selected_race_id/number/name, runners |
stats (from compute_stats(runs)): career, by_country, by_jockey, by_trainer,
by_distance, by_track, by_class, by_sot, last5, preferred_range,
consecutive_wins. See [[database-schema]] for the source columns.
Errors
| Scenario | Status | Body |
|---|---|---|
| Not authenticated | 302 | Redirect to /form/ |
| Horse/jockey/trainer ID not found | 200 | error.html — "{Entity} {id} not found." |
| Search query < 3 chars | 200 | { "horses": [], "query": "..", "min_chars": 3 } |
Missing race_id/date on a JSON endpoint |
200 | Empty array under the relevant key |
| Sales lookup fails (DB error) | 200 | Page renders with sales = [] (caught and logged in views.runner_form) |
Caching
All JSON endpoints and page data are cached via Django's default in-memory (per-process) cache.
Keys are versioned (v2, v3, v7, …) so a query/shape fix can be forced through by bumping
the suffix in queries.py instead of flushing the cache server.
| Cache key | Timeout | Data |
|---|---|---|
runner_form:data:v7:{horse_id} |
120s | Full runner page data (horse, runs, stats, trials) |
runner_form:sales:v3:{horse_id} |
600s | Sales records |
runner_form:available_dates |
300s | /api/dates/ |
runner_form:meetings:v2:{date} |
300s | /api/meetings/ |
runner_form:races:{date}:{track} |
300s | /api/races/ |
runner_form:runners:{race_id} |
300s | /api/runners/ |
runner_form:horse_search:{q} |
180s | /api/search/ |
runner_form:form_guide:v2:{date}:{country} |
120s | /guide/ meetings + races |
runner_form:form_guide_dates:v3 |
300s | /guide/ date tab strip |
runner_form:form_guide_countries:v2:{date} |
300s | /guide/ country filter |
runner_form:meeting_details:{date} |
120s | get_meeting_details() — used by meeting_list.html/meeting_detail.html templates, which exist but aren't currently wired to a URL in urls.py |
A condensed version of this table lives in [[quick-reference]].