You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This document describes Questarr's external software interfaces: the REST
API exposed by the Express server, and the real-time events pushed over
Socket.io. It complements docs/ARCHITECTURE.md (system
actors and data flow) and docs/SECURITY_ASSESSMENT.md
(risk assessment).
Update policy: re-run documentation generation for the affected route
group whenever server/routes.ts changes; manually update the Steam/
PCGamingWiki sections when server/steam-routes.ts or
server/pcgamingwiki-router.ts change.
Overview
Base URL: all endpoints are served under /api on the same origin as
the app (default http://localhost:5000).
Format: JSON request/response bodies throughout, except file uploads
(multipart/form-data for SSL certificate upload) and the download bundle
endpoint (application/zip response).
Authentication: a JWT bearer token obtained from POST /api/auth/login
(or POST /api/auth/setup for the first account), sent as
Authorization: Bearer <token> on subsequent requests. Verified by
authenticateToken/optionalAuthenticateToken in server/auth.ts. A
global gate — app.use("/api", authenticateToken) in server/routes.ts —
requires a valid JWT for every route registered after it; a small set of
routes registered earlier remain public (see the Authentication section
below).
Rate limiting: generalApiLimiter (100 req/min/IP) applies to all
/api routes; authRateLimiter additionally guards login;
sensitiveEndpointLimiter additionally guards write-heavy/sensitive
endpoints; igdbRateLimiter additionally guards IGDB proxy endpoints. See
server/middleware.ts.
{ success: true, message }; 401 if current password wrong; 400 { error, details } on Zod failure
/api/auth/setup and /api/auth/login are the two security-sensitive
endpoints that rely on inline typeof checks plus manual length/if
validation instead of an express-validator chain or a Zod schema (see the
risk register in docs/SECURITY_ASSESSMENT.md). /api/auth/password uses a
proper Zod schema (updatePasswordSchema).
Routes registered before the global authenticateToken gate (server/routes.ts,
before the auth-gate line) are public unless they explicitly list
authenticateToken: /api/auth/*, /api/health, /api/settings/ssl
(GET/PATCH), /api/settings/ssl/generate, /api/settings/ssl/upload,
/api/system/filesystem, /api/config. Everything registered after that
line — including /api/ready and every resource group below — requires a
JWT even where a table row doesn't repeat authenticateToken.
Games
Method
Path
Auth Required
Request Body
Response
GET
/api/games
JWT (global gate)
Query: search?, includeHidden?, status? (comma-separated or array)
{ name (1–200), type (transmission|rtorrent|qbittorrent|sabnzbd|nzbget), url (full URL or bare hostname/IP), username?, password?, enabled?, downloadPath? (no ".."), label?, urlPath? } via insertDownloaderSchema; SSRF-checked via isSafeUrl
JWT (registered after the global auth gate, so optionalAuthenticateToken never actually sees an unauthenticated request) + sanitizeIndexerSearchQuery, validateRequest
All blacklist entries across the user's games (ReleaseBlacklist[])
Per-game blacklist management (POST/GET/DELETE /api/games/:gameId/blacklist*)
is documented under Games.
Steam
server/steam-routes.ts — 2 routes, mounted directly on the app (no prefix
router), both behind the global auth gate plus explicit authenticateToken.
Method
Path
Auth Required
Request Body
Response
PATCH
/api/user/steam-id
JWT
{ steamId: string } — must be a 17-digit SteamID64 starting with 7656 (steamService.validateSteamId)
{ success: true, steamId }; 400 if missing/invalid format
POST
/api/steam/wishlist/sync
JWT
—
{ success, addedCount, games } (via syncUserSteamWishlist); 400 if Steam ID not linked or sync fails
PCGamingWiki
server/pcgamingwiki-router.ts — 1 route.
Method
Path
Auth Required
Request Body
Response
GET
/api/external/pcgamingwiki
JWT
Query: steamAppId (required, positive integer)
{ url: string | null }; 400 if steamAppId missing/invalid; result cached 24h (5min on lookup failure)
Error Format
Across handlers, error responses follow one of these consistent shapes:
Validation failures (express-validator chains): the shared
validateRequest middleware (server/middleware.ts) runs after any
sanitize* validator array. On failure it returns HTTP 400 with:
(message text varies per handler, e.g. "Invalid game data", "Invalid
status data", "Invalid settings data")
Ad hoc business-logic errors: most handlers return a plain
{ "error": "<message>" } object directly for 400/401/403/404/409/502
cases (sometimes with an extra field attached, e.g. { error, game } on a
duplicate-game 409), without going through shared middleware.
Uncaught/rethrown errors (global handler): routes that call
next(error) fall through to errorHandler (server/middleware.ts),
which returns:
{
"error": "<message, or 'Internal Server Error' in production for 5xx>",
"details": "<only if err.details was set>"
}
Status comes from err.status/err.statusCode, defaulting to 500. In
production, 5xx messages are sanitized to "Internal Server Error" to
avoid leaking internals; 4xx messages pass through as-is. All errors are
logged via Pino with method/path context — error level for 5xx, warn
otherwise.
No-content success: several DELETE endpoints return 204 No Content
with an empty body rather than a JSON payload (e.g. DELETE /api/games/:id,
DELETE /api/indexers/:id, DELETE /api/downloaders/:id,
DELETE /api/rss/feeds/:id, DELETE /api/notifications).
Real-time interface (Socket.io)
Alongside the REST API, the server pushes real-time events over Socket.io
(server/socket.ts). See docs/ARCHITECTURE.md §6
for the full actor/data-flow explanation. Summary: