WebUI API Reference¶
Complete API reference for qBitrr's WebUI REST API. All endpoints are accessible via the built-in WebUI server (default port 6969).
Overview¶
Base URL¶
Default: http://localhost:6969
Endpoint Patterns¶
qBitrr provides dual endpoint patterns for flexibility:
| Pattern | Purpose | When authentication is required | Typical use |
|---|---|---|---|
/api/* | API-first endpoints | Same rules as /web/* for mirrored routes: when WebUI auth is enabled, use Bearer WebUI.Token or a browser session after login | External clients, scripts, automation |
/web/* | First-party endpoints | Same as /api/* for mirrored data/control routes (see Public endpoints); not a separate "anonymous API" | React WebUI, same-origin tools |
Mirrored pairs (for example GET /api/processes and GET /web/processes) return the same JSON. Prefer /api/* for scripts (Bearer header) and /web/* for the bundled WebUI (session cookie).
OpenAPI and Swagger UI¶
The running WebUI serves a bundled OpenAPI 3 document and Swagger UI:
| Resource | URL | Description |
|---|---|---|
| Interactive docs | GET /web/docs or GET /api/docs | Swagger UI (both views load the same filtered spec on the same origin) |
| OpenAPI JSON | GET /web/openapi.json or GET /api/openapi.json | Machine-readable spec for codegen or import into API clients |
Authentication: When WebUI.AuthDisabled is true, these URLs work without credentials. When WebUI authentication is enabled, they use the same rules as other protected routes: open /web/docs in a browser after logging into the WebUI (session cookie), or pass Authorization: Bearer <WebUI.Token> (or use Authorize in Swagger for "Try it out"). The main WebUI header includes an OpenAPI link to /web/docs.
The served OpenAPI document is /api/*-first; it also includes mirrored Arr poster thumbnail routes under /web/* (see Arr poster thumbnails). Other /web/*-only routes remain runtime-available and are documented in prose here.
The base spec is maintained in the repository at qBitrr/openapi.json (also shipped inside the Python package). A prose reference for every endpoint continues in this document.
Keeping the spec aligned with the runtime¶
Every Flask route registered under qBitrr/webui/ should appear under paths in qBitrr/openapi.json (and vice versa). A static drift check is provided so the two files cannot diverge silently:
The check is also wired into pre-commit (local hook openapi-check). It scans @app.<method>("/path") and @_dual_route("/path") decorators across qBitrr/webui/**/*.py, walks paths in qBitrr/openapi.json, and fails on any route that exists in one place but not the other. Path shape is what matters; cosmetic parameter renames (e.g. {id} ↔ {entry_id}) are tolerated.
If you add or remove an endpoint, update qBitrr/openapi.json in the same commit.
Authentication¶
Bearer Token¶
When WebUI authentication is enabled, /api/* endpoints require authentication via Bearer token (or a valid session cookie for browser access):
Header:
Query Parameter (alternative):
Example:
Public Endpoints¶
The following endpoints are always public (no Bearer token or login session):
GET /health— Health checkGET /— Root redirectGET /ui— WebUI entry point (redirect)GET /login— Redirects into the WebUI login flowGET /sw.js— Service workerGET /static/*— Static assets for the WebUIPOST /web/login,POST /web/logout,POST /web/auth/set-password— Local auth flowsGET /web/auth/oidc/challenge— Starts OIDC loginGET <WebUI.OIDC.CallbackPath>— OIDC callback (default/signin-oidc; must match your IdP redirect URI)GET /web/meta— Version and auth flags (used by the WebUI before login)
Special case: GET /web/token returns the API token when auth is disabled, or when the caller is already authorized; when auth is enabled and the caller is not authorized, it responds with 401 and {"token":""} (so the SPA can detect auth without treating the response as a fatal error).
All other GET/POST /api/* and GET/POST /web/* routes (including library views, config, logs, processes, OpenAPI/Swagger URLs, and GET /api/meta) require authentication when WebUI auth is enabled.
Token Authentication¶
Authentication supports multiple formats for flexibility:
Bearer Token (recommended):
Query Parameter (for URLs):
Example:
Token Retrieval — GET /api/token (requires auth) returns the current token:
Endpoint Categories¶
- OpenAPI / Swagger UI — Interactive docs on the running server
- System - Health, status, version info
- Processes - Process monitoring and control
- Logs - Log file access
- Arr Views - Radarr/Sonarr/Lidarr library browsing
- Configuration - Config management
- Updates - Auto-update and version management
System Endpoints¶
Health Check¶
Check if WebUI is running.
Endpoint: GET /health
Authentication: None
Response:
Use Case: Monitoring, reverse proxy health checks, container liveness probes.
Root Redirect¶
Redirect to WebUI.
Endpoint: GET /
Authentication: None
Response: HTTP 302 redirect to /ui
WebUI Entry Point¶
Serve React SPA.
Endpoint: GET /ui
Authentication: None
Response: HTTP 302 redirect to /static/index.html
Headers:
Service Worker¶
Serve service worker for PWA support.
Endpoint: GET /sw.js
Authentication: None
Response: JavaScript file (text/javascript)
Headers:
Use Case: Progressive Web App functionality, offline support for static assets.
Caching behavior (client public/sw.js):
/web/*and/api/*: network-first (dynamic JSON is never served cache-first).- Static assets (JS/CSS/fonts/images under the app origin): cache-first.
- Log tail requests bypass the runtime cache.
The WebUI also keeps a short in-memory TTL on selected GETs (for example /web/status ~2s, /web/config ~30s, /web/meta ~60s unless force=1) so overlapping shell and view polls share recent responses.
System Status¶
Get qBittorrent and Arr instance statuses.
Endpoints: - GET /api/status (requires auth) - GET /web/status (public)
Response:
{
"qbit": {
"alive": true,
"host": "localhost",
"port": 8080,
"version": "4.6.0"
},
"qbitInstances": {
"default": {
"alive": true,
"host": "localhost",
"port": 8080,
"version": "4.6.0"
}
},
"arrs": [
{
"category": "radarr-4k",
"name": "Radarr-4K",
"type": "radarr",
"alive": true
},
{
"category": "sonarr-tv",
"name": "Sonarr-TV",
"type": "sonarr",
"alive": true
}
],
"webui": {
"LiveArr": true,
"Theme": "Dark",
"ViewDensity": "Comfortable"
},
"ready": true
}
Fields:
qbit- Legacy single-instance qBittorrent info (for backward compatibility)qbitInstances- Multi-instance qBittorrent info keyed by instance namearrs[].alive- Arr instance process health (checks both search and torrent processes)webui- WebUI configuration settingsready- Overall system ready state
Version Metadata¶
Get current version, latest version, update availability, and changelog.
Endpoints: - GET /api/meta (requires auth) - GET /web/meta (public)
Query Parameters: - force (boolean, optional) - Force refresh from GitHub (bypasses 1-hour cache)
Response:
{
"current_version": "5.2.0",
"latest_version": "5.3.0",
"update_available": true,
"changelog": "## What's Changed\n- Added feature X\n- Fixed bug Y",
"current_version_changelog": "## Previous Release\n- Added feature A",
"changelog_url": "https://github.com/Feramance/qBitrr/releases/tag/v5.3.0",
"repository_url": "https://github.com/Feramance/qBitrr",
"homepage_url": "https://github.com/Feramance/qBitrr",
"last_checked": "2025-11-27T12:00:00Z",
"installation_type": "pip",
"error": null,
"update_state": {
"in_progress": false,
"last_result": null,
"last_error": null,
"completed_at": null
}
}
Installation Types:
pip- Installed via pip/PyPIdocker- Running in Docker containerbinary- Standalone binarysource- Running from sourceunknown- Cannot determine
Update State:
in_progress- Manual update in progresslast_result-"success"or"error"(last update result)last_error- Error message (if last update failed)completed_at- ISO 8601 timestamp (when last update completed)
Caching: Results cached for 1 hour. Use ?force=true to bypass cache.
Process Endpoints¶
List Processes¶
Get all Arr instance processes (search and torrent loops).
Endpoints: - GET /api/processes (requires auth) - GET /web/processes (public)
Response:
{
"processes": [
{
"category": "radarr-4k",
"name": "Radarr-4K",
"kind": "search",
"pid": 12345,
"alive": true,
"rebuilding": false,
"searchSummary": "Found 12 missing movies, searched 5",
"searchTimestamp": "2025-11-27T12:00:00Z"
},
{
"category": "radarr-4k",
"name": "Radarr-4K",
"kind": "torrent",
"pid": 12346,
"alive": true,
"rebuilding": false,
"queueCount": 3,
"categoryCount": 8
}
]
}
Fields:
kind- Process type ("search"or"torrent")pid- Process ID (null if not started)alive- Process running staterebuilding- Global rebuild state (all processes restarting)
Search Process Fields:
searchSummary- Human-readable search status (e.g., "Searched 5 movies, found 2 missing")searchTimestamp- Last search activity timestamp (ISO 8601)
Torrent Process Fields:
queueCount- Active downloads in Arr queuecategoryCount- Torrents in qBittorrent with matching categoryfreeSpacePaused- Torrents tagged as paused due to free-space guard (Torrent Policy Manager only)metricType- Special metric type ("torrent-policy"for Torrent Policy Manager,"category"for PlaceHolderArr)
Refresh Interval: Poll this endpoint every 5-10 seconds for real-time updates.
Restart Process¶
Restart specific Arr instance process(es).
Endpoints: - POST /api/processes/<category>/<kind>/restart (requires auth) - POST /web/processes/<category>/<kind>/restart (public)
Path Parameters:
category(string, required) - Arr instance category (e.g.,radarr-4k)kind(string, required) - Process type:search,torrent, orall
Request: No body required
Response (Success):
Response (Error):
HTTP Status Codes:
200- Success400- Invalid kind parameter404- Unknown category503- Arr manager not ready
Behavior:
- Kills existing process(es) via
SIGTERM - Removes from child process list
- Spawns new process(es) via multiprocessing
- Returns immediately (restart is asynchronous)
Restart All Processes¶
Restart all Arr instance processes (global restart).
Endpoints: - POST /api/processes/restart_all (requires auth) - POST /web/processes/restart_all (public)
Request: No body required
Response:
Behavior: Iterates through all managed Arr instances and restarts both search and torrent processes.
Change Log Level¶
Dynamically change application log level without restart.
Endpoints: - POST /api/loglevel (requires auth) - POST /web/loglevel (public)
Request Body:
Valid Levels: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, TRACE
Response (Success):
Response (Error):
Behavior:
- Updates root logger level
- Updates all child loggers (qBitrr, qBitrr.arr, qBitrr.webui, etc.)
- Changes take effect immediately (no restart required)
- Does not persist to config file
Rebuild Arr Databases¶
Trigger database rebuild for all Arr instances.
Endpoints: - POST /api/arr/rebuild (requires auth) - POST /web/arr/rebuild (public)
Request: No body required
Response:
Behavior:
- Spawns background thread
- Restarts all Arr instances sequentially
- Triggers
db_update()for each instance - Refreshes cached library data from Arr APIs
- Returns immediately (rebuild is asynchronous)
Use Case: Force refresh after bulk library changes in Radarr/Sonarr/Lidarr.
Restart Arr Instance¶
Restart specific Arr instance (both search and torrent processes).
Endpoints: - POST /api/arr/<section>/restart (requires auth) - POST /web/arr/<section>/restart (public)
Path Parameters:
section(string, required) - Arr instance key (e.g.,Radarr-4K,Sonarr-TV)
Request: No body required
Response (Success):
Response (Error)**:
HTTP Status Codes:
200- Success404- Unknown section503- Arr manager not ready
Open Arr Item in Arr UI¶
Open a specific Radarr/Sonarr/Lidarr item in its native Arr web interface.
Endpoints: - GET /api/arr/<category>/open/<kind>/<entry_id> (requires auth) - GET /web/arr/<category>/open/<kind>/<entry_id> (public)
Path Parameters:
category(string, required) - Arr instance category key (for exampleradarr-4k)kind(string, required) - Target item type:movie,series, orartistentry_id(integer, required) - Arr item id in that instance
Response (Success): HTTP 302 redirect to the Arr UI page.
Route Resolution Behavior:
- qBitrr first resolves the managed Arr instance by
categoryand validateskind. - qBitrr fetches the item from that Arr API using
entry_id. - qBitrr then builds an app-native URL token:
- Radarr: prefers
titleSlug, falls back to numericentry_id - Sonarr: prefers
titleSlug, falls back to numericentry_id - Lidarr: prefers
foreignArtistId(thentitleSlug), falls back to numericentry_id
Item Targets:
movie->.../movie/<route-token>(Radarr)series->.../series/<route-token>(Sonarr)artist->.../artist/<route-token>(Lidarr)
Response (Error):
HTTP Status Codes:
302- Redirect to Arr UI item page400- Unknown item kind or missing Arr URI401- Unauthorized404- Unknown Arr section/type mismatch or item lookup failure503- Arr manager not ready
Get qBit Categories¶
Get qBittorrent categories managed by qBitrr (qBit-managed and Arr-managed) with seeding statistics.
Endpoint: - GET /web/qbit/categories (public only; no /api/ variant)
Authentication: Requires Bearer token or WebUI session when authentication is enabled (WebUI.AuthDisabled = false). Same token gate as other protected /web/* routes.
Response: Array of category objects, each including: - category - Category name - instance - qBittorrent instance name - managedBy - "qbit" or "arr" - torrentCount, seedingCount, totalSize, avgRatio, avgSeedingTime - seedingConfig - Per-category seeding limits (e.g. maxRatio, maxTime, removeMode, downloadLimit, uploadLimit)
Use Case: Category management UI, seeding stats display (Processes chips).
Get qBit Overview¶
Get monitored qBittorrent categories (qBit-managed and Arr-managed) with per-torrent transfer details for the WebUI overview tab.
Endpoint: - GET /web/qbit/overview (public only; no /api/ variant)
Authentication: Requires Bearer token or WebUI session when authentication is enabled (WebUI.AuthDisabled = false). Same token gate as other protected /web/* routes.
Query Parameters: - instance (optional) - qBittorrent instance name (e.g. qBit). Omit or pass all to include every configured client.
Response:
{
"instances": ["qBit"],
"categories": [
{
"category": "movies",
"qbitInstance": "qBit",
"managedBy": "arr",
"arrName": "Radarr",
"torrentCount": 2,
"seedingCount": 1,
"totalSize": 123456789,
"avgRatio": 1.2,
"avgSeedingTime": 3600,
"seedingConfig": {
"maxRatio": -1,
"maxTime": -1,
"removeMode": -1,
"downloadLimit": -1,
"uploadLimit": -1
},
"torrents": [
{
"hash": "…",
"name": "Example",
"state": "uploading",
"progress": 1.0,
"size": 1000,
"dlspeed": 0,
"upspeed": 50,
"ratio": 1.5,
"tags": ["qbitrr"]
}
]
}
],
"ready": true
}
qBit-managed categories are scoped to a single qBit client (qbitInstance). Arr-managed categories appear once per Arr (torrents aggregated across the in-scope client(s); qbitInstance is the selected client, or "all" when viewing every client). Each category may include torrentsTruncated: true when the torrent list was capped server-side. Torrent objects use camelCase fields aligned with qBittorrent torrents/info (hash, name, state, progress, speeds, peers, limits, etc.). Path and tracker fields are omitted from this payload.
Use Case: qBittorrent WebUI tab — instance picker + collapsible category sections with List-style torrent rows.
Get Torrent Distribution¶
Get torrent distribution statistics across all categories.
Endpoints: - GET /api/torrents/distribution (requires auth) - GET /web/torrents/distribution (public)
Response:
{
"distribution": {
"radarr-4k": {
"default": 50
},
"sonarr-tv": {
"default": 30
},
"uncategorized": {
"default": 5
}
}
}
Fields:
distribution- Object keyed by category name, containing objects keyed by qBit instance name with torrent counts- Each inner object maps qBit instance names to torrent counts for that category
Log Endpoints¶
All log routes require authentication when WebUI auth is enabled (/api/* Bearer or /web/* session). See Logs View for UI behavior.
List Log Files¶
Endpoints: GET /api/logs, GET /web/logs
Response:
On restart, active logs are renamed to *.log.old (not size-based numbered rotation).
Get Log Content (tail / delta)¶
Endpoints: GET /api/logs/<name>, GET /web/logs/<name>
Query parameters:
| Param | Description |
|---|---|
format=json | Return JSON LogTailPayload |
lines | Tail window (default 2000, max 50000) |
offset | Skip N lines from end (older chunks) |
since_bytes | Incremental append from byte offset (implies JSON) |
inode | Detect rotation when inode changes |
around_line | Window centered on 1-based line number |
JSON response (format=json or since_bytes):
{
"content": "...",
"next_bytes": 123456,
"size": 123456,
"inode": 42,
"rotated": false,
"truncated": false
}
Legacy callers without format=json still receive text/plain (full file or lines/offset tail).
Example:
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:6969/api/logs/Main.log?format=json&lines=2000"
Live log SSE stream¶
Endpoints: GET /api/logs/<name>/stream, GET /web/logs/<name>/stream
Query: since_bytes, inode, lines
Response: text/event-stream with events append, rotated, ping, reconnect.
Browsers should use /web/logs/<name>/stream with a session cookie (EventSource cannot set Authorization). Connections auto-close after ~5 minutes with a reconnect event; the client reconnects with the last cursor.
Search log file¶
Endpoints: GET /api/logs/<name>/search, GET /web/logs/<name>/search
Query: q (required), case, regex, max_matches, context, include_rotated
Scans the selected file and optional rotated siblings (name*) line-by-line with match/byte/time caps.
Download Log File¶
Endpoints: GET /api/logs/<name>/download, GET /web/logs/<name>/download
Response: File attachment (Content-Disposition: attachment).
Example:
Arr View Endpoints¶
Arr poster thumbnails (cached)¶
Read-only image bytes for browse Icon tiles and detail modals. qBitrr prefers deterministic same-host MediaCover URLs (poster-250 / poster-500 before full-size), then falls back to the Arr entity JSON image list. Only images on that Arr host are used; external CDN metadata is ignored. Downloaded bytes are resized (longest edge 250px), encoded as WebP (JPEG fallback), and stored under the qBitrr data directory with strong ETags. Parallel requests for the same entry share a single fetch (single-flight). Cache key version v3 — older full-size cache files are not reused. If no same-host image exists, the route returns 404 and the WebUI shows a built-in placeholder.
Endpoints (each has a /api and /web mirror; behavior is identical):
| Method | Path pattern |
|---|---|
GET | /api/radarr/<category>/movie/<id>/thumbnail · /web/radarr/<category>/movie/<id>/thumbnail |
GET | /api/sonarr/<category>/series/<id>/thumbnail · /web/sonarr/<category>/series/<id>/thumbnail |
GET | /api/lidarr/<category>/artist/<id>/thumbnail · /web/lidarr/<category>/artist/<id>/thumbnail |
Parameters: category is the qBitrr Arr instance category; id is the Arr database id for that entity (movie, series, or Lidarr artist). Optional ?token=<WebUI.Token> remains accepted for non-browser clients; the WebUI Icon view uses session-cookie auth without embedding the token in <img src>.
Responses: 200 with an image body (or 304 when If-None-Match matches), 401 if unauthorized, 404 if the entity or image cannot be resolved. Successful responses use Cache-Control: private, max-age=86400.
These routes are listed in qBitrr/openapi.json (both /api and /web variants).
Radarr Movies¶
Browse Radarr movie library from cached database.
Endpoints: - GET /api/radarr/<category>/movies (requires auth) - GET /web/radarr/<category>/movies (public)
Path Parameters:
category(string, required) - Radarr instance category (e.g.,radarr-4k)
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
q | string | null | Search query (title substring match) |
page | integer | 0 | Page number (0-indexed) |
page_size | integer | 50 | Results per page (1-100) |
year_min | integer | null | Minimum release year filter |
year_max | integer | null | Maximum release year filter |
monitored | boolean | null | Filter by monitored status |
has_file | boolean | null | Filter by file availability |
quality_met | boolean | null | Filter by quality profile met |
is_request | boolean | null | Filter by request status (Ombi/Overseerr) |
Response:
{
"category": "radarr-4k",
"counts": {
"available": 120,
"monitored": 150,
"missing": 30,
"quality_met": 100,
"requests": 5
},
"total": 150,
"page": 0,
"page_size": 50,
"movies": [
{
"id": 1,
"title": "Inception",
"year": 2010,
"monitored": true,
"hasFile": true,
"movieFileId": 12345,
"tmdbId": 27205,
"imdbId": "tt1375666",
"qualityMet": true,
"isRequest": false,
"upgrade": false,
"customFormatScore": 0,
"minCustomFormatScore": 0,
"customFormatMet": true,
"reason": null,
"qualityProfileId": 1,
"qualityProfileName": "Ultra-HD"
}
]
}
Fields:
counts- Aggregate totals (unaffected by pagination)total- Total movies matching filterspage/page_size- Pagination echomovies[].reason- Why entry appears in search results (e.g., "Missing", "Quality Unmet")
Performance: Uses cached database (SQLite). For real-time data, enable WebUI.LiveArr = true (increases Arr API load).
Sonarr Series¶
Browse Sonarr series library from cached database.
Endpoints: - GET /api/sonarr/<category>/series (requires auth) - GET /web/sonarr/<category>/series (public)
Path Parameters:
category(string, required) - Sonarr instance category
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
q | string | null | Search query (series title) |
page | integer | 0 | Page number |
page_size | integer | 25 | Results per page |
missing / only_missing | boolean | false | Show only missing episodes |
Response:
{
"category": "sonarr-tv",
"counts": {
"available": 500,
"monitored": 600,
"missing": 100
},
"total": 50,
"page": 0,
"page_size": 25,
"series": [
{
"series": {
"id": 1,
"title": "Breaking Bad",
"tvdbId": 81189,
"seriesType": "standard",
"monitored": true,
"qualityProfileId": 1,
"qualityProfileName": "HD-1080p"
},
"totals": {
"available": 62,
"monitored": 62,
"missing": 0
},
"seasons": [
{
"seasonNumber": 1,
"available": 7,
"monitored": 7,
"missing": 0,
"episodes": [
{
"id": 101,
"episodeNumber": 1,
"title": "Pilot",
"airDateUtc": "2008-01-20T00:00:00Z",
"hasFile": true,
"episodeFileId": 1001,
"monitored": true
}
]
}
]
}
]
}
Grouping: Episodes grouped by series → season (hierarchical structure).
Lidarr Artists¶
Browse Lidarr artist library from the cached database. Mirrors Radarr/Sonarr filtering: a Status (missing only) and Search Reason filter, scoped to the artists' albums.
Endpoints: - GET /api/lidarr/<category>/artists (requires auth) - GET /web/lidarr/<category>/artists (public)
Path Parameters:
category(string, required) – Lidarr instance category. The literal stringlidarrresolves to the single configured Lidarr instance when only one exists.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
q | string | null | Search query (artist name) |
page | integer | 0 | Page number |
page_size | integer | 50 | Results per page (alias size accepted) |
monitored | boolean | null | Filter by monitored artist |
missing | boolean | false | Restrict to artists with at least one monitored album whose file is missing |
reason | string | all | Restrict to artists with at least one album whose Reason matches. Accepted values: Missing, Quality, CustomFormat, Upgrade, Not being searched (also matches NULL). Use all (or omit) for no reason filter. |
The missing and reason filters are applied at the album level via an EXISTS-style subquery on AlbumFilesModel; total and pagination reflect the filtered artist set, so the UI shows accurate counts.
Lidarr Albums¶
Browse Lidarr album library from cached database.
Endpoints (dual_route — /api and /web mirrors; behavior is identical): - GET /api/lidarr/<category>/albums (requires auth) - GET /web/lidarr/<category>/albums (public / session)
Path Parameters:
category(string, required) - Lidarr instance category
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
q | string | null | Search query (artist or album name) |
page | integer | 0 | Page number |
page_size | integer | 25 | Results per page |
monitored | boolean | null | Filter by monitored status |
has_file | boolean | null | Filter by file availability |
Response:
{
"category": "lidarr",
"counts": {
"available": 200,
"monitored": 250,
"missing": 50,
"quality_met": 180,
"requests": 2
},
"total": 250,
"page": 0,
"page_size": 25,
"albums": [
{
"album": {
"id": 1,
"title": "Dark Side of the Moon",
"artistId": 1,
"artistName": "Pink Floyd",
"monitored": true,
"hasFile": true,
"foreignAlbumId": "12345",
"releaseDate": "1973-03-01",
"qualityMet": true,
"isRequest": false,
"upgrade": false,
"customFormatScore": 0,
"minCustomFormatScore": 0,
"customFormatMet": true,
"reason": null,
"qualityProfileId": 1,
"qualityProfileName": "Lossless"
},
"totals": {
"available": 10,
"monitored": 10,
"missing": 0
},
"tracks": [
{
"id": 101,
"trackNumber": 1,
"title": "Speak to Me",
"duration": 70,
"hasFile": true,
"trackFileId": 1001,
"monitored": true,
"reason": "Not being searched"
}
]
}
]
}
Grouping: Tracks nested within albums.
Track reason: Each object in tracks includes a string reason derived for that row (for example Missing, Unmonitored, Not being searched, or album-level states such as Quality when applicable). Older responses may omit it; clients can fall back to the parent album reason.
List Arr Instances¶
Get all configured Arr instances.
Endpoints: - GET /api/arr (requires auth) - GET /web/arr (public)
Response:
{
"arr": [
{
"category": "radarr-4k",
"name": "Radarr-4K",
"type": "radarr"
},
{
"category": "sonarr-tv",
"name": "Sonarr-TV",
"type": "sonarr"
},
{
"category": "lidarr",
"name": "Lidarr",
"type": "lidarr"
}
],
"ready": true
}
Fields:
arr- Array of Arr instancesarr[].category- qBittorrent category (used in API paths)arr[].name- Friendly display namearr[].type- Arr type (radarr,sonarr,lidarr)ready- Overall system ready state
Configuration Endpoints¶
Get Configuration¶
Fetch current configuration from disk.
Endpoints: - GET /api/config (requires auth) - GET /web/config (public)
Response:
{
"Settings": {
"ConsoleLevel": "INFO",
"Logging": true,
"CompletedDownloadFolder": "/mnt/downloads",
"FreeSpace": "100G",
"AutoUpdateEnabled": true
},
"WebUI": {
"Host": "0.0.0.0",
"Port": 6969,
"Token": "abc123...",
"LiveArr": false,
"Theme": "Dark"
},
"qBit": {
"Host": "localhost",
"Port": 8080,
"UserName": "admin",
"Password": "***"
},
"Radarr-4K": {
"Managed": true,
"URI": "http://localhost:7878",
"APIKey": "***",
"Category": "radarr-4k"
}
}
Behavior:
- Reloads config from disk (always fresh)
- Returns entire config tree as JSON
- Converts TOML to JSON-compatible structure
Public Endpoint: /web/config includes config version mismatch warnings:
{
"config": { ... },
"warning": {
"type": "config_version_mismatch",
"message": "Config version 1 is outdated (current: 2)",
"currentVersion": 1
}
}
Update Configuration¶
Apply changes to configuration and trigger reload.
Endpoints: - POST /api/config (requires auth) - POST /web/config (public)
Request Body:
{
"changes": {
"Settings.LoopSleepTimer": 60,
"Radarr-4K.EntrySearch.SearchLimit": 10,
"WebUI.Theme": "Dark"
}
}
Dotted Key Format: Use dot notation for nested keys (e.g., Radarr-4K.Torrent.AutoDelete).
Deletion: Set value to null to delete key:
Response (Success):
{
"status": "ok",
"configReloaded": true,
"reloadType": "single_arr",
"affectedInstances": ["Radarr-4K"]
}
Reload Types (classified by qBitrr/config_reload_policy.py; see also Config file → Live config reload and Config editor):
| Type | Description | Behavior |
|---|---|---|
frontend | Frontend-only changes | No reload (e.g., WebUI.Theme) |
live | Global or Arr loop settings | No full Arr respawn/DB wipe; LIVE attrs via _sync_loop_settings_from_config → _apply_arr_live_attrs_from_config; supervisor may start/stop the search worker when SearchMissing changes |
qbit_hot | qBit category seeding | Refresh in-memory qBit category managers without respawn |
webui | WebUI server settings | Host/Port rebind Waitress; Token/UrlBase soft-apply when possible |
single_arr | One Arr instance | Respawn that instance (preserve or reset search DB depending on keys) |
multi_arr | Multiple Arr instances | Respawn each affected instance sequentially |
full | Global / PlaceHolder rebuild | Reload all components — PlaceHolder renames (FailedCategory / RecheckCategory) preserve Arr search DBs; other full keys wipe them |
Response (Validation Error):
{
"error": "Please resolve the following issues:\nWebUI.Port: WebUI Port must be between 1 and 65535."
}
Response (Protected Key Error):
HTTP Status Codes:
200- Success400- Invalid request body403- Protected key modification attempt500- Save failure
Protected Keys:
Settings.ConfigVersion- Managed automatically by migration system
Test Arr Connection¶
Test connection to Arr instance without saving configuration.
Endpoints: - POST /api/arr/test-connection (requires auth) - POST /web/arr/test-connection (public)
Request Body: Either credentials or an instance key:
-
By credentials (e.g. new instance or form has real values):
-
By instance key (when API key is redacted in the UI; backend uses stored config):
WheninstanceKeyis present,uriandapiKeyare not required.
Valid Arr Types: radarr, sonarr, lidarr
Response (Success):
{
"success": true,
"version": "4.3.2.6857",
"qualityProfiles": [
{
"id": 1,
"name": "HD-1080p"
},
{
"id": 4,
"name": "Ultra-HD"
}
]
}
Response (Failure):
HTTP Status Codes:
200- Connection test completed (checksuccessfield)400- Missing required fields500- Unexpected error
Use Case: Validate Arr credentials before saving config changes.
Update Endpoints¶
Trigger Manual Update¶
Trigger qBitrr self-update (pip or binary).
Endpoints: - POST /api/update (requires auth) - POST /web/update (public)
Request: No body required
Response (Success):
Response (Already Running):
HTTP Status Codes:
200- Update started409- Update already in progress
Behavior:
- Spawns background thread
- Runs
pip install --upgrade qBitrr2(pip) or downloads binary (binary) - Restarts qBitrr on success
- Returns immediately (update is asynchronous)
Monitoring: Poll GET /api/meta to check update_state.in_progress and update_state.last_result.
Download Binary Update¶
Redirect to GitHub binary download URL for current platform.
Endpoints: - GET /api/download-update (requires auth) - GET /web/download-update (public)
Response (Success): HTTP 302 redirect to GitHub asset URL
Response (Not Binary):
Response (No Update):
Response (No Binary):
HTTP Status Codes:
302- Redirect to download400- Not a binary installation404- No update or no binary available
Use Case: Manual binary download for offline update.
Error Responses¶
Standard Error Format¶
All errors return JSON with an error field:
Common HTTP Status Codes¶
| Code | Meaning | Common Causes |
|---|---|---|
200 | Success | Request completed successfully |
302 | Redirect | Root endpoint, binary download |
400 | Bad Request | Invalid parameters, malformed JSON |
401 | Unauthorized | Missing or invalid token |
403 | Forbidden | Protected key modification |
404 | Not Found | Unknown category, missing log file |
409 | Conflict | Update already in progress |
500 | Server Error | Config save failure, unexpected exception |
503 | Service Unavailable | Arr manager not ready |
Rate Limiting¶
No rate limiting is enforced by default. External reverse proxies (e.g., Nginx, Traefik) should implement rate limiting if exposed publicly.
CORS¶
Cross-Origin Requests: Not explicitly configured. CORS headers are not set by default. Configure reverse proxy to add CORS headers if needed:
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "Authorization, Content-Type";
WebSocket Support¶
Not supported. Use HTTP polling for real-time updates:
GET /api/processes- Poll about every 2 seconds while the Processes tab is activeGET /api/meta- Quiet poll about every 5 minutes (forced on tab visibility / update UI); server may cache longerGET /api/status- Poll about every 15 seconds for Arr tab visibility- Arr catalog endpoints - Poll about every 15 seconds while the Arr tab is active and
WebUI.LiveArris enabled
Pagination¶
Arr view endpoints support pagination via page and page_size parameters:
Example:
# Page 1 (first 50 results)
curl "http://localhost:6969/web/radarr/radarr-4k/movies?page=0&page_size=50"
# Page 2 (next 50 results)
curl "http://localhost:6969/web/radarr/radarr-4k/movies?page=1&page_size=50"
Response:
Calculating Pages:
Filtering¶
Arr view endpoints support multiple filters:
Example (Radarr):
# Missing 4K movies from 2020-2023
curl "http://localhost:6969/web/radarr/radarr-4k/movies?has_file=false&year_min=2020&year_max=2023&monitored=true"
Filters are cumulative (AND logic). All specified filters must match.
Best Practices¶
- Use
/web/*endpoints for WebUI to avoid token management - Use
/api/*endpoints for external clients with Bearer token - Cache
/api/metaresponses for 1 hour to reduce GitHub API load - Poll
/api/processesevery 5-10 seconds (not faster to avoid overhead) - Enable
WebUI.LiveArronly when real-time Arr data is required (increases API load) - Set
WebUI.Tokenwhen exposing WebUI publicly - Use reverse proxy for HTTPS, rate limiting, and authentication
- Monitor update state via
/api/metaafter triggering/api/update - Test Arr connections via
/api/arr/test-connectionbefore saving config - Page Arr views to avoid memory issues with large libraries (use
page_size=50)
Example: Python Client¶
import requests
class QbitrrClient:
def __init__(self, base_url, token=None):
self.base_url = base_url
self.token = token
def _headers(self):
if self.token:
return {"Authorization": f"Bearer {self.token}"}
return {}
def get_processes(self):
url = f"{self.base_url}/api/processes"
response = requests.get(url, headers=self._headers())
response.raise_for_status()
return response.json()
def restart_process(self, category, kind):
url = f"{self.base_url}/api/processes/{category}/{kind}/restart"
response = requests.post(url, headers=self._headers())
response.raise_for_status()
return response.json()
def get_radarr_movies(self, category, page=0, page_size=50, has_file=None):
url = f"{self.base_url}/api/radarr/{category}/movies"
params = {"page": page, "page_size": page_size}
if has_file is not None:
params["has_file"] = str(has_file).lower()
response = requests.get(url, params=params, headers=self._headers())
response.raise_for_status()
return response.json()
# Usage
client = QbitrrClient("http://localhost:6969", token="abc123...")
processes = client.get_processes()
print(f"Found {len(processes['processes'])} processes")
movies = client.get_radarr_movies("radarr-4k", has_file=False)
print(f"Missing movies: {movies['counts']['missing']}")
Example: cURL Commands¶
Get Processes¶
Restart Arr Instance¶
Get Radarr Movies (Missing Only)¶
Update Configuration¶
curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer abc123..." \
-d '{"changes": {"Settings.LoopSleepTimer": 60}}' \
http://localhost:6969/api/config
Trigger Update¶
Related Pages¶
- Configuration Editor - WebUI config management
- Processes Page - Process monitoring interface
- Logs Page - Log viewing interface
- Arr Views - Library browsing interface
- WebUI Overview - Introduction to WebUI
See Also¶
- Configuration File Reference - Manual TOML editing
- First Run Guide - Initial setup
- Troubleshooting - Common issues