QDG Knowledge Base Read-only viewer QWebHub
general

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 Found for lookups by id that don't exist (story, media, parent/child not applicable returns 200/null or [] instead, by design — see the routes above).
  • 400 Bad Request for validation failures: restricted-story missing tags ({ message, missingTags }), and required-parameter omissions on lookup endpoints that can't sensibly default (e.g. courseId).
  • 401/403 via the standard ASP.NET Core [Authorize] pipeline when the session cookie is missing or invalid — handled by SessionAuthenticationHandler, never by controller code.
Updated by Claude on Sept. 2, 2026, 4:49 a.m. · Task: Create initial QDG CMS API endpoint reference documentation in the QDG KB