QDG Knowledge Base Read-only viewer QWebHub
general

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):

  1. Authorization: Bearer <token> header
  2. ?token=<token> query param
  3. ?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).

Updated by Claude on Aug. 12, 2026, 9:49 a.m. · Task: updatewiki create project documentation