API Reference
Version 1 · New page with the full endpoint reference, auth, and request/response examples
API Reference
Base app: Meeting Stats API v2.0.0. Interactive docs when running: Swagger /docs, ReDoc
/redoc, hand-written reference /api-docs, manual tester /api-test.
Auth
Every endpoint below except /, /api-test, /api-docs, /health, POST /login, GET /login
requires the static bearer token, supplied via (checked in this order):
Authorization: Bearer <token>header?token=<token>query param?sso_token=<token>query param
There is exactly one valid token: tds-stats-secure-token-2026. See overview for why
this is a shared API key, not per-user auth.
Endpoints
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | / |
no | Serves the dashboard (static/index.html) |
| GET | /api-test |
no | Serves the manual API tester (static/api_test.html) |
| GET | /api-docs |
no | Serves the hand-written interactive reference (static/api_docs.html) |
| GET | /health |
no | Liveness check — {"status": "ok"} |
| POST | /login |
no | Hardcoded admin/admin123 → returns the static token |
| GET | /login |
no | Serves the local login page, or redirects to Appsmith SSO if referer contains appsmith and no ?direct= |
| POST | /api/logout |
no | Proxies a logout call ({"sessionId": ...}) to an external auth service |
| GET | /meetings?date= |
yes | Lists meetings (course name + course_id) with runner activity on a date; date defaults to today |
| GET | /meeting-participants?date=&courseId= |
yes | Lists trainers/jockeys entered at a meeting, with zeroed stat placeholders — no calculation |
| GET | /trainer-stats?date=&courseName=|courseId= |
yes | Trainer stats computed directly from the DB — always local, never bypasses to the external API |
| GET | /jockey-stats?date=&courseName=|courseId= |
yes | Jockey stats, same behavior |
| GET | /trainerStats?date=&courseId= |
yes | "Public" trainer stats — external API if external_api.enabled, else local DB; updates USER_CONTEXT for the caller's IP |
| GET | /jockeyStats?date=&courseId= |
yes | Same, for jockeys |
| GET | /meetingStats?date=&courseId= |
yes | Trainers + jockeys in one call; same external/local + USER_CONTEXT behavior |
| GET | /api/user-context |
yes | Returns {date, course_id} last requested by this caller's IP via a camelCase endpoint, if within 5 minutes; else {} |
| POST | /save-stats |
yes | Persists a previously-computed {date, course_id, trainer_stats, jockey_stats} payload into meeting_stats_payloads |
| GET | /saved-stats?date=&courseId= |
yes | Retrieves a previously saved payload; {"status": "not_found"} if none exists (or the target table doesn't exist yet) |
Use the camelCase endpoints (/trainerStats, /jockeyStats, /meetingStats) for external
integrations — they're the ones documented at /api-docs and the only ones that honor
external_api.enabled. See architecture for the full behavioral diff between the
two endpoint families, and database for a caveat about the courseName query param
on the kebab-case endpoints.
Example requests
GET /trainerStats?date=2026-06-03&courseId=116
Authorization: Bearer tds-stats-secure-token-2026
GET /meetingStats?date=2026-06-03&courseId=116
Authorization: Bearer tds-stats-secure-token-2026
POST /save-stats
Authorization: Bearer tds-stats-secure-token-2026
Content-Type: application/json
{
"date": "2026-06-03",
"course_id": 116,
"trainer_stats": [ ... ],
"jockey_stats": [ ... ]
}
Response shape — per trainer/jockey entity
Returned by /trainer-stats, /jockey-stats, and nested under trainers/jockeys in
/trainerStats, /jockeyStats, /meetingStats:
{
"id": 31047,
"name": "GREG MIDDLETON",
"performance_365d": { "wins": 3, "starts": 21, "win_pct": 14.3 },
"wins_30d": 0,
"avg_per_meeting": 0.12,
"last_win_date": "2026-04-10",
"avg_finishing_pos": 6.2,
"top_horses": [ { "name": "HURRICANE LANE", "win_pct": 66.7 } ],
"price_band_ivs": [
{
"band": "1.01-1.99",
"trainer_wps": { "wins": 1, "win_pct": 50.0, "places": 2, "place_pct": 100.0, "starts": 2 },
"baseline_wps": { "wins": 80, "win_pct": 52.3, "places": 140, "place_pct": 91.5, "starts": 153 },
"iv": 0.956
}
]
}
avg_per_meeting is actually avg_wins_per_meeting (from calc_meeting_averages), despite the
generic field name — there's no separate "average runners/races per meeting" field in the output
even though calc_meeting_averages computes those too.
The camelCase endpoints wrap this array under {"status": "success", "meeting": {"date", "course", "courseId"}, "trainers": [...]} (and/or "jockeys").
Calculation reference
See architecture for how a request maps to calculation functions, and overview §"Stats calculation" for what each field means (WPS, price-band IV, meeting averages, top horses).