Complete API reference for qBitrr's WebUI REST API. All endpoints are accessible via the built-in WebUI server (default port 6969).
http://<host>:<port>
Default: http://localhost:6969
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).
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.
Every Flask route registered in qBitrr/webui.py should appear under paths in qBitrr/openapi.json (and vice versa). A static drift check is provided so the two files cannot diverge silently:
make openapi-check # or: python scripts/openapi_check.pyThe check is also wired into pre-commit (local hook openapi-check). It parses the @app.<method>("/path") decorators in qBitrr/webui.py, walks paths in qBitrr/openapi.json, and fails on any route that exists in one file 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.
When WebUI authentication is enabled, /api/* endpoints require authentication via Bearer token (or a valid session cookie for browser access):
Header:
Authorization: Bearer <token>Query Parameter (alternative):
?token=<token>
Example:
curl -H "Authorization: Bearer abc123..." http://localhost:6969/api/processesThe 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.
Authentication supports multiple formats for flexibility:
Bearer Token (recommended):
Authorization: Bearer <token>Query Parameter (for URLs):
?token=<token>
Example:
curl -H "Authorization: Bearer abc123..." http://localhost:6969/api/processesToken Retrieval — GET /api/token (requires auth) returns the current token:
{"token": "<your-token>"}- 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
Check if WebUI is running.
Endpoint: GET /health
Authentication: None
Response:
{
"status": "ok"
}Use Case: Monitoring, reverse proxy health checks, container liveness probes.
Redirect to WebUI.
Endpoint: GET /
Authentication: None
Response: HTTP 302 redirect to /ui
Serve React SPA.
Endpoint: GET /ui
Authentication: None
Response: HTTP 302 redirect to /static/index.html
Headers:
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0Serve service worker for PWA support.
Endpoint: GET /sw.js
Authentication: None
Response: JavaScript file (text/javascript)
Headers:
Cache-Control: no-cache, no-store, must-revalidateUse Case: Progressive Web App functionality, offline support.
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,
"GroupSonarr": true,
"GroupLidarr": 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
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.
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 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):
{
"status": "ok",
"restarted": ["search", "torrent"]
}Response (Error):
{
"error": "Unknown category radarr-4k"
}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 Arr instance processes (global restart).
Endpoints:
POST /api/processes/restart_all(requires auth)POST /web/processes/restart_all(public)
Request: No body required
Response:
{
"status": "ok"
}Behavior: Iterates through all managed Arr instances and restarts both search and torrent processes.
Dynamically change application log level without restart.
Endpoints:
POST /api/loglevel(requires auth)POST /web/loglevel(public)
Request Body:
{
"level": "DEBUG"
}Valid Levels: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, TRACE
Response (Success):
{
"status": "ok",
"level": "DEBUG"
}Response (Error):
{
"error": "Invalid log level"
}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
Trigger database rebuild for all Arr instances.
Endpoints:
POST /api/arr/rebuild(requires auth)POST /web/arr/rebuild(public)
Request: No body required
Response:
{
"status": "started"
}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 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):
{
"status": "ok",
"restarted": ["search", "torrent"]
}Response (Error)**:
{
"error": "Unknown section Radarr-4K"
}HTTP Status Codes:
200- Success404- Unknown section503- Arr manager not ready
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
- Radarr: prefers
Item Targets:
movie->.../movie/<route-token>(Radarr)series->.../series/<route-token>(Sonarr)artist->.../artist/<route-token>(Lidarr)
Response (Error):
{
"error": "Unknown sonarr section sonarr-tv"
}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 qBittorrent categories managed by qBitrr (qBit-managed and Arr-managed) with seeding statistics.
Endpoint:
GET /web/qbit/categories(public only; no/api/variant)
Authentication: None (public endpoint).
Response: Array of category objects, each including:
category- Category nameinstance- qBittorrent instance namemanagedBy-"qbit"or"arr"torrentCount,seedingCount,totalSize,avgRatio,avgSeedingTimeseedingConfig- Per-category seeding limits (e.g.maxRatio,maxTime,removeMode,downloadLimit,uploadLimit)
Use Case: Category management UI, seeding stats display.
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
Get all available log files.
Endpoints:
GET /api/logs(requires auth)GET /web/logs(public)
Response:
{
"files": ["Main.log", "WebUI.log"]
}Fields:
files- Array of log filenames (use with/api/logs/<filename>to get content)
Log Rotation: Log files rotate at 10 MB, keeping 5 backups. Older backups appear as Main.log.1, Main.log.2, etc.
Stream log file content as plain text.
Endpoints:
GET /api/logs/<name>(requires auth)GET /web/logs/<name>(public)
Path Parameters:
name(string, required) - Log filename (e.g.,Main.log)
Response: Plain text (MIME type: text/plain; charset=utf-8)
Headers:
Cache-Control: no-cache
Content-Type: text/plain; charset=utf-8Example:
curl http://localhost:6969/web/logs/Main.logBehavior:
- Reads entire log file into memory
- Returns full content (supports dynamic loading in LazyLog component)
- Invalid UTF-8 sequences ignored (errors="ignore")
Download log file as attachment.
Endpoints:
GET /api/logs/<name>/download(requires auth)GET /web/logs/<name>/download(public)
Path Parameters:
name(string, required) - Log filename
Response: File attachment (MIME type: application/octet-stream)
Headers:
Content-Disposition: attachment; filename="Main.log"Example:
curl -O http://localhost:6969/web/logs/Main.log/downloadRead-only image bytes for browse Icon tiles and detail modals. The server asks each Arr instance for the entity (movie, series, or Lidarr artist) and only serves images from that same Arr host (for example MediaCover paths under the Arr base URL). Metadata that points at external CDNs is ignored; if no same-host image exists, the route returns 404 and the WebUI shows a built-in placeholder. qBitrr downloads bytes from the Arr URL using the instance API key, caches them under the qBitrr data directory, and returns them with long-cache headers when possible. For Lidarr artists, when the JSON payload has no usable same-host URL, qBitrr may probe MediaCover paths on that Arr host before giving up. Cache files written before this policy may still contain older bytes until cleared.
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> works for <img src> when not using a session cookie.
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.
These routes are listed in qBitrr/openapi.json (both /api and /web variants).
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).
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).
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.
Browse Lidarr album library from cached database.
Endpoints:
GET /web/lidarr/<category>/albums(public)
Note: There is no /api/lidarr/<category>/albums endpoint. Use /web/lidarr/<category>/albums instead.
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.
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
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,
"GroupSonarr": true,
"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
}
}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:
{
"changes": {
"WebUI.Token": null
}
}Response (Success):
{
"status": "ok",
"configReloaded": true,
"reloadType": "single_arr",
"affectedInstances": ["Radarr-4K"]
}Reload Types:
| Type | Description | Behavior |
|---|---|---|
frontend |
Frontend-only changes | No reload (e.g., WebUI.Theme) |
webui |
WebUI server settings | Restart WebUI server |
single_arr |
One Arr instance | Reload that instance only |
multi_arr |
Multiple Arr instances | Reload each instance sequentially |
full |
Global settings | Reload all components |
Response (Validation Error):
{
"error": "Please resolve the following issues:\nWebUI.Port: WebUI Port must be between 1 and 65535."
}Response (Protected Key Error):
{
"error": "Cannot modify protected configuration key: Settings.ConfigVersion"
}HTTP Status Codes:
200- Success400- Invalid request body403- Protected key modification attempt500- Save failure
Protected Keys:
Settings.ConfigVersion- Managed automatically by migration system
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):
{
"arrType": "radarr",
"uri": "http://localhost:7878",
"apiKey": "abc123..."
}- By instance key (when API key is redacted in the UI; backend uses stored config):
{
"arrType": "radarr",
"instanceKey": "Radarr-Movies"
}When instanceKey is present, uri and apiKey are 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):
{
"success": false,
"message": "Connection refused"
}HTTP Status Codes:
200- Connection test completed (checksuccessfield)400- Missing required fields500- Unexpected error
Use Case: Validate Arr credentials before saving config changes.
Trigger qBitrr self-update (pip or binary).
Endpoints:
POST /api/update(requires auth)POST /web/update(public)
Request: No body required
Response (Success):
{
"status": "started"
}Response (Already Running):
{
"error": "An update is already in progress."
}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.
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):
{
"error": "Download only available for binary installations"
}Response (No Update):
{
"error": "No update available"
}Response (No Binary):
{
"error": "No binary available for your platform"
}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.
All errors return JSON with an error field:
{
"error": "Error message"
}| 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 |
No rate limiting is enforced by default. External reverse proxies (e.g., Nginx, Traefik) should implement rate limiting if exposed publicly.
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";Not supported. Use HTTP polling for real-time updates:
GET /api/processes- Poll every 5-10 secondsGET /api/meta- Poll every 60 seconds (cached for 1 hour)GET /api/status- Poll every 10-30 seconds
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:
{
"total": 150,
"page": 0,
"page_size": 50,
"movies": [ ... ]
}Calculating Pages:
const totalPages = Math.ceil(response.total / response.page_size);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.
- 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)
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']}")curl -H "Authorization: Bearer abc123..." \
http://localhost:6969/api/processescurl -X POST \
http://localhost:6969/web/processes/radarr-4k/all/restartcurl "http://localhost:6969/web/radarr/radarr-4k/movies?has_file=false&monitored=true"curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer abc123..." \
-d '{"changes": {"Settings.LoopSleepTimer": 60}}' \
http://localhost:6969/api/configcurl -X POST \
http://localhost:6969/web/update- 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
- Configuration File Reference - Manual TOML editing
- First Run Guide - Initial setup
- Troubleshooting - Common issues