-
Notifications
You must be signed in to change notification settings - Fork 15
Questarr API Reference
Base URL (local dev): http://<your_ip_or_domain>:<your_port>
Content-Type: application/json for all request bodies unless noted otherwise.
Corresponding Questarr Version:: v1.3.0
Questarr uses JWT Bearer tokens.
- obtain a token via
POST /api/auth/login. - Include the token in the
Authorizationheader of every protected request:Authorization: Bearer <token>
Most /api/* routes require authentication. Exceptions are noted below.
Token payload contains id and username. Tokens are signed with JWT_SECRET (auto-generated if unset).
All error responses share this shape:
{ "error": "Human-readable message", "details": [...] }details is only present for validation failures (express-validator or Zod). In production, 5xx messages are replaced with "Internal Server Error".
| Limiter | Window | Max Requests | Applied To |
|---|---|---|---|
authRateLimiter |
15 min | 20 | Login endpoint |
sensitiveEndpointLimiter |
1 min | 30 | Write operations |
Auth: None
Purpose: Check whether initial setup has been completed.
Response 200:
{ "hasUsers": true }Auth: None
Rate limit: authRateLimiter
Purpose: Authenticate and receive a JWT.
Request body:
| Field | Type | Required |
|---|---|---|
username |
string | Yes |
password |
string | Yes |
Response 200:
{
"token": "<jwt>",
"user": { "id": "uuid", "username": "admin" }
}Errors: 400 missing/wrong types, 401 invalid credentials.
Auth: JWT required
Purpose: Returns the currently authenticated user.
Response 200:
{ "id": "uuid", "username": "admin", "steamId64": null }Auth: None
Purpose: Liveness probe. Always returns 200 if the server process is running.
Response 200: { "status": "ok" }
Auth: None
Purpose: Readiness probe. Checks database connectivity and IGDB API reachability.
Response 200: { "status": "ok" }
Response 503: { "status": "error" }
All game routes require JWT authentication.
Auth: JWT required
Purpose: List the authenticated user's game collection.
Query params:
| Param | Type | Required | Description |
|---|---|---|---|
search |
string | No | Filter by title substring |
status |
string | No | Comma-separated statuses: wanted, owned, completed, downloading
|
includeHidden |
"true" |
No | Include hidden games |
Response 200: Array of Game objects.
Auth: JWT required
Purpose: List games by a single status value.
Path param: status — one of wanted, owned, completed, downloading.
Query params:
| Param | Type | Required | Description |
|---|---|---|---|
includeHidden |
"true" |
No | Include hidden games |
Response 200: Array of Game objects.
Auth: JWT required
Purpose: Dedicated title search over the authenticated user's collection. Prefer this over GET /api/games?search= when you need a focused search: q is required and validated (1–200 chars), results can be capped with limit, and the endpoint cannot be combined with status filtering. Use GET /api/games?search= instead when you want to combine a title filter with status or retrieve the full collection.
Query params:
| Param | Type | Required | Constraints |
|---|---|---|---|
q |
string | Yes | 1–200 chars |
includeHidden |
"true" |
No | |
limit |
integer | No | 1–100 |
Response 200: Array of Game objects.
Auth: JWT required
Rate limit: sensitiveEndpointLimiter
Purpose: Add a game to the collection. In Questarr, we first search for the game on IGDB to validate its title and retrieve the other informations. Note that they cannot be set manually, so ideally they should all be provided at call time.
Request body:
| Field | Type | Required | Constraints |
|---|---|---|---|
title |
string | Yes | 1–500 chars |
igdbId |
integer | No | min 1 |
summary |
string | No | max 5000 chars |
coverUrl |
string | No | valid URL |
releaseDate |
string | No | YYYY-MM-DD |
rating |
number | No | 0–10 |
platforms |
string[] | No | each max 100 chars |
genres |
string[] | No | each max 100 chars |
publishers |
string[] | No | each max 200 chars |
developers |
string[] | No | each max 200 chars |
status |
string | No | default "wanted"
|
Response 201: Game object.
Errors: 400 validation, 409 game already in collection (returns { "error": "...", "game": {...} }).
Auth: JWT required
Rate limit: sensitiveEndpointLimiter
Purpose: Update a game's tracking status.
Path param: id — UUID.
Request body:
| Field | Type | Required | Values |
|---|---|---|---|
status |
string | Yes |
wanted, owned, completed, downloading
|
completedAt |
date | No | ISO date |
Response 200: Updated Game object.
Errors: 400 validation, 404 not found.
Auth: JWT required
Rate limit: sensitiveEndpointLimiter
Purpose: Remove a game from the collection.
Path param: id — UUID.
Response 204: No content.
Errors: 404 not found.
Auth: JWT required
Purpose: Quick-add a game by title — looks up the best IGDB match and adds it to the collection in one step.
Request body:
| Field | Type | Required |
|---|---|---|
title |
string | Yes |
Response 201: Game object.
Errors: 400 no title, 404 no IGDB match, 409 already in collection.
POST /api/auth/setup → get token
POST /api/indexers → add indexer(s)
POST /api/downloaders → add download client
GET /api/igdb/search?q=Elden+Ring → find IGDB data
POST /api/games → add to collection (status: "wanted")
GET /api/indexers/search?query=Elden+Ring → find releases
POST /api/downloads { url, title, gameId } → send to best downloader
→ game status auto-set to "downloading"
GET /api/downloads → poll all downloaders
GET /api/games/:id/downloads → check tracked downloads for a game
PATCH /api/games/:id/status { status: "owned" } → mark as owned when done
{
id: string; // UUID
userId: string | null;
igdbId: number | null;
steamAppId: number | null;
title: string;
summary: string | null;
coverUrl: string | null;
releaseDate: string | null; // "YYYY-MM-DD"
rating: number | null; // IGDB aggregated rating, 0–100
userRating: number | null; // personal rating, 0.5–10
platforms: string[] | null;
genres: string[] | null;
publishers: string[] | null;
developers: string[] | null;
screenshots: string[] | null;
source: "manual" | "steam" | "api";
igdbWebsites: Array<{ category: number; url: string }> | null;
aggregatedRating: number | null;
status: "wanted" | "owned" | "completed" | "downloading";
releaseStatus: string | null; // "upcoming" etc.
earlyAccess: boolean;
hidden: boolean;
searchResultsAvailable: boolean;
addedAt: number | null; // timestamp ms
completedAt: number | null; // timestamp ms
}