TroyenAPI Reference
Version 1 · Create initial TroyenAPI endpoint reference from controller source, versioning/auth config, and Program.cs
TroyenAPI Reference
TroyenAPI is the customer/external-facing, read-only REST API over the Mongo-backed racing data (meetings, races, race cards, speed maps, stats). Source: TroyenAPI/Controllers/.
Correction to earlier assumption: this API is not JWT-authenticated. Auth is a custom opaque API-key scheme (
Authorization: Bearer td-...), validated in-process against aclient_api_keysMongo collection. JWT only exists in the separateTroyenDataWebadmin app. See Auth below.
Routing & versioning
- Base route:
api/v{version:apiVersion}/{Controller}/...(TroyenAPI/Controllers/BaseApiController.cs). - Versioning (
TroyenAPI/Extensions/ApiVersioningExtensions.cs): default version1.0,AssumeDefaultVersionWhenUnspecified = true.v1controllers carry no[ApiVersion]attribute;v2controllers are[ApiVersion("2.0")]. Version can also be requested viaAccept: application/json;v=2.0(MediaTypeApiVersionReader). v1controllers are class-level[AllowAnonymous];v2controllers have no[AllowAnonymous]and inherit[Authorize]from the base — but see the auth caveat below, since enforcement currently depends on anAllowEmptyKeysetting.- Swagger/ReDoc:
/swagger/{version}/swagger.jsonper version; ReDoc UI (DocumentTitle: "Troyon Data Helpers API"— likely a copy-paste leftover) is pinned to the v1 spec only.wwwroot/APIDefinition/v1.jsonis a separate static OpenAPI file not verified against the live Swagger output — treat as possibly stale. - CORS:
AllowAll(any origin/method/header). HTTPS redirection is disabled inProgram.cs.
v1 endpoints
api/v1/Meeting
| Verb | Route | Params | Returns |
|---|---|---|---|
| GET | meeting-list |
date (required, yyyy-MM-dd), discipline (T|H|G), countryISO/ISO2/ISO3 |
List<MeetingListItem> |
| GET | extended-meeting-list |
same + template (customer-template token; 403 if unresolvable) |
List<Meetingv2> with embedded race list |
api/v1/Race
| Verb | Route | Params | Returns |
|---|---|---|---|
| GET | race-list/{meetingId} |
path meetingId |
List<Race> (non-hidden meetings only, unordered) |
| GET | racecard/{raceId} |
path raceId; query language="EN", style="AU", ignoreCache=false |
RaceCard (v1 shape; form lines truncated to last 3) |
| GET | race-search |
date, countryCode, course, discipline="T", meetingId, raceNumber=1, isIndirect=false, source |
disambiguation list, or a single RaceCardV2 if exactly one match. Writes lookup corrections via SaveLookup when source given. |
| GET | speedmap/{raceId} |
path raceId |
SpeedMap |
| GET | stats/{raceId} |
path raceId |
{ runnerId, runnerName, stats }[] |
| GET | extended-race-list/{raceId} |
path raceId |
RaceCard (includes previous-run form data) |
| GET | next-to-jump |
discipline="T,H,G", countryISO="", limit=15 (1–100), combined=false |
{ status, timestamp, upcomingRaces: [] } |
api/v1/OpenUtility
| Verb | Route | Returns |
|---|---|---|
| GET | heart-beat |
plain string "API up and running : Server time - {time}" — health check |
v2 endpoints
Same routes/params as v1 unless noted, under api/v2/..., requiring [Authorize] (TokenScheme):
Meeting:meeting-list(identical to v1, notemplatesupport difference);extended-meeting-list(notemplateparam at all in v2).Race:race-list/{meetingId}(doesn't require non-hidden meeting, orders by race number);racecard/{raceId}returnsRaceCardV2— maps distances to end in"f", normalizes region codes to ISO, reformats finish times, and reads aclientheader for customer-template shaping;speedmap/{raceId},stats/{raceId},extended-race-list/{raceId},next-to-jump— identical to v1.
Internal-only (not part of the customer surface)
POST internal/template-cache/reload(TemplateCacheController) — hot-reloads customer data templates. Gated by anX-Internal-Secretheader compared against config, not the customer API-key scheme (/internalis inApiKeyMiddleware.SkipPrefixes). Exclude from any customer-published version of this doc.
Auth
- No login/token endpoint exists inside
TroyenAPIitself. API keys (prefixtd-..., AES-encrypted,SHA256(PassKey)-derived) are minted viaCommon/Services/ApiKeyService.CreateKeyAsyncand stored inclient_api_keysin Mongo — the issuance/admin UI for this was not found insideTroyenAPIand is likely inTroyenDataWeb(worth a follow-up look there if the issuance side needs documenting). - Validation happens in-process (
ApiKeyMiddleware+TokenAuthenticationHandler, scheme nameTokenScheme) — decrypt, DB lookup, expiry/allowed-endpoint/rate-limit checks. No external auth service (e.g. QDBAuth) is involved in this path. - Operational caveat, verify before relying on it:
ApiKeySettings:AllowEmptyKey = trueinappsettings.jsonmeans requests with no/malformedAuthorizationheader are currently let through as anonymous rather than rejected with 401 — combined with all v1 controllers being[AllowAnonymous], auth is effectively optional in the current configuration for both v1 and v2. Confirm the deployed value ofAllowEmptyKeyper environment before describing auth as "required" anywhere customer-facing.
Ports / environment
- Local dev:
http://localhost:6101(Kestrel profile) /http://localhost:53537(IIS Express) —TroyenAPI/Properties/launchSettings.json. - Docker:
EXPOSE 8080(TroyenAPI/Dockerfile). appsettings.jsonAPIBasesection lists base URLs for other upstream services (Internal,External,Silk) this API calls — not this API's own public base URL, which isn't set explicitly anywhere found.appsettings.json/launchSettings.jsoncontain live credentials (Mongo connection string, S3 keys,ApiKeySettings:PassKey,InternalSettings:ReloadSecret) — not reproduced here; scrub before any external sharing and rotate if ever exposed.
Known documentation gap
TroyenAPIBruno/*.bru (Meetings.bru, Races.bru, RaceCard.bru, DataDump.bru) — a Bruno collection meant to exercise this API — target a stale host/route shape (http://102.218.213.42:1540/RaceContoller/...) that doesn't match any current controller (there's also no data-dump endpoint in the codebase at all). Treat this collection as outdated; regenerate example requests from the live /swagger/v1/swagger.json / /swagger/v2/swagger.json instead of trusting it.