Repository navigation
API
Everything the wranglarr UI does is a JSON call to its own HTTP API. Scripts, Home Assistant, cron jobs and monitoring can make the same calls. This page lists every address, what it takes and what it answers.
-
Base URL:
http://<host>:8787(the port isWRANGLARR_PORTin.env), or wranglarr's address behind your reverse proxy. -
JSON in, JSON out. Send a body as JSON with
Content-Type: application/json. Two addresses answer with something else: an app's logo (an image) and the AuthTunnel gate (an empty answer). -
Times are ISO 8601 in UTC. A backup version id is its start time,
YYYYMMDDTHHMMSSZ(with-1,-2… added when two start in the same second). -
Query flags such as
refreshtaketrue/falseor1/0.
This page is the API's reference; wranglarr serves no generated schema.
Every address needs a sign-in, except the few listed under Without a sign-in. There are two kinds of sign-in.
Create one under Account → API keys (needs your password). The key starts
with wr_, is shown once, and only its hash is stored. Send it in the
X-Api-Key header. It is not accepted as a query parameter or as a bearer
token.
export WR=http://nas:8787 KEY=wr_...
curl -fsS -H "X-Api-Key: $KEY" "$WR/api/suites"
curl -fsS -X POST -H "X-Api-Key: $KEY" "$WR/api/suites/media/backup" # -> {"run": 42}A request with an API key needs no Origin header. A key can do everything on
this page except the account itself: the password, two-factor, passkeys, API
keys and sessions answer 403 "this needs a signed-in browser, not an API
key". Revoke on the Account page stops a key at once. See
Login.
Signing in sets the wranglarr_session cookie (HttpOnly, SameSite=Strict, or
Lax with AuthTunnel; Secure over HTTPS). It lasts 24 hours, or 30 days with
Remember me. A script can sign in the same way, but an API key is simpler:
curl -fsS -c jar -H "Origin: $WR" -H "Content-Type: application/json" \
-d '{"username":"admin","password":"..."}' "$WR/api/auth/login"
curl -fsS -b jar -H "Origin: $WR" -X POST "$WR/api/suites/media/backup"With two-factor on, the login answers {"mfa": true} and sets a short-lived
wranglarr_mfa cookie. Send the code to POST /api/auth/login/totp with the
same cookie jar to get the session.
A request signed in with a session cookie that changes something (any
method but GET, HEAD and OPTIONS) must come from wranglarr's own pages. The
Origin header (or, without one, Referer) must name the host the request
was sent to: the Host header, or X-Forwarded-Host from a trusted proxy. Otherwise the
answer is 403 with
{"error": "refused: this request didn't come from wranglarr's own pages"}.
Browsers send Origin on their own. A non-browser client using a session
cookie must send Origin: http://<the host:port it calls>. The same check
applies to the sign-in POSTs below, which work without a session. Requests
with an API key are not checked.
Behind a reverse proxy, preserve Host or set X-Forwarded-Host, and set
X-Forwarded-Proto. Configure the proxy's exact source IP in
FORWARDED_ALLOW_IPS (default 127.0.0.1); untrusted peers' forwarded host
and scheme are ignored. See Login for the trust and port
access requirements.
| Address | Notes |
|---|---|
GET /api/health |
answers {"ok": true} only; the details need a sign-in |
GET /api/auth/gate |
AuthTunnel's check, see Sign-in and account |
POST /api/auth/setup |
creating the account (needs the setup code from the log) |
POST /api/auth/login, POST /api/auth/login/totp
|
password sign-in |
GET /api/auth/passkey/login-options, POST /api/auth/passkey/login
|
passkey sign-in |
GET /login, GET /setup
|
the sign-in pages |
/static/… |
stylesheets, scripts, icons |
Any other /api/ address without a sign-in answers 401
{"error": "sign in first (or send an X-Api-Key header)"}. A page redirects to
/login (or to /setup while there is no account yet).
With WRANGLARR_AUTH=off in .env there is no login: every address answers
without a key or cookie. A change that carries an Origin or Referer header
naming another site is still refused, so other web pages can't drive wranglarr;
scripts that send neither header are let through. The account
endpoints answer 403, and GET /api/auth/me answers {"enabled": false}.
Only do this behind a reverse proxy that asks for a login itself
(Login).
Backups, restores, updates, rollbacks, app connections, indexer syncs and
shared-settings syncs run in the background. Their POST answers 202 with
the run's id:
{"run": 42}Ask GET /api/runs/{id} until status is no longer queued or running:
{
"id": 42, "suite": "media", "kind": "backup", "target": "",
"status": "ok",
"started": "2026-10-26T03:30:00Z", "finished": "2026-10-26T03:31:12Z",
"log": "2026-10-26T03:30:00Z backup of suite media -> version 20261026T033000Z (manual)\n...",
"result": {"version": "20261026T033000Z", "pruned": [], "total_bytes": 812345678}
}| Field | Values |
|---|---|
status |
queued → running → ok, partial (some parts failed) or failed. A run still queued or running when wranglarr restarts is marked failed ("abandoned: wranglarr restarted while this ran"); a container it had stopped may still be stopped |
kind |
backup, restore, restore-plan, sync, connect, update, rollback, indexers
|
suite |
the suite's name; system for connect, update, rollback and indexers runs |
target |
what the run is about: radarr@20261026T033000Z, all, the containers, … |
log |
the whole log as text, one timestamped line per step |
result |
per kind, below; {"error": "…"} when the run failed outright |
| Kind | result |
|---|---|
backup |
version, pruned (versions removed by retention), total_bytes
|
restore |
version, app, databases (names restored), config (true/false) |
restore-plan |
done, failed, unavailable (lists of app@version) |
sync |
results: [{"app", "resource", "name", "action", "error"?}]
|
connect |
results: [{"link", "src", "dst", "action", "detail"}]; action is created, updated, kept, connected, waiting or failed. kept: already set up (in "new" mode left alone; in "all" mode saved again with its key, nothing else changed) |
update |
results: {"<container>": "updated" | "recreated" | "current" | "held" | "skipped" | "refused" | "failed"}, and reports: {"<container>": {"key", "id", "title", "search", "state"}} for the failures that left a report
|
rollback |
container, image, and data_from (the backup version) when its data went back too |
indexers |
results: [{"app", "indexer", "action", "error"?}]
|
One run per suite at a time. Starting another while one is queued or
running answers 409 ("suite media already has a run in progress"). All
system runs share one slot: an update is refused while an app-connections
or indexer run is going, and the other way round. Nothing queues: try again
when the other run has ended.
Poll from the shell (needs jq):
#!/bin/sh
# back up a suite and wait for it; exit 1 unless it ended ok
WR=http://nas:8787 KEY=wr_... SUITE=media
run=$(curl -fsS -X POST -H "X-Api-Key: $KEY" "$WR/api/suites/$SUITE/backup" | jq -r .run) || exit 1
while :; do
status=$(curl -fsS -H "X-Api-Key: $KEY" "$WR/api/runs/$run" | jq -r .status)
case "$status" in queued|running) sleep 5 ;; *) break ;; esac
done
echo "run $run: $status"
[ "$status" = ok ] || { curl -fsS -H "X-Api-Key: $KEY" "$WR/api/runs/$run" | jq -r .log; exit 1; }Or from Python, with the standard library only:
import json, time, urllib.request
WR, KEY = "http://nas:8787", "wr_..."
def call(method, path, body=None):
req = urllib.request.Request(WR + path, method=method, headers={"X-Api-Key": KEY},
data=json.dumps(body).encode() if body is not None else None)
if body is not None:
req.add_header("Content-Type", "application/json")
with urllib.request.urlopen(req) as r:
return json.load(r)
run = call("POST", "/api/suites/media/backup")["run"]
while (r := call("GET", f"/api/runs/{run}"))["status"] in ("queued", "running"):
time.sleep(5)
print(r["status"], r["result"])urlopen raises urllib.error.HTTPError on a 4xx/5xx; its body holds the
{"error": …} described next.
Every error comes back as JSON with one field:
{"error": "suite media already has a run in progress"}| Status | When |
|---|---|
200 |
done; the body is the answer |
201 |
created (a notification channel, a media server) |
202 |
a job was started: {"run": id}. Saving or deleting an indexer source also answers 202, with "run": null when another system run is busy: the sync follows it |
204 |
the AuthTunnel gate lets a request through |
400 |
the input is wrong; the text says what (an invalid cron schedule, a missing field, a bad address) |
401 |
not signed in, or a wrong password or code at sign-in |
403 |
the Origin check refused it; an account endpoint called with an API key; a wrong current password or setup code |
404 |
no such suite, version, app, run, channel, server, source, container… |
409 |
busy (a run in progress), or nothing to do (no restore plan, no update to roll back, Docker control off, the account already exists) |
422 |
the body doesn't have the expected shape: a missing required field or a wrong type, e.g. {"error": "app: required"}
|
429 |
too many failed sign-ins from this address: 5 in 15 minutes locks it for 15 minutes (a restart clears it) |
502 |
something wranglarr asked failed: plex.tv, a Jellyfin or Emby server, an arr during a Community configs call |
500 |
a bug; the log (docker compose logs wranglarr) has the traceback |
An address that doesn't exist answers 404 {"error": "Not Found"}, and a
wrong method 405 {"error": "Method Not Allowed"}.
A job that fails after it started doesn't change the POST's answer: the
POST still answered 202, and the run ends failed or partial.
Below, {suite}, {app}, {version} and so on are path parts. "Body" is
the JSON to send. Changes the apps, stops containers and deletes
mark calls that do more than change wranglarr's own settings.
| Method | Path | Body / query | Answer |
|---|---|---|---|
| GET | /api/suites |
every suite's summary (below) | |
| GET | /api/suites/{suite} |
one suite's summary | |
| GET | /api/suites/{suite}/overview |
?refresh=true skips the 5-minute cache |
each app asked live (below) |
| PUT | /api/suites/{suite}/settings |
schedule and retention (below) | the suite's settings; 400 for a bad cron schedule |
| DELETE | /api/suites/{suite}/settings |
back to wranglarr.toml's values; the settings |
|
| POST | /api/suites/{suite}/backup |
none |
202 {"run": id}; 409 busy |
| POST | /api/suites/{suite}/prune |
none |
deletes versions retention no longer keeps: {"pruned": ["20260901T033000Z", …]}
|
A suite summary:
{
"name": "media",
"apps": [{"name": "radarr", "kind": "radarr", "databases": ["radarr", "radarr-log"]}],
"settings": {"schedule": "30 3 * * *", "enabled": true, "keep_last": 14, "keep_days": 0, "source": "config"},
"usage": {"versions": 12, "bytes": 9123456789, "newest": {"...": "the newest version's manifest"}},
"next_run": "2026-10-27T03:30:00+00:00",
"active_run": null,
"shared": [{"resource": "downloadclient", "label": "download client", "name": "qBittorrent",
"apps": ["radarr", "sonarr"]}]
}settings.source is config (from wranglarr.toml) or ui (changed here;
then updated says when). active_run is the queued or running run, if any.
The settings body. A field left out keeps its current value (an empty
schedule means no scheduled backups):
{"schedule": "30 3 * * *", "enabled": true, "keep_last": 14, "keep_days": 0}keep_last and keep_days of 0 mean no limit of that kind.
The overview asks every app in the suite now:
{"suite", "fetched", "apps": [...], "postgres": {"version"} | null}. Each app
has name, kind, url, online, databases ([{"name", "size"}]) and,
when it answered, version, database_type, instance_name, branch,
collection (counts: movies, episodes, …), scope (counts of indexers,
download clients, profiles, …) and health (its warnings and errors). An app
that didn't answer has error instead.
Prune and version delete answer 409 while a run is queued or running in the
suite, or while another run uses its versions (a restore plan, an update's
pre-update backup).
| Method | Path | Body | Answer |
|---|---|---|---|
| GET | /api/suites/{suite}/versions |
every version's manifest, newest first, with what changed since the one before | |
| GET | /api/suites/{suite}/versions/{version} |
that version's manifest.json; 404
|
|
| DELETE | /api/suites/{suite}/versions/{version} |
deletes the version's folder: {"deleted": "<version>"}; 404 when there's no such version; 409 while a run uses the suite |
|
| POST | /api/suites/{suite}/versions/{version}/restore |
{"app": "radarr", "databases": true, "config": true} |
202 {"run": id}; stops containers; 404 for an app not in the suite or no such version; 409 busy |
A manifest has suite, version, created, trigger (manual,
scheduled, pre-update), wranglarr (its version), status (ok,
partial, failed; running while the backup runs; incomplete in the list
for a folder without a readable manifest), total_bytes, postgres_version,
vpn, and apps: per app its kind, version, databases
([{"name", "file", "size", "sha256"}]), config_zip, config_snapshot,
collection and errors. The list adds to each:
-
deltas: per app,version_changed,prev_version,count_key,count_delta; -
changes:[{"app", "kind", "what": "added" | "removed" | "upgraded" | "count" | "error", …}]; -
size_delta: bytes more or less than the version before (nullfor the oldest).
A restore of databases stops the app's container (when wranglarr has the
Docker socket and knows the container), restores each dump, and starts it
again. Without Docker control it restores while the app runs and asks the
app to restart. config restores an arr's or Bazarr's own config backup
through its API (the app restarts), or, for the download clients and Seerr,
the copy of its /config with its container stopped. Those can't have their
config restored without Docker control. Both flags default to true. Details:
Backups and Restore.
| Method | Path | Query | Answer |
|---|---|---|---|
| GET | /api/runs |
suite (system for the stack-wide jobs), limit (default 20) |
runs, newest first, each as in Jobs and runs |
| GET | /api/runs/{id} |
the run with its whole log; 404
|
The setup wizard writes a restore plan when you pick versions to bring back on a new install (Setup).
| Method | Path | Answer |
|---|---|---|
| GET | /api/restore-plan |
the plan, or {"items": []}
|
| POST | /api/restore-plan/run |
202 {"run": id}; stops containers; 409 when there's no plan, none of its versions exist, an item was interrupted, or the suite is busy |
| DELETE | /api/restore-plan |
deletes the plan file: {"discarded": true}; 409 while a plan run is queued/running |
{"created": "2026-10-20T12:00:00+00:00", "source": "setup wizard",
"items": [{"suite": "media", "version": "20261019T033000Z", "app": "radarr",
"databases": true, "config": true, "available": true}]}available says whether the app is configured and its version is in the store.
Successful items are removed immediately; unavailable items remain pending.
The plan is removed only when no items remain. started: true marks an item
whose restore failed or was interrupted and requires review before any retry.
A failure stops the plan. Discarding a plan does not undo completed work.
Download clients and notifications defined once in wranglarr.toml
([[shared]], see Shared Settings), listed in each
suite summary's shared.
| Method | Path | Body | Answer |
|---|---|---|---|
| POST | /api/suites/{suite}/sync |
{"app": "radarr"}, or none / {} for every app in the suite |
202 {"run": id}; changes the apps; 404 no such app; 409 busy |
| Method | Path | Body | Answer |
|---|---|---|---|
| GET | /api/updates |
the overview (below), from the last check | |
| POST | /api/updates/check |
none | asks the registries now and answers the overview. It waits for every registry, so it can take a while; 409 when Docker control is off |
| POST | /api/updates/apply |
{"containers": ["radarr"], "force": false}; none or "containers": null = everything with an update that isn't held |
202 {"run": id}; stops containers (recreates them); 404 not a managed container; 409 Docker control off, nothing to update, or busy |
| POST | /api/updates/rollback |
{"container": "radarr", "restore_data": true} |
202 {"run": id}; stops containers; then holds it. restore_data (default false) also puts the app's data back from the backup taken just before that update; 404 not a managed container; 409 no successful update to roll back, that backup isn't kept any more, its data can't be put back with the app stopped (an SQLite arr whose config folder isn't mounted into wranglarr read-write), a container left parked as <name>-wranglarr-old from an earlier update or rollback, or busy |
| POST | /api/updates/hold |
{"container": "radarr", "hold": true} |
the overview; "hold": false releases it; 404
|
| POST | /api/updates/self |
none |
202 {"run": id}; replaces wranglarr's own container, so its API goes away for a while; needs a signed-in browser session (403 with an API key); 409 Docker control off, its container not found, it can't update itself here (the reason), or an update of wranglarr already under way |
| PUT | /api/updates/settings |
below | the settings; 400 for a bad cron schedule |
| DELETE | /api/updates/settings |
back to wranglarr.toml's values; the settings |
|
| PUT | /api/updates/tracks/{container} |
{"tag":"develop"}; null or blank follows the deployed tag/digest |
overview; stores a preference without restarting; invalid tag 400, unknown container 404, queued/running update 409
|
| DELETE | /api/updates/tracks/{container} |
overview; removes the UI track override and uses [updates.tracks] or the deployed image |
|
| GET | /api/updates/reports |
{"reports": [...]}: the kept failure reports, newest first, each as below |
|
| GET | /api/updates/reports/{key} |
one failure report, by its key (<run>-<container>; id is the fingerprint, shared by the same failure): its fields, state (how the container was left, as Docker tells it afterwards: running, unhealthy, stopped as it was, not_started (back under its name, but it wouldn't start), parked, rejected (the new container couldn't be removed; it's stopped and the old one parked), rejected_running (it couldn't be stopped either) or unknown), blocked (whether the new image is skipped from now on), data, outcome (those in a sentence), seen and first_seen (how often and since when its fingerprint has failed here), title, markdown and links (search, new_issue, body_in_link; null links when there's no repo to report to); 404
|
|
| GET | /api/updates/reports/{key}/reported |
{"issue": {"number", "title", "state", "url"}}: the oldest issue in the report's repo, open or closed, with its fingerprint; {"issue": null} when there's none. wranglarr asks GitHub's search; 404 no such report, 409 no repo to look in, 502 GitHub didn't answer or is limiting searches |
|
| DELETE | /api/updates/reports/{key} |
{"forgotten": key}; 404
|
An update pulls the new images first, backs up the suites of the apps it is
about to replace (backup_first), then recreates each container with the
settings it had and waits for it to be healthy, putting the old one back if
it isn't. With force, it updates an app even when that pre-update backup
was incomplete. Holds only stop automatic updates: a container you name in
containers is updated even when held. apply never touches wranglarr's
own container: /api/updates/self does, through a helper container, and the
run (kind self-update) is finished by the wranglarr that comes up after it.
See Keeping wranglarr itself up to date.
Checks and updates use each container's selected track within its existing
image repository. A target's image is the deployed tracking reference;
target_image is the reference checked. track includes tag (null follows
the deployed image), source (ui, config, or container), the selected
image, publisher-specific choices (tag, label), docs, changed, and
an optional error. Changing a track invalidates that container's cached
check. Track preferences survive restarts and are separate from Automation
settings. Namespace-only recreations preserve dependents' exact running image.
The settings body. A field left out keeps its current value:
{"enabled": true, "check_schedule": "0 */6 * * *", "auto_update": false,
"auto_schedule": "", "backup_first": true, "self_update": false, "self_schedule": "30 5 * * *"}The overview:
{
"settings": {"enabled": true, "check_schedule": "0 */6 * * *", "auto_update": false,
"auto_schedule": "", "backup_first": true, "self_update": false,
"self_schedule": "30 5 * * *", "source": "config"},
"next": {"check": "2026-10-26T18:00:00+00:00", "auto": null, "self": null},
"docker": true,
"available": 2,
"own": {"container": "wranglarr", "role": "self", "status": "self", "newer": true,
"remote_digest": "sha256:…", "self_update": true, "self_update_blocker": null,
"update_hint": "update it from the shell: `docker compose pull wranglarr && docker compose up -d wranglarr`",
"error": "a newer wranglarr is out: press Update now", "blocked": null, "last_update": null},
"targets": [{"container": "radarr", "role": "app", "suite": "media", "app": "radarr", "kind": "radarr",
"status": "available", "image": "lscr.io/linuxserver/radarr:latest", "version": "5.14.0",
"running": true, "health": "healthy", "checked": "2026-10-26T12:00:03+00:00", "error": null,
"held": false, "hold_reason": null, "blocked": null, "last_update": null,
"can_rollback": false, "rollback_to": null, "rollback_backup": null, "report": null,
"interrupted": null, "recovery": null}],
"history": [{"id": 7, "container": "sonarr", "action": "update", "status": "ok", "old_version": "4.0.9",
"new_version": "4.0.10", "run_id": 40, "at": "2026-10-20T04:00:12Z", "note": ""}]
}report is {"key", "id", "title", "search", "state"} while the container's last
update is a failed one that left a failure report, else null. interrupted names
the parked <name>-wranglarr-old container while there is one, and recovery says
why it was left stopped and what to do, when an update or rollback stopped because
the data couldn't go back.
The full /api/updates/reports/{key} response records recovery facts separately:
state describes the container's final availability; recovery_phase identifies
an incomplete container recovery (remove_new, restore_data, rename_old or
start_old), or is null when those steps completed. data_recovery contains
state, completed and api. Its state is untouched (no recovery writes
attempted), restoring, offline_restored, verified or needs_recovery.
completed lists the database/file steps that finished. The API outcome is
not_attempted, unavailable_before_upload, request_failed,
upload_outcome_unknown, restart_not_ready or verified. An app answering
requests does not prove that an uncertain config upload succeeded; inspect both
the container and data outcomes. data retains the human-readable explanation.
request_failed means the app returned an error and still answers, not that the
restore made no changes.
role is app, postgres, vpn, extra or wranglarr. status is unchecked,
current, available, pinned (a digest in the image reference), local
(built here, not pulled), self (wranglarr itself), blocked (a new image
wranglarr refuses, such as a Postgres major upgrade), missing or error.
own is wranglarr's own row (also in targets), or null when it
isn't running in a container it can find. newer is set when the registry
has a newer image. self_update says whether Update now can be used for
it. self_update_blocker says why it can't (built here, pinned to a digest,
no Docker socket or state folder mount), and then update_hint is the shell
command.
Download clients into the arrs and Prowlarr, the arrs into Prowlarr, Sonarr and Radarr into Bazarr and Seerr (App Connections).
| Method | Path | Body | Answer |
|---|---|---|---|
| GET | /api/connect |
each link and its state, asked from the apps now (below) | |
| POST | /api/connect |
{"mode": "all", "links": []} |
202 {"run": id}; changes the apps; 400 bad mode; 404 none of the links exist; 409 busy |
A link's key is <the app pointed at>><the app being configured>:
qbittorrent>radarr is qBittorrent as a download client in Radarr,
radarr>prowlarr is Radarr as an application in Prowlarr, sonarr>bazarr,
radarr>seerr. mode is all (create or update every link) or new (only
links never handled before). An empty links means every link. Both fields
are optional.
{"links": [{"key": "qbittorrent>radarr", "src": "radarr", "src_kind": "radarr", "dst": "qbittorrent",
"dst_kind": "qbittorrent", "what": "download client", "handled": true,
"target": "qbittorrent:8090", "state": "connected", "detail": "'qBittorrent', category radarr"}],
"auto": true, "missing": 0}state is connected, differs (detail says how), missing, waiting
(Seerr isn't set up yet) or error.
POST /api/hooks/{app} (the arrs' webhook, signed per app, no sign-in),
GET /api/events, POST /api/events/wire, GET /api/events/stream
(Server-Sent Events) and GET /api/keys (signed-in pages only):
Live status and API keys.
Trackers, usenet indexers, Jackett, NZBHydra2 or an outside Prowlarr, pushed to the arrs you pick (Indexers).
| Method | Path | Body | Answer |
|---|---|---|---|
| GET | /api/indexers |
sources and their state in each arr, asked now (below) | |
| POST | /api/indexers/sources |
a source (below) |
202 {"source": …, "run": id | null}; saves it and starts a sync; changes the apps; 400
|
| DELETE | /api/indexers/sources/{id} |
202 {"run": id | null}; the sync removes the indexers wranglarr added for it; 404
|
|
| POST | /api/indexers/sync |
none |
202 {"run": id}; changes the apps; 409 busy |
run is null when another system run was busy: the source is saved, and
the next sync applies it.
A source. Leave id out to add one; send it to change one. A blank
api_key when changing keeps the stored key.
{"id": "", "type": "jackett", "name": "Jackett", "url": "http://jackett:9117",
"api_key": "...", "apply_to": [], "enabled": true, "priority": 25, "min_seeders": 1, "torrents": false}-
type:torznab,newznab,jackett,nzbhydraorprowlarr(a Prowlarr outside this stack). -
name: 1-60 letters, digits, spaces and. _ ( ) - + &; unique. -
url:http://orhttps://. -
api_key: required, except fortorznab. -
apply_to: arr names;[]means every arr. -
priority: 1-50.min_seeders: for torrent indexers,0or more (default1). -
torrents: NZBHydra2 only: also add its Torznab feed.
type, name and url are required (422 without them).
The answer to GET. api_key is never returned; has_key says whether one is
stored.
{"targets": [{"name": "radarr", "kind": "radarr"}],
"sources": [{"id": "a1b2c3d4", "type": "jackett", "type_label": "Jackett", "name": "Jackett",
"url": "http://jackett:9117", "has_key": true, "apply_to": [], "enabled": true,
"priority": 25, "min_seeders": 1, "torrents": false, "count": 6,
"detail": "6 tracker(s) configured in Jackett", "cells": {"radarr": "ok"}}],
"errors": {}, "types": {"torznab": "Tracker (Torznab)", "...": "..."},
"stack_prowlarr": [{"name": "prowlarr", "url": "http://prowlarr:9696"}], "indexer_counts": {"radarr": 9}}cells per arr: ok, partial, missing, off (not picked or the source
is off) or error.
Channels, events and the watcher (Notifications).
| Method | Path | Body | Answer |
|---|---|---|---|
| GET | /api/notify |
everything the page shows (below) | |
| POST | /api/notify/channels |
a channel (below) |
201 {"channel": …, "arrs": […]}; changes the apps when arr.apps is set; 400
|
| PUT | /api/notify/channels/{id} |
a channel |
{"channel": …, "arrs": […]}; 400; 404
|
| DELETE | /api/notify/channels/{id} |
{"arrs": […]}; removes it from the arrs too; 404
|
|
| POST | /api/notify/test |
{"channel": {…}, "id": "a1b2c3d4"} |
sends a test: {"ok": true} or {"ok": false, "error": "…"}. With only id, the saved channel is tested; 404 for an unknown id, 400 when the channel itself is invalid |
| PUT | /api/notify/settings |
{"interval": 5, "disk_free_gb": 20, "stall_minutes": 60, "link": "https://wranglarr.example.com"} |
the settings; fields left out keep their value; 400
|
| POST | /api/notify/check |
none | one watcher round now, which sends what's new: {"at", "problems", "sent"}, or {"skipped": "a check is already running"}
|
A channel:
{"kind": "ntfy", "name": "Phone", "enabled": true,
"config": {"server": "https://ntfy.sh", "topic": "my-arrs-x7k2", "token": ""},
"events": ["backup_failed", "restore", "update_failed", "app_down", "vpn_down", "disk_low"],
"arr": {"apps": ["radarr", "sonarr"], "events": ["import", "manual", "health"]}}kind |
config keys (secret ones in bold) |
|---|---|
telegram |
bot_token, chat_id, topic_id
|
discord |
webhook_url |
slack |
webhook_url |
ntfy |
server, topic, token, username, password
|
gotify |
server, token
|
pushover |
user_key, token
|
apprise |
server, key, urls (a key or urls is needed) |
email |
host, port, security (starttls, ssl, none), username, password, sender, to
|
webhook |
url, username, password
|
events are wranglarr's own: backup_failed, backup_ok, restore,
update_available, update_done, update_failed, self_update_available, sync_failed, api_key_changed,
app_down, app_health, vpn_down, disk_low, download_problem,
import_blocked, sign_in_lockout. arr.apps adds the channel to those
arrs' Settings → Connect, as "wranglarr · ", sending arr.events:
grab, import, upgrade, manual, health, app_update (default
import, manual, health). name defaults to the service's name.
Secrets are never sent back. Answers show secret fields as ••••••••
(also given as mask in GET /api/notify). Sending •••••••• back keeps the
stored value, so a client can GET a channel, change a field and PUT it whole.
A PUT replaces the channel, so leave nothing out. For test, id names the
saved channel whose secrets fill in the masked fields; leave it out for a new
channel.
arrs in the answers says what happened in each arr:
[{"app": "radarr", "action": "created" | "updated" | "removed" | "none"}], or
{"app", "error"} (with the secrets taken out of the text).
GET /api/notify answers kinds (each service's fields, as the form builds
them), events, arr_events, arr_events_default, arrs, channels,
settings, watching (the problems the watcher tracks now: key, event,
title, message, since, sent), log (the last 40 messages, with
which channels they reached) and mask.
Plex, Jellyfin, Emby and Kodi in the arrs' Connect settings, and Seerr's state (Connections).
| Method | Path | Body | Answer |
|---|---|---|---|
| GET | /api/connections |
{"media": …, "seerr": … | null} (below) |
|
| POST | /api/connections/media |
a server (below) |
201 {"server": …, "arrs": […]}; changes the apps; 400
|
| PUT | /api/connections/media/{id} |
a server |
{"server": …, "arrs": […]}; 400; 404
|
| DELETE | /api/connections/media/{id} |
{"arrs": […]}; removes it from the arrs too; 404
|
|
| POST | /api/connections/media/test |
{"server": {…}, "id": "a1b2c3d4"} |
{"ok": true, "version", "libraries", …} or {"ok": false, "error"}, also for invalid input |
| POST | /api/connections/plex/pin |
none | starts Plex's sign-in: {"id": 123456, "auth_url": "https://app.plex.tv/auth#?…"}; 502
|
| GET | /api/connections/plex/pin/{id} |
{"done": false} until the user approves at auth_url, then {"done": true, "token", "servers": [{"name", "token", "addresses"}]}; 502
|
|
| POST | /api/connections/jellyfin/key |
{"url": "http://192.168.1.10:8096", "username": "admin", "password": "..."} |
{"token": "..."}: a new API key named wranglarr-arrs on that Jellyfin or Emby. The login isn't kept; 400 wrong login or not an administrator; 502
|
A server:
{"kind": "plex", "name": "Plex", "url": "http://192.168.1.10:32400", "token": "...",
"update_library": true, "map_from": "", "map_to": "", "arrs": ["radarr", "sonarr"]}kind is plex, jellyfin, emby or kodi. token is the Plex token or
the Jellyfin/Emby API key, and is required for those three. Kodi takes
username and password instead. map_from and map_to translate the
arrs' paths to the server's, both or neither. As with channels, token and
password come back as ••••••••, and sending that keeps the stored value.
The Plex PIN check and the Jellyfin key call answer the new token in clear:
that's what they are for.
media is {"kinds", "servers", "arrs", "mask"}. seerr is null without
a Seerr in the stack, else {"name", "online", "initialized", "url", "version", "links"} (error when it couldn't be asked). links are the
radarr>seerr and sonarr>seerr links as in
App connections; connect them with POST /api/connect
and those keys. Seerr has no other endpoint here.
What the Dashboard shows (Dashboard).
| Method | Path | Body / query | Answer |
|---|---|---|---|
| GET | /api/links |
{"settings": …, "links": {"<app>": {"url", "how", "note"}}} |
|
| PUT | /api/links |
link settings (below) | as GET; 400
|
| PUT | /api/links/configuration |
{"proxy": {…}, "links": {…}} |
as GET; atomic save of both; 400
|
| DELETE | /api/links |
back to the defaults; as GET | |
| GET | /api/apps/{app}/logo |
the app's own logo (image/svg+xml or image/png), else the one wranglarr ships; 404
|
|
| GET | /api/home/settings |
what the Dashboard lists show | |
| PUT | /api/home/settings |
any of the settings' fields | the settings; fields left out keep their value; 400
|
| DELETE | /api/home/settings |
back to the defaults | |
| GET | /api/home/feeds |
?refresh=true skips the 2-minute cache |
each app's lists (below) |
| GET | /api/downloads |
?refresh=true skips the 4-second cache |
every download client's queue (below) |
Link settings. A PUT replaces them all:
{"mode": "port", "scheme": "http", "host": "192.168.1.10",
"pattern": "", "apps": {"seerr": "https://requests.example.com"}}host is this machine's address for the direct links (empty = the one the
browser used); apps sets one app's address outright. mode is port, or
domain for a pattern with {name} or {kind} saved before the Links panel
had the proxy settings (the panel saves port). Where each app is on the proxy
is /api/proxy's apps. The GET answer also has panel: the proxy domain, the
local network, this machine's address, and wranglarr and each app with its
place (at, local, sub), its port, its direct address and where its card
opens now. In the
answer, how is set, domain, proxy (its name on the reverse proxy: the
request came in at the Reverse proxy page's domain, AuthTunnel's cookie
domain, or a name under it), port or none (not published: set an address
for it). Through the proxy, an app configured as Direct IP gets port at host
(else HOST_IP, else the Reverse proxy page's address), with a note saying
local only. Configured proxy URLs are used without network probing. Links are built from the host the request was sent to, so
a script gets the addresses for the name it used.
The Links panel uses PUT /api/links/configuration: proxy has the same patch
semantics as PUT /api/proxy, while links replaces the link settings. Both
objects are required and validated before either is written. They are committed
in one SQLite transaction, so validation or database failure changes neither.
Use the individual endpoints when changing only one set of settings.
Dashboard settings and their defaults:
{"details": "hover", "queue": true, "upcoming": true, "days": 7, "unmonitored": false,
"release_next": 10, "release_days": 90, "missing": true, "missing_days": 30,
"requests": true, "indexers": true, "limit": 25}details is hover or always. days 1-90, missing_days 0-3650 (0 = all),
release_next 1-200, release_days 1-365, limit (items per list) 3-200.
Feeds: {"settings", "apps": {"radarr": {"lists": [{"key": "upcoming", "title": "Upcoming", "note", "items": [{"title", "sub", "when", "tag", "state", "path", "day"}], "total"}]}}}. The
arrs have upcoming and missing, Bazarr its wanted subtitles, Prowlarr its
indexers, Seerr its requests. A list or app that couldn't be read has
error.
Downloads:
{"at": "2026-10-26T12:00:00+00:00", "speed": 5242880,
"clients": [{"name": "qbittorrent", "kind": "qbittorrent", "ok": true, "error": "", "count": 3, "speed": 5242880}],
"items": [{"id": "…", "name": "…", "category": "radarr", "state": "downloading", "state_text": "…",
"progress": 0.42, "size": 4294967296, "done": 1803886264, "speed": 5242880, "eta": 600,
"added": "…", "client": "qbittorrent", "client_kind": "qbittorrent",
"arr": {"name": "radarr", "kind": "radarr"}}]}state is downloading, processing, checking, metadata, stalled,
error, queued or paused. progress is 0 to 1; sizes are bytes, speeds
bytes per second, eta seconds. arr is the arr the category belongs to, or
null.
| Method | Path | Answer |
|---|---|---|
| GET | /api/app-logins |
how the apps' logins are set up, read from each app now. Read only: logins are set at init (App Logins) |
{"mode": "tunnel", "mode_text": "AuthTunnel: wranglarr signs you in to every app", "username": "",
"required": "", "required_text": "", "cookie_domain": "example.com", "login_url": "https://wranglarr.example.com",
"wranglarr_auth": true, "last_run": {"mode": "tunnel", "at": "…", "username": "…"}, "rerun": ["…"],
"apps": [{"app": "radarr", "kind": "radarr", "method": "external", "username": "", "login": false,
"gate": true, "warn": ""}]}No passwords are returned.
With WRANGLARR_URL_BASE=/wranglarr, every API route also accepts the
/wranglarr prefix, while bare endpoints stay available for internal callers.
Browser URLs and redirects include the base. Authentication and origin checks
apply identically to both forms.
Config text for a reverse proxy, built from the stack as it is now (Reverse Proxy). wranglarr only writes the text; nothing is changed in your proxy.
| Method | Path | Body | Answer |
|---|---|---|---|
| GET | /api/proxy |
{"settings", "valid", "text", "filename", "how", "plan", "notes"} |
|
| PUT | /api/proxy |
the settings (below) | as GET; 400
|
{"kind": "caddy", "domain": "example.com", "upstream": "host", "upstream_host": "",
"lan": "192.168.1.0/24", "network": "proxy", "entrypoint": "websecure", "resolver": "letsencrypt",
"apps": {"wranglarr": {"at": "subdomain", "local": false, "sub": ""},
"radarr": {"at": "path", "local": true, "sub": ""},
"seerr": {"at": "subdomain", "local": false, "sub": "requests"},
"qbittorrent": {"at": "direct", "local": true, "sub": ""}}}-
kind:caddy,nginx,traefik-file,traefik-labels,caddy-labels,cloudflared,npm. -
upstream:host(the published ports) ordocker(the proxy is a container on the stack's network). -
lan: the networks a local-only app answers; empty = the private ranges. -
domain: empty with AuthTunnel =AUTH_COOKIE_DOMAIN. -
network,entrypoint,resolver: Traefik's and caddy-docker-proxy's names. -
apps: per app (andwranglarr),atissubdomain(its own name),path(under wranglarr's name) ordirect(not proxied); wranglarr's issubdomain,domain(the domain itself),path(at/{sub}on the domain, requiring the sameWRANGLARR_URL_BASE) ordirect.local: only thelannetworks get through.sub: its name (default: the app's name). Unset, an app is wherestyleputs it (subdomain, orpathfor a path each), local only. The older{"expose": "public" | "lan" | "off"}still reads.
A PUT merges with the saved settings: a field left out keeps its value. The answer's
plan says, per app, its address and where the proxy sends it; notes
lists what to do by hand (URL bases, DNS, …). Duplicate routes, reserved wranglarr
paths and unsupported placements are 400 errors and are not saved. NPM requires
one access policy per hostname. Cloudflare Tunnel rejects local-only routes and
paths that require stripping. An existing invalid configuration returns
valid: false, an empty text, and an explanation in notes, keeping the editor
accessible without producing an unsafe scaffold.
Custom formats, quality profiles, sizes and more, shared as JSON (Community configs).
| Method | Path | Body | Answer |
|---|---|---|---|
| GET | /api/configs |
{"format": "wranglarr-config/1", "apps", "examples", "history", "prefix"} |
|
| GET | /api/configs/examples/{id} |
{"text": "<the example config's JSON>"}; 404
|
|
| POST | /api/configs/check |
a config and choices (below) | a preview of what applying would do; changes nothing; 400
|
| POST | /api/configs/apply |
the same, plus "ack": true when the check's ack is true |
changes the apps: {"results": [{"app", "kind", "ok", "error", "changed", "history", "installed"}], "history"}; 400
|
| GET | /api/configs/installed |
each app's timeline, after a look for changes made in the app: {"installed": [{"app", "kind", "error", "marks": [{"id", "at", "kind", "label", "source", "history", "partial", "kept", "count", "items"}]}]}
|
|
| GET | /api/configs/installed/{app}/{id} |
what removing that install would do: {"mode": "none"|"confirm"|"choose", "label", "keep", "throughout"}; changes nothing; 404
|
|
| POST | /api/configs/installed/{app}/{id}/remove |
{"throughout": false} |
changes the apps: removes the install (below): {"notes", "installed"}; 400 for a mark that isn't an install; 404
|
| POST | /api/configs/history/{id}/undo |
none |
changes the apps back to the copies saved at import: {"notes", "history"}; 400 already undone; 404
|
| DELETE | /api/configs/history/{id} |
forgets the entry (the apps stay as they are): {"history"}; 404 for an unknown id |
|
| GET | /api/configs/inventory/{app} |
what the app has, part by part, for export; 400 for an app that takes no configs |
|
| POST | /api/configs/export |
what to export (below) |
{"config": {…}, "notes": […]}: a config made from your apps; changes nothing |
| POST | /api/configs/try |
{"app": "radarr", "title": "Some.Movie.2024.1080p.WEB-DL.x265-GRP"} |
how the app parses that release name: {"quality", "languages", "formats", "profiles": [{"name", "score", "min", "allowed"}]}
|
Check and apply take:
{"text": "{\"wranglarr\": \"wranglarr-config/1\", \"name\": \"…\", \"apps\": {…}}",
"targets": {"radarr": ["radarr", "radarr4k"]},
"extras": {"radarr/customFormats/x265 (HD)": false},
"resolve": {"radarr|qualityProfiles|HD-1080p": "prefix"},
"prefix": "v2 · "}-
text: the config as a string (up to 512 KB), as pasted. -
targets: per app kind in the config, the apps it goes into. A kind left out goes into the first app of that kind. -
extras: the config's optional parts,<kind>/<part>/<name>→ on or off; left out = the config's default. -
resolve: for something the app already has by that name,<app>|<part>|<name>→change,keeporprefix(add the config's as a copy named withprefix). Defaultchange. -
prefix: defaultv2 ·. -
ack(apply only): OK to change what the plans list underprotected.
The check answers {"config", "extras", "plans", "fits", "chosen", "prefix", "tag_prefix", "ack"}. Each plan has:
-
kind,app,app_kind; -
rows, one per item, with itsactionand details; - counts of
changes,errorsandwarnings; -
protected:[{"part", "label", "name", "items", "why", "overrides"}], the changes to built-in items, app-wide parts and the default delay profile, or to what another installed config set (overridesnames those configs).
ack is true when any plan has some. Apply then refuses that app (ok: false)
unless the body has "ack": true.
Apply writes a partial history entry before sending mutations and stores each previous value before its request. An uncertain create with no returned ID remains in history; Undo reports it for manual verification instead of deleting an item by name. Unfinished entries are not evicted by the history limit. Removal also keeps a partial recovery entry until its changes and timeline update are verified; undoing an interrupted deletion requires manual recovery because an app may assign a new ID when recreating it. Removal refuses to proceed or finish verification when app resources are unreadable.
Each apply goes on the app's timeline as a mark, and its id is in installed.
An install's mark keeps the config's source. A mark's kind is one of:
-
before: what the settings were before wranglarr first changed them; -
config: an install; -
external: changes wranglarr noticed made in the app, labelledExternal manual change.
items lists the changes per item: {"part", "label", "name", "changes": ["min 35 → 40"]}.
Removal replays the timeline without the install. Its mode:
-
none: nothing in the app changes; -
confirm: only the install's own changes go back; -
choose: later changes touch the same settings, or use what it made, and the two ways differ.
keep and throughout each give effects (the same shape as items), kept (what
stays, and why) and overrides (later marks whose changes it undoes). With
throughout: false, later changes stay, and what they use stays in the install's
mark. With true, its settings go back to how they were before it, over later
changes, manual ones included. throughout only matters when the mode is choose.
Neither removal nor apply checks yet which films, series or artists use what they
change. An app that refuses to delete a profile in use keeps it, and the refusal
comes back in notes.
Export takes, per app, the names to export in each part (as the inventory lists them):
{"name": "My config", "description": "", "author": "",
"apps": {"radarr": {"customFormats": ["x265 (HD)"], "qualityProfiles": ["HD-1080p"], "withFormats": true,
"extras": {"customFormats": {"x265 (HD)": {"default": false, "note": "…"}}}}}}withFormats adds the custom formats the picked profiles score. An optional
source (a name, a URL, or {"name", "url"}) goes into the config as it is
given.
| Method | Path | Query | Answer |
|---|---|---|---|
| GET | /api/vpn |
?refresh=true skips the 30-second cache |
{"configured": false}, or {"configured": true, "name", "reachable", "status", "public_ip", "forwarded_port", "error", "ok"}
|
| GET | /api/health |
{"ok": true} without a sign-in; signed in: {"ok": true, "version", "postgres", "docker", "vpn", "updates_available"}
|
ok in the health answer only says wranglarr answers. postgres and
docker say whether it reaches them; vpn is true when gluetun's tunnel is
running, false when not, null without a VPN. A monitoring check of the
stack should look at those fields.
The sign-in calls are public but still need the Origin check. The calls
from password to sessions/end-others need a browser session: with an
API key they answer 403. Calls that change the account and say "password" in the body
need the current password; a wrong one is 403.
| Method | Path | Body | Answer |
|---|---|---|---|
| POST | /api/auth/setup |
{"code": "AB12-CD34-EF56", "username": "admin", "password": "..."} |
creates the account and signs in: {"ok": true} + cookie; 403 wrong setup code; 409 the account exists; 400 username or password rules |
| POST | /api/auth/login |
{"username": "admin", "password": "...", "remember": false} |
{"ok": true} + cookie, or {"mfa": true} + the wranglarr_mfa cookie; 401; 429
|
| POST | /api/auth/login/totp |
{"code": "123456"} (or a recovery code) |
{"ok": true} + cookie; 401
|
| GET | /api/auth/passkey/login-options |
{"flow", "options"} for navigator.credentials.get
|
|
| POST | /api/auth/passkey/login |
{"flow": "…", "credential": {…}, "remember": false} |
{"ok": true} + cookie; 401
|
| POST | /api/auth/logout |
none | ends this session: {"ok": true}
|
| GET | /api/auth/me |
{"enabled", "username", "created", "totp", "recovery_left", "passkeys", "sessions", "api_keys", "rp_id", "https"}; works with an API key too |
|
| POST | /api/auth/password |
{"current": "...", "new": "..."} |
{"ok": true} |
| POST | /api/auth/totp/begin |
{"password": "..."} |
{"secret", "uri", "qr_svg"} for the authenticator app |
| POST | /api/auth/totp/enable |
{"code": "123456"} |
{"recovery": ["…", …]}: the recovery codes, shown once |
| POST | /api/auth/totp/disable |
{"password": "..."} |
{"ok": true} |
| POST | /api/auth/recovery |
{"password": "..."} |
new recovery codes: {"recovery": [...]} (needs two-factor on) |
| POST | /api/auth/passkey/register-options |
{"password": "..."} |
{"flow", "options"} for navigator.credentials.create
|
| POST | /api/auth/passkey/register |
{"flow": "…", "credential": {…}, "name": "Laptop"} |
{"id", "name", "created", "rp_id"} |
| PATCH | /api/auth/passkeys/{id} |
{"name": "Phone"} |
{"ok": true}; 404
|
| DELETE | /api/auth/passkeys/{id} |
{"ok": true}; 404
|
|
| POST | /api/auth/api-keys |
{"name": "home-assistant", "password": "..."} |
{"id", "name", "prefix", "created", "last_used", "key"}: key is shown this once |
| DELETE | /api/auth/api-keys/{id} |
revokes it: {"ok": true}; 404
|
|
| DELETE | /api/auth/sessions/{id} |
signs that session out: {"ok": true}; 404 for an unknown id |
|
| POST | /api/auth/sessions/end-others |
none | signs out every other session: {"ended": 3}
|
| GET | /api/auth/gate |
AuthTunnel only (below) |
Passwords need 12 characters or more, can't be a common one, and can't
contain the username.
Usernames are 1-64 letters, digits and . _ - @.
GET /api/auth/me never returns a password hash, a TOTP secret, or an API
key: api_keys lists each key's id, name, prefix (its first 7
characters), created and last_used, and sessions each session's id,
created, last_seen, expires, method, ip, agent and current.
GET /api/auth/gate is what wranglarr-gate asks before every request to an
app with APPS_AUTH=tunnel (App Logins). It answers 204
for a signed-in browser session (or when wranglarr's login is off), and 401
otherwise, with an
X-Wranglarr-Login header that says where to sign in. An API key is not
enough here. Without AuthTunnel it answers 404.
The first-run wizard (wranglarr-setup, Setup) is a separate
server on port 8788 with its own small API. It is internal to the wizard
page: it runs only while you set up and writes .env in the stack folder.
Opening the printed /?t=<code> link sets a cookie; without it every
/api/setup/* call answers 401. Don't leave it running, and don't script against it. For
completeness:
| Method | Path | What it does |
|---|---|---|
| GET | / |
the wizard page; ?t=<code> sets the cookie and redirects to /
|
| GET | /api/setup/defaults |
what the wizard starts from: the existing .env (if any), the services, host user, time zone and mounts |
| POST | /api/setup/check-path |
{"path", "key", "uid", "gid", "create"}: does a folder exist, can the apps' user write it, free space, filesystem notes |
| POST | /api/setup/generate |
{"kind": "password" | "apikey"} → {"value"}
|
| POST | /api/setup/check-vpn |
the VPN answers → {"errors", "warnings"}
|
| POST | /api/setup/scan-backups |
{"appdata": "./appdata"}: backups found there, to restore |
| POST | /api/setup/preview |
the answers → {"env"}: the .env it would write, secrets masked |
| POST | /api/setup/preview-auth |
--auth mode: the app-login keys it would change |
| POST | /api/setup/write-auth |
--auth mode: changes only the app-login keys in .env (after a backup copy) |
| POST | /api/setup/write |
writes .env, wranglarr.toml and the restore plan; 409 when .env exists and overwrite isn't set |
Errors come back as {"error": "…"}, as in the main API.
The HTML pages, for links from elsewhere: / (Dashboard), /suites,
/suites/{suite}, /suites/{suite}/versions/{version}, /activity,
/runs/{id}, /updates, /indexers, /notifications, /connections,
/configs, /keys, /proxy, /system, /account, /login, /setup. The suite
page's settings form posts to POST /suites/{suite}/settings (form fields,
then a redirect); scripts use PUT /api/suites/{suite}/settings instead.
This wiki is built from wiki/ in the repo, and edits made here are replaced on the next build: change the page there instead. Also at https://wranglarrr.github.io/Wranglarr/.
Start here
- Overview
- Quickstart
- Setup
- Bringing in existing apps
- TrueNAS SCALE
- Environment variables
- Login
- App logins and AuthTunnel
- Security
Using wranglarr
- Dashboard, links and logins
- Notifications
- Live status
- Connections: media servers, Seerr
- Backups and restore
- Container updates
- Release notes
- Roadmap
- App connections
- Indexers
- Community configs
- Shared settings
Services
Networking
Reference