An anime-focused PVR β Sonarr's job, built around anime-native tooling and metadata. It monitors anime series and films, finds releases on anime indexers, drives a download client, and organizes the results into a media library.
Status: Beta. The acquisition loop runs end-to-end and unattended β add a series or a film from AniList, and what you monitor is searched, graded against your quality profile, grabbed via qBittorrent, and hardlinked into a Plex/Jellyfin-ready library. Automation ships off by default; flip it on in Settings, or set it to notify-only first to watch what it would grab without grabbing anything. Indexing is via Torznab/Prowlarr for now.
Anime breaks Sonarr's assumptions: messy fansub filenames, absolute vs. per-season numbering, release-group/dual-audio/sub preferences, and metadata that lives on AniList/AniDB rather than TVDB.
Today:
- AniList-native metadata β add series and films from AniList search, browse a seasonal discovery chart, and see upcoming episodes and film premieres on an airing calendar keyed to Japanese broadcast times.
- Series and films, each handled as itself β an episode is matched by number
and filed under its show; a film is matched by title and release year and
filed into a movies library as
Placeholder Film (2019)/Placeholder Film (2019).mkv. Plex and Jellyfin want those in different places, so Transpondarr keeps them there: each library takes its own root, and a film waits in the queue rather than landing in the wrong one until the movies root is set. Format decides and episode count never does, so a one-episode OVA is a series and files with them. A film's year is what automation matches on, so it waits for one to be published rather than guessing; searching and grabbing by hand work throughout. - Automated acquisition β recent-feed polling grabs new releases within minutes of them appearing, and a scheduled sweep backs it up for everything that already existed. Monitoring is per title and per episode β choose at add time whether to chase a whole back catalogue or only what airs next, and unmonitor anything you don't want chased β under a global off / notify-only / on switch (off until you enable it). Notify-only rehearses the whole thing β real searches and real decisions, reported rather than grabbed. Requests can be filtered to specific indexer categories, and a poll that misses a page puts the series that aired inside the gap back at the front of the search queue.
- A Wanted queue that says why β everything still missing across the library, and everything you hold that scores below its profile's cutoff, each with the reason it hasn't been grabbed: automation off, unmonitored, waiting its turn in the search queue, blocklisted β or the release the last pass found and declined, and why.
- Notifications and an activity feed β Discord, generic webhook, and ntfy, with per-event toggles and a test button each; an Activity page collects the in-flight queue, the grab/import history across every title, and any download left in the client that nothing is waiting on.
- Anime-aware quality profiles β release group is the dominant axis, then resolution/source, dual audio, and sub preferences, with a score floor and hard excludes. A profile is chosen when you add a title and can be reassigned from its page later. A per-title pinned group can also mean wait for β hold new episodes for the pinned group's release before settling for another. Opt a profile into upgrades and an episode you already have is re-grabbed while what holds it scores below the cutoff, then left alone for good.
- Failure memory β a failed release is blocklisted with escalating expiry instead of re-grabbed forever, an environmental-fault breaker stops one bad afternoon from blocklisting the library, and everything is visible and unblockable in the UI.
- Manual control that's never refused β search and grab any release by hand, with an episode's Search opening the release list focused on that episode; profiles inform manual actions but only gate automation.
- Seeding-safe library import β hardlink (or copy) into Plex/Jellyfin-ready naming, without breaking the seeding torrent. Episodes file into season folders or flat, whichever your scanner prefers. Season packs import episode by episode, so a back catalogue arrives in one grab, and anything the importer can't place by itself is fixable by hand from the Activity queue. Archived payloads aren't unpacked: a RAR-set download says so and names what to extract, and extracting it in place then retrying from Fix import completes the import.
- Self-hosted, single binary β embedded web UI, login + API key auth, REST API with an OpenAPI spec, observable background jobs, and live-editable settings β no restarts.
Planned (tracked in the milestones):
- Post-1.0: AniList account sync (auto-monitor your Watching list), adopting a pre-existing library and noticing when it changes on disk, more indexers and download clients with per-title routing between them, and Sonarr-API compatibility for existing dashboard/mobile apps.
- Post-1.0: first-class handling for series whose releases aren't numbered the way AniList numbers them β continuously-airing long-runners, fan re-cuts, and a per-series override for when the automatic mapping is simply wrong.
Transpondarr ships as a single static binary with the web UI embedded β no separate database or frontend to run.
Pull the published multi-arch image (or build locally with docker build -t transpondarr .):
docker pull ghcr.io/matthewdias/transpondarr:latest
docker run -p 9797:9797 -v ./config:/config ghcr.io/matthewdias/transpondarr:latestThe container starts as root only to fix ownership of the mounted config dir, then drops to
PUID/PGID(default1000:1000) before serving. To skip the root phase entirely, run with--user "$(id -u):$(id -g)"β the config dir must then already exist and be writable by that user.
For a real deployment alongside qBittorrent and a media server, see Docker deployment below.
Download the archive for your platform from the
releases page, extract
it, and run ./transpondarrd.
To build from source instead, see CONTRIBUTING.md.
The server listens on :9797. The web UI uses a login (username + password):
on first run you create an admin account, or set TRANSPONDARR_AUTH_USERNAME/
_PASSWORD to bootstrap one. A separate API key guards /api/* for machine
clients (dashboards, scripts, a future HA integration) via the X-Api-Key header
β it's generated and persisted on first run and shown in Settings β API access
(set TRANSPONDARR_API_KEY to pin one). Health check (public):
curl localhost:9797/api/v1/healthIntegrations are set through TRANSPONDARR_* environment variables or edited at
runtime in the Settings UI (those DB overrides take precedence over the
environment and apply live, without a restart). Unset, unconfigured integrations
are simply disabled β the server still starts.
| Variable | Default | Purpose |
|---|---|---|
TRANSPONDARR_API_KEY |
(generated + persisted) | Machine-client key for /api/* (X-Api-Key). Auto-generated and saved in the DB; set to override. |
TRANSPONDARR_AUTH_USERNAME / _PASSWORD |
β | Bootstrap the initial web-UI admin account on first run (otherwise use the setup screen). |
TRANSPONDARR_AUTH_REQUIRED |
enabled |
enabled (always require login) | local (skip login for local/private addresses). |
TRANSPONDARR_ADDR |
:9797 |
Listen address. |
TRANSPONDARR_DATA_DIR |
./data |
SQLite DB + state (/config in Docker). |
TRANSPONDARR_DB |
<DATA_DIR>/transpondarr.db |
SQLite DB file path. Override to relocate the DB independently of the data dir. |
TRANSPONDARR_QBIT_URL |
β | qBittorrent WebUI root; unset β no download client. |
TRANSPONDARR_QBIT_USER / _PASSWORD |
β | qBittorrent credentials. |
TRANSPONDARR_QBIT_CATEGORY |
transpondarr |
Category applied to grabbed torrents. |
TRANSPONDARR_STALL_TIMEOUT_HOURS |
6 |
Hours a download may sit having transferred nothing at all before its grab is failed and the release remembered; 0 waits forever. Covers a download the client reports as stalled and one still fetching a magnet's metadata. A download with any progress is never abandoned. |
TRANSPONDARR_TORZNAB_URL |
β | Torznab feed (Prowlarr/Jackett); unset β no indexer. |
TRANSPONDARR_TORZNAB_APIKEY |
β | Torznab API key. |
TRANSPONDARR_TORZNAB_NAME |
torznab |
Display name for the indexer. |
TRANSPONDARR_TORZNAB_CATEGORIES |
β | Comma-separated Newznab category IDs sent as cat= on every search and the recent feed (anime is usually 5070); unset β no filter. |
TRANSPONDARR_LIBRARY_DIR |
β | Library root episodes import into; unset β episodes do not import. |
TRANSPONDARR_LIBRARY_MOVIES_DIR |
β | Library root films place into, separate from the root above; unset β a grabbed film waits in the Activity queue instead of importing. |
TRANSPONDARR_LIBRARY_SERIES_LAYOUT |
season_folders |
Path shape inside the series root: season_folders | flat. Films are unaffected, and switching applies to future imports only. |
TRANSPONDARR_IMPORT_MODE |
auto |
auto (hardlink, copy across filesystems) | hardlink | copy. |
TRANSPONDARR_AUTOMATION_ENABLED |
false |
off | notify_only | on (bools also accepted). notify_only rehearses: it reports what automation would grab, without grabbing. |
TRANSPONDARR_PIN_DELAY_HOURS |
0 |
Hours automation waits for a series' pinned group before taking another; per-series overrides in the UI. |
PUID / PGID |
1000 / 1000 |
Docker only: the uid:gid the container drops to after fixing /config ownership on start. |
Auth & reverse proxies. The
localauth mode skips login only for requests from loopback/private addresses with no forwarding headers, so reverse-proxied requests (which setX-Forwarded-For) always require login β a same-host proxy can't turn local-bypass into open access. Session cookies are markedSecureautomatically when the proxy setsX-Forwarded-Proto: https.
For a real deployment alongside qBittorrent and a media server, use
docker-compose.yml as a template. Two things matter:
- Imports hardlink from the path qBittorrent reports. Mount your shared
downloads/library volume into Transpondarr at the same path qBittorrent uses,
with both on one filesystem (a hardlink can't cross filesystems). The standard
single-mount layout (
/data/torrents+/data/media) satisfies this. The movies root is one more directory under the same mount, not a second one. - Ownership. Set
PUID/PGIDto the UID:GID that owns your media volume β the container starts as root, fixes/configownership, and drops to that user before serving, so hardlinks into the library land with the right ownership. Persist the/configvolume (it holds the SQLite DB).
Verify a running deployment (the second call needs your API key):
curl -s http://localhost:9797/api/v1/health # {"status":"ok",...}
curl -s -X POST -H "X-Api-Key: <key>" http://localhost:9797/api/v1/download/test
# {"status":"ok","client":"qbittorrent"} (502 => qBit URL/creds wrong)See CONTRIBUTING.md for the stack, toolchain, build-from-source steps, and codebase layout.
Apache-2.0. Third-party dependency licenses and notices are
reproduced in THIRD-PARTY-NOTICES.md (regenerate with
make notices).