API Reference
Version 1 · Initial API reference: full endpoint table for Auth, Story, Media, Lookup controllers plus health check and error conventions
API Reference
All routes are versioned: api/v{version:apiVersion}/[controller], currently v1. Except where noted, every endpoint requires the qdg_session cookie ([Authorize(AuthenticationSchemes = SessionAuthenticationOptions.SchemeName)]) — see User Guide for how to obtain it. Interactive docs are served by Swagger UI at the API root (/) in Development and Production.
Auth (/api/v1/auth) — anonymous except me/logout
| Method | Route | Auth | Description |
|---|---|---|---|
| POST | /login |
anonymous | Proxies credentials to the QDBAuth service; returns whether a TOTP step is required. |
| POST | /totp-verify |
anonymous | Verifies the TOTP code; on success sets the qdg_session cookie (HttpOnly, Secure, SameSite=Lax, path /api, 1-day expiry) and returns the session payload. |
| GET | /me |
session | Returns the current user (id, email, first/last name, role) from the session's claims. |
| POST | /logout |
session | Revokes the session with QDBAuth and deletes the qdg_session cookie. |
Stories (/api/v1/story) — session required
| Method | Route | Description |
|---|---|---|
| GET | / |
Paged/filtered story list (search, date range, visible, newsType, contributor, accessScope, breakout, hasStoryImage, siteId, sort, page/pageSize). |
| GET | /{id} |
Full story detail by id, or 404. |
| GET | /{id}/parent |
Parent story for the linked-story widget (200 with null if none). |
| GET | /{id}/children |
Child stories linked to this story, ordered by sort order. |
| GET | /link-search |
Search stories to link, scoped to required siteIds; numeric queries match by id, otherwise title LIKE. |
| POST | / |
Creates a story (syncs site/news-type mappings). 400 with missingTags if AccessScope is Restricted and required access-tier tags are absent from the content. |
| PUT | /{id} |
Updates a story (same mapping resync and restricted-tag validation as create). 404 if the id doesn't exist. |
| DELETE | /{id} |
Hard-deletes the story and its site/news-type mapping rows in one transaction. 404 if nothing was deleted. |
Media (/api/v1/media) — session required
One controller serves both the Image Gallery and Video Gallery; type (photo/video, from MediaType) disambiguates.
| Method | Route | Description |
|---|---|---|
| GET | / |
Paged/filtered media list for a given type (search, photoType, page/pageSize). |
| GET | /upload-url |
Phase 1 of upload: returns a presigned Wasabi PUT URL + the bare object key it's signed for. The frontend uploads file bytes directly to Wasabi with this URL. |
| GET | /{id}?type= |
Single media item; type required to disambiguate photo vs video ids sharing the same numeric id space. |
| POST | / |
Phase 2 of upload: persists a new tblphotos row referencing the object key already uploaded to Wasabi. |
| PUT | /{id} |
Updates a media item; omitting an object-key field in the body keeps the existing stored file for that slot (COALESCE at the DB layer). |
| DELETE | /{id}?type= |
Deletes the DB row and every associated Wasabi object (original + resized variants for photos). |
Lookups (/api/v1/lookup) — session required, read-only
| Method | Route | Description |
|---|---|---|
| GET | /bootstrap?keys= |
Multiple lookup lists in one call; comma-separated keys (sites, authors, contributors), unknown keys omitted rather than erroring. |
| GET | /sites |
Site dropdown list. |
| GET | /authors |
Author dropdown list (journalists). |
| GET | /contributors |
Contributor dropdown list. |
| GET | /horses?query= |
Search-as-you-type horse lookup (qdb database); empty query returns [] without hitting the DB. |
| GET | /trainers?query= |
Search-as-you-type trainer lookup (qdb database). |
| GET | /jockeys?query= |
Search-as-you-type jockey lookup (qdb database). |
| GET | /courses?date= |
Course list, optionally filtered to a given date (yyyy-MM-dd). |
| GET | /races?courseId= |
Races for a given course; courseId required (400 if missing). |
| GET | /news-types?siteIds= |
News types available to the given comma-separated site ids; empty/missing siteIds returns []. |
Health
| Method | Route | Auth | Description |
|---|---|---|---|
| GET | /health |
anonymous | JSON report of tagged health checks: mysql (tags db, ready) and wasabi (tags storage, ready). Used by the deploy pipeline's post-deploy check. |
Error conventions
404 Not Foundfor lookups by id that don't exist (story, media, parent/child not applicable returns200/nullor[]instead, by design — see the routes above).400 Bad Requestfor validation failures: restricted-story missing tags ({ message, missingTags }), and required-parameter omissions on lookup endpoints that can't sensibly default (e.g.courseId).401/403via the standard ASP.NET Core[Authorize]pipeline when the session cookie is missing or invalid — handled bySessionAuthenticationHandler, never by controller code.