The missing *arr for books.
Quick start · Features · Configuration · Releases · Contributing
Self-hosted book, audiobook, and manga search and download manager -- like Sonarr/Radarr but for your reading library.
Librarr searches all configured indexers in parallel, scores results by confidence, and auto-imports into your Calibre, Audiobookshelf, Kavita, or Komga library. Single ~17MB Go binary, no runtime dependencies — ~14MB RSS idle in a real homelab[^1], typically 10-20× lower than the .NET-based *arr apps.
[^1]: Measured on v1.1.0 in an LXC on Debian 12 (Mar 2026). Reference: Sonarr 4.x ≈ 240MB, Radarr 5.x ≈ 220MB on the same host.
Screenshots
| Search | Library | |---|---| | !Parallel indexer search with confidence scoring | !Unified library view across ebooks, audiobooks, and manga |
| Wanted | Settings | |---|---| | !Wanted list: every book has a state, a quality profile and the last scheduler decision | !Settings: scheduler, quality profile editor with cutoff, and monitored authors |
Quick Start
Docker (recommended)
services:
librarr:
image: ghcr.io/jeremiahm37/librarr:latest
ports:
- "5050:5050"
volumes:
- ./data:/data
- /path/to/ebooks:/books/ebooks
- /path/to/audiobooks:/books/audiobooks
- /path/to/manga:/books/manga
environment:
- AUTH_USERNAME=admin
- AUTH_PASSWORD=changeme
- API_KEY=your-api-key-here
- QB_URL=http://qbittorrent:8080
- QB_USER=admin
- QB_PASS=changeme
- PROWLARR_URL=http://prowlarr:9696
- PROWLARR_API_KEY=your-prowlarr-api-key
restart: unless-stopped
docker compose up -d
Binary
# Download from releases
curl -LO https://github.com/JeremiahM37/librarr/releases/latest/download/librarr_linux_amd64.tar.gz
tar xzf librarr_linux_amd64.tar.gz
Configure
export AUTH_USERNAME=admin
export AUTH_PASSWORD=changeme
export QB_URL=http://localhost:8080
... set other env vars as needed
Run
./librarr
Open http://localhost:5050 in your browser.
Why Librarr?
- Import your Goodreads or StoryGraph library via CSV and bulk-download everything
- Request workflow -- users request books, admins approve, downloads happen automatically (like Jellyseerr for books)
- Wanted list with quality profiles -- every wanted book is a monitored entity with a state (missing, downloading, upgrade wanted, satisfied); rank formats (EPUB > AZW3 > MOBI > PDF), set a cutoff, and librarr grabs a first copy, then upgrades it until the cutoff is met and retires the old file
- Author monitoring that acts -- follow authors; new works (tracked by Open Library work key, so reissues don't fire) land on the wanted list automatically
- Series auto-complete -- detects gaps in series and searches for missing volumes
- Torznab API -- add Librarr as an indexer in Prowlarr or Readarr (it works both ways)
- OPDS 1.2 feed -- browse your library from any e-reader app
- Tiny footprint -- ~14MB idle RSS, runs comfortably on a Pi or any thermally-constrained mini-PC
Features
Search and Scoring
- Pluggable indexer registry -- driver kinds listed below; default endpoints are fetched at startup from the
librarr-sourcescompanion repo and overrideable at runtime viaLIBRARR_SOURCES_URL/LIBRARR_SOURCES_PATH - Confidence scoring -- 0-100 score with breakdown (title match, author match, format, seeders, file size)
- Quality profiles -- per media type, best-first format ranking with a cutoff; formats left out are never grabbed automatically; built-in defaults for ebook / audiobook / manga
- Release profiles -- preferred and excluded words for fine-grained filtering
- Blocklist -- failed downloads are auto-blocked to prevent retries; a release that claimed a better format but delivered the same or a worse one is rejected after import and blocklisted; manual entries supported
- Already-in-library detection -- results you already own are flagged with
in_library/library_item_idand badged in the UI; matching normalizes punctuation, author prefixes and parenthetical alternate titles, soAgatha Christie - 4.50 From Paddingtonfinds4:50 from Paddington
Download Management
- Multiple download clients -- qBittorrent, Transmission or rTorrent for torrents, plus SABnzbd for Usenet
- Anna's Archive membership fast download -- optional account secret key uses
/dyn/api/fast_download.json, with LibGen mirror fallback - Request/approval workflow -- pending, approved, searching, downloading, completed states with per-request notifications
- Scheduled wanted-list searches -- the scheduler searches every monitored wanted item on an interval, runs its quality profile over the results (match score gates, format rank decides) and grabs the best acceptable release;
POST /api/scheduler/run?wait=1runs a pass synchronously - Torrent completion watcher -- polls download client, auto-imports completed downloads
- Dead letter retry -- failed jobs can be retried individually or in bulk
Library Management
Without Audiobookshelf or Kavita configured, the library tabs show locally imported items with titles, authors, formats, and file sizes. Local library search matches titles and authors across all pages.
- Auto-import pipeline -- organize files by author/title, rename on import (configurable pattern), scan into Calibre/Audiobookshelf/Kavita/Komga
- Series auto-complete -- detect gaps in series, search for and download missing books
- Author monitoring -- follow authors; the first check records their catalogue as a baseline, later checks add each new work to the wanted list (or only notify, per author)
- Reading history -- track what you've read with stats (books per month, pages, completion rate)
- Tags -- organize library items with custom tags for filtering and grouping
- Series grouping -- groups related books/volumes in the library view
- EPUB verification -- checks title word overlap to detect wrong-book downloads
Notifications and Webhooks
- In-app notifications -- persistent alerts for downloads, requests, failures, and author releases
- Discord webhooks -- rich embeds for download events, request updates, and errors
- Generic webhooks -- JSON payloads for any webhook-compatible service
- Configurable events -- choose which events trigger notifications
Import and Export
- Goodreads CSV import -- import your shelves, auto-download "to-read" books
- StoryGraph CSV import -- import your reading list
- Library export -- JSON and CSV export for library, wishlist, and requests
- Backup and restore -- full database backup with one-click restore
APIs and Integrations
- Torznab/Newznab API at
/torznab/api-- add as indexer in Prowlarr, Readarr, or any compatible app - OPDS 1.2 feed at
/opds-- browse and download from e-readers (KOReader, Moon+ Reader, Librera) - Prometheus metrics at
/metrics-- request counts, download stats, source health, library size - REST API -- full JSON API for all operations (see API section below)
Security and Multi-User
- Multi-user auth -- session login with bcrypt passwords and admin/user roles
- TOTP 2FA -- RFC 6238 time-based one-time passwords with QR code setup
- OIDC / SSO -- OpenID Connect for Authelia, Keycloak, Authentik, and others
- API key auth --
X-Api-Keyheader or?apikey=parameter for programmatic access - Rate limiting -- per-endpoint rate limits with configurable thresholds
- Security headers -- X-Content-Type-Options, X-Frame-Options, CORS, request size limits
UI and Admin
- Modern dark UI -- Tailwind CSS, mobile-responsive, single-page app
- Admin dashboard -- library stats, source health, activity log, system info
- Bulk operations -- retry or cancel multiple downloads at once
- File uploads -- drag and drop ebooks/audiobooks, auto-organize and library scan
- Connection tests -- verify Prowlarr, qBittorrent, SABnzbd, Audiobookshelf, Kavita connectivity
Deployment
- Single static binary -- ~17MB, zero CGO, pure-Go SQLite (
modernc.org/sqlite) - Docker-ready -- minimal Alpine image, runs as non-root user
- Cross-platform -- Linux, macOS, Windows; amd64, arm64, armv7
Search Sources
Librarr ships with driver implementations -- the protocols it can speak. The list of active indexers, their endpoints, mirrors, and enabled flags lives in a JSON registry, hosted in the librarr-sources companion repo and fetched at startup (similar to how Prowlarr syncs its indexer definitions). The binary itself ships no embedded registry. After the first successful fetch the registry is cached on disk so subsequent restarts work offline. To use a different registry, set LIBRARR_SOURCES_URL (another HTTP source) or LIBRARR_SOURCES_PATH (a local file).
| Driver | Used for |
|--------|----------|
| Torznab / Newznab | Prowlarr-managed indexers (any Torznab-compatible source) |
| OPDS 1.2 catalogs | Library, archive, and OPDS-acquisition feeds |
| Public JSON / RSS APIs | Open metadata APIs, public RSS feeds |
| Direct-download with MD5/key lookup | Archive sites that key downloads by content hash |
| Authenticated forum / tracker | Private trackers (user supplies credentials) |
| Library-card-style API | Account-gated catalogs (user supplies endpoint + credentials) |
| Web-novel crawler (lncrawl) | Web-novel sites with chapter pagination |
| HTML scrape with regex extractor | Sites without a structured API |
To add or remove a specific indexer endpoint, edit the registry -- no code changes required.
Configuration
All configuration is via environment variables. Every variable has a sensible default.
Service URLs (ABS_URL, PROWLARR_URL, KAVITA_URL, …) may be written with or
without a scheme — audiobookshelf:13378 is normalized to
http://audiobookshelf:13378, and trailing slashes are trimmed. The same applies
to URLs saved from the Settings UI. Use https:// explicitly when the service is
behind TLS.
Server
| Variable | Default | Description |
|----------|---------|-------------|
| LIBRARR_PORT | 5050 | HTTP listen port |
| LIBRARR_DB_PATH | /data/librarr.db | SQLite database path |
| SETTINGS_FILE | /data/settings.json | Persistent settings file |
Authentication
| Variable | Default | Description |
|----------|---------|-------------|
| AUTH_USERNAME | | Login username (enables session auth) |
| AUTH_PASSWORD | | Login password |
| API_KEY | | API key for programmatic access (X-Api-Key header or ?apikey= param) |
OIDC / SSO
| Variable | Default | Description |
|----------|---------|-------------|
| OIDC_ENABLED | false | Enable OpenID Connect login |
| OIDC_PROVIDER_NAME | SSO | Button label on login page |
| OIDC_ISSUER | | OIDC issuer URL |
| OIDC_CLIENT_ID | | OAuth2 client ID |
| OIDC_CLIENT_SECRET | | OAuth2 client secret |
| OIDC_REDIRECT_URI | | Callback URL (https://librarr.example.com/auth/oidc/callback) |
| OIDC_AUTO_CREATE_USERS | true | Auto-create users on first OIDC login |
| OIDC_DEFAULT_ROLE | user | Default role for OIDC-created users |
| OIDC_PROXY_HEADERS_ENABLED | false | Trust Authentik identity headers from a reverse proxy |
| OIDC_LINK_BY_USERNAME | false | Let an SSO login take over an existing non-admin password account with the same username (see below) |
| LOCAL_AUTH_DISABLED | false | Send everyone to SSO; only local admins sign in with a password, at /login?local=1 |
| OIDC_POST_LOGOUT_REDIRECT_URI | | Where the provider returns the browser after sign-out (e.g. https://librarr.example.com/). Must be registered with the provider |
| OIDC_LOGOUT_URL | | Send SSO users here on sign-out instead of the provider's logout endpoint. Proxy-header setups need it, e.g. /outpost.goauthentik.io/sign_out |
| LIBRARR_TRUSTED_PROXIES | | Comma-separated IPs/CIDRs of your reverse proxy. Required for OIDC_PROXY_HEADERS_ENABLED; also gates X-Forwarded-Proto |
When OIDC_PROXY_HEADERS_ENABLED=true and Librarr sits behind a trusted reverse
proxy that injects Authentik headers like X-Authentik-Username, it will treat
those requests as an authenticated SSO session, auto-provision the local user if
needed, and skip the manual "Login with SSO" click. Enable this only for
proxy-gated deployments.
Identity headers are honored only when the connection comes from an address
in LIBRARR_TRUSTED_PROXIES (e.g. LIBRARR_TRUSTED_PROXIES=172.18.0.5 or a
Docker network CIDR). From any other peer they are ignored, and with the list
empty they are always ignored — Librarr logs a warning at startup and users fall
back to the "Login with SSO" button. Your proxy must also strip these headers
from incoming client requests.
Signing out of Librarr alone only clears its session cookie; while the proxy
keeps sending the identity header, the next request signs the browser back in.
Set OIDC_LOGOUT_URL to the proxy's sign-out URL (for Authentik,
/outpost.goauthentik.io/sign_out) so signing out ends the proxy session too.
Account linking. Each account signs in one way: with a password (optionally
with TOTP) or with SSO. SSO accounts are matched by the provider's subject
(sub), so renaming a user at the provider doesn't split their account.
Proxy-header logins carry only a username, so they match by username: renaming
a user at the provider gives them a new account there.
- A new SSO user gets a new account (with
OIDC_AUTO_CREATE_USERS). - An existing password account is never taken over by an SSO login just
OIDC_LINK_BY_USERNAME=trueinstead converts a non-admin password account
preferred_username,
email, name), so turn it on only while migrating, then off. Admin
accounts are never linked this way.
- On a fresh install, register the admin account first: SSO sign-in is refused
Recovering an account. librarr reset-password sets a new
password from the server's shell, for example
docker exec -it librarr librarr reset-password admin. For an SSO account,
add --unlink-sso to turn it back into a password account, for instance when
the identity provider is down. The last admin can't be demoted or deleted.
Upgrading from a version without account linking. Accounts that SSO created earlier become password accounts with an unknown random password, so their SSO logins are refused until they are linked:
- If the admin account was created by SSO, run
librarr reset-password and sign in with that password. Link it
to SSO under Settings > Account if you want.
- For the other users, either set
OIDC_LINK_BY_USERNAME=trueuntil they
SSO-only sign-in. With LOCAL_AUTH_DISABLED=true the login page goes
straight to the identity provider. Password sign-in and registration are
refused for everyone except local admins, who sign in at /login?local=1. The
first-run admin account can still be registered. Librarr tries to reach the
provider up to three times at startup, five seconds apart (configuration
errors such as a wrong issuer are not retried). If it still can't, local
sign-in stays enabled until the next restart, so you are not locked out. When
the provider starts alongside Librarr, start it first (in Compose,
depends_on with a health check).
Non-admin password accounts can no longer sign in to link themselves, so have
them link first, or migrate them with OIDC_LINK_BY_USERNAME.
The setting covers the web login only. Password accounts can still use the
OPDS feed (/opds) with their password, so existing e-readers keep working.
SSO accounts have no password, so they can't sign in to OPDS; e-readers
can't go through an SSO login.
Signing out. Signing out of an SSO account also ends the session at the
identity provider, when it advertises an end_session_endpoint (Authentik,
Keycloak, Authelia and most others do). Set OIDC_POST_LOGOUT_REDIRECT_URI to
come back to Librarr afterwards; without it, the provider shows its own
signed-out page. OIDC_LOGOUT_URL overrides the provider's endpoint. Local
admins signing out stay in Librarr. After signing out, the login page waits for
a click instead of redirecting to the provider, so a provider that couldn't end
its session doesn't silently sign you back in.
Download Clients
Librarr sends torrents to qBittorrent, Transmission or rTorrent. Configure one,
or configure both and choose with TORRENT_CLIENT. The category/save-path
settings below apply to all of them (Transmission uses the category as a torrent
label, which requires Transmission 3.0+; rTorrent stores it in d.custom1, shown as
the label in ruTorrent).
rTorrent notes. Downloads go to RTORRENT_DIRECTORY, or rTorrent's own
directory.default when it is empty; QB_*_SAVE_PATH is not used. Each torrent's
path is read from rTorrent at import time, so finished downloads can be moved, but
the folder a torrent finishes in must be mounted in Librarr at INCOMING_DIR (or
QB_AUDIOBOOK_SAVE_PATH / MANGA_INCOMING_DIR). To keep seeding, set
REMOVE_TORRENT_AFTER_IMPORT=false with IMPORT_MODE=hardlink. rTorrent's API cannot
delete data, so removing a torrent with its files deletes them through that mount.
| Variable | Default | Description |
|----------|---------|-------------|
| TORRENT_CLIENT | | Active torrent backend when both are configured: empty (auto, qBittorrent preferred), qbittorrent, transmission, or rtorrent |
| QB_URL | | qBittorrent Web UI URL |
| QB_USER | admin | qBittorrent username |
| QB_PASS | | qBittorrent password |
| QB_SAVE_PATH | /downloads | Ebook download path as seen by qBittorrent (the remote/client-side path) |
| QB_CATEGORY | librarr | Torrent category for ebooks |
| QB_AUDIOBOOK_SAVE_PATH | /audiobooks-incoming | Audiobook download path |
| QB_AUDIOBOOK_CATEGORY | audiobooks | Torrent category for audiobooks |
| QB_MANGA_SAVE_PATH | /manga-incoming | Manga download path |
| QB_MANGA_CATEGORY | manga | Torrent category for manga |
| QB_PRIORITY | 1 | Download client priority (lower = preferred) |
| REMOVE_TORRENT_AFTER_IMPORT | true | Remove the torrent from the download client after a successful import. Set it to false to keep seeding — librarr then hardlinks imports instead of moving them, so the payload stays where the client can seed it. See Seeding after import. |
| TRANSMISSION_URL | | Transmission RPC URL (e.g. http://transmission:9091) |
| TRANSMISSION_USER | | Transmission RPC username (optional — only if RPC auth is enabled) |
| TRANSMISSION_PASS | | Transmission RPC password (optional) |
| RTORRENT_URL | | rTorrent XML-RPC URL over HTTP, e.g. http://rutorrent/RPC2 (no path means /RPC2) |
| RTORRENT_USER | | HTTP basic auth username (optional) |
| RTORRENT_PASS | | HTTP basic auth password (optional) |
| RTORRENT_DIRECTORY | | Download directory (optional; empty uses rTorrent's directory.default) |
| SABNZBD_URL | | SABnzbd URL |
| SABNZBD_API_KEY | | SABnzbd API key |
| SABNZBD_CATEGORY | librarr | NZB download category |
| SAB_PRIORITY | 2 | Download client priority |
Prowlarr
| Variable | Default | Description |
|----------|---------|-------------|
| PROWLARR_URL | | Prowlarr URL |
| PROWLARR_API_KEY | | Prowlarr API key |
Anna's Archive
Search is currently blocked upstream (August 2026). Anna's Archive sits
behind DDoS-Guard, which serves an hCaptcha "I am human" check on
/searchand/md5/*. This blocks a real browser, not just scrapers, so the
annas source returns no results and will report itself unhealthy. The cause
is upstream and legal rather than a librarr bug: after the January 2026
lawsuit, Cloudflare disabled the nameservers for several AA domains and the
survivors moved to DDoS-Guard, followed by a global domain takedown order.
> What this means in practice:
> - Search: unavailable. Every surviving domain (.gl,.pk,.gd)
behaves the same, and AA publishes no JSON search API — its own API
self-documentation (GET /dyn/api/fast_download.json with no parameters)
lists fast_download as the only stable endpoint.
- Downloads: still work with a membership key. /dyn/api/fast_download.json
is not behind the challenge, so ANNAS_ARCHIVE_SECRET_KEY below still
resolves MD5s to download URLs.
- FlareSolverr does not help. It solves Cloudflare, not DDoS-Guard, and
times out on the hCaptcha challenge.
-annas-archive.organd.seno longer resolve, andannas-archive.liis
an unrelated parked domain — do not point ANNAS_ARCHIVE_DOMAIN at it.
> Other sources (Prowlarr, Gutenberg, Open Library, Standard Ebooks, Z-Library)
are unaffected.
| Variable | Default | Description |
|----------|---------|-------------|
| ANNAS_ARCHIVE_DOMAIN | (from sources registry) | AA mirror hostname (no scheme) |
| ANNAS_ARCHIVE_SECRET_KEY | | Account secret key from the AA login page (also called a donator key in some tools). Used for /dyn/api/fast_download.json when the account has an active membership. Env alias AA_DONATOR_KEY is still accepted. Without a key, downloads use public LibGen mirrors. |
Library Imports
| Variable | Default | Description |
|----------|---------|-------------|
| CALIBRE_LIBRARY_PATH | | Path to Calibre library (auto-import via calibredb) |
| CALIBRE_URL | | Calibre-Web URL |
| KAVITA_URL | | Kavita server URL |
| KAVITA_USER | | Kavita username |
| KAVITA_PASS | | Kavita password |
| KAVITA_API_KEY | | Kavita auth key (Kavita 0.8.9+), used instead of KAVITA_USER and KAVITA_PASS. Library scans need a key from an admin account |
| KAVITA_LIBRARY_PATH | | Kavita ebook library path |
| KAVITA_MANGA_LIBRARY_PATH | | Kavita manga library path |
| KAVITA_EBOOK_LIBRARY_ID | | Kavita library to scan after an ebook import (blank = all) |
| KAVITA_MANGA_LIBRARY_ID | | Kavita library to scan after a manga import (blank = all) |
| KAVITA_PUBLIC_URL | | Kavita URL for external links |
| ABS_URL | | Audiobookshelf server URL |
| ABS_TOKEN | | Audiobookshelf API token |
| ABS_LIBRARY_ID | | Audiobookshelf audiobook library ID |
| ABS_EBOOK_LIBRARY_ID | | Audiobookshelf ebook library ID |
| ABS_PUBLIC_URL | | Audiobookshelf URL for external links |
| KOMGA_URL | | Komga server URL |
| KOMGA_USER | | Komga username |
| KOMGA_PASS | | Komga password |
| KOMGA_LIBRARY_ID | | Komga library ID |
| KOMGA_LIBRARY_PATH | | Komga library path |
File Organization
| Variable | Default | Description |
|----------|---------|-------------|
| FILE_ORG_ENABLED | true | Auto-organize downloaded files |
| IMPORT_MODE | (automatic) | How organized files reach the library: move (the download payload is consumed), hardlink (a second name for the same data — keeps seeding, no extra disk, same filesystem only), or copy (keeps seeding, uses twice the disk). Leave it unset and it follows REMOVE_TORRENT_AFTER_IMPORT: removing torrents moves, keeping them hardlinks. See Seeding after import. |
| EBOOK_DIR | /books/ebooks | Organized ebook destination |
| AUDIOBOOK_DIR | /books/audiobooks | Organized audiobook destination |
| MANGA_DIR | /books/manga | Organized manga destination |
| INCOMING_DIR | /data/incoming | Incoming file staging directory as seen by Librarr; qBittorrent paths beneath QB_SAVE_PATH are translated here before import |
| MANGA_INCOMING_DIR | /data/manga-incoming | Manga incoming staging directory |
Seeding after import
One setting. For private trackers with seed-time minimums:
REMOVE_TORRENT_AFTER_IMPORT=false
Two separate things have to survive an import for a torrent to keep seeding —
its record in the client, and its payload files where the client wrote
them. Keeping the record is what that setting says; keeping the payload is what
IMPORT_MODE controls. Rather than make you set both, librarr infers the second
from the first:
| REMOVE_TORRENT_AFTER_IMPORT | IMPORT_MODE unset resolves to | Result |
|---|---|---|
| true (default) | move | Payload goes into the library; nothing is left to seed, and nothing needs to be. Unchanged from earlier releases. |
| false | hardlink | Payload stays in the download folder, the library gets a second name for the same data, and the torrent keeps seeding. |
Setting IMPORT_MODE explicitly always wins over that inference — use it to
force copy on a filesystem without hardlinks, or hardlink while still
removing torrents. IMPORT_MODE=move together with
REMOVE_TORRENT_AFTER_IMPORT=false is the one combination that cannot seed;
librarr logs a warning at startup if you configure it.
Notes on the modes:
hardlinkcosts no extra disk — the library entry and the download folder
/data) rather than separate /downloads and /books
mounts. When a link cannot be created (different filesystem, or a filesystem
without hardlinks such as CIFS/exFAT), librarr logs a warning and copies
instead, so the import still lands.
copyalways works but stores the book twice until you delete the torrent.
- With
hardlinkorcopy, if you also leaveREMOVE_TORRENT_AFTER_IMPORT=true,
- Seed goals belong to your torrent client, not to librarr. Once imports
- File organization must be on (
FILE_ORG_ENABLED=true) for any of this; with it
Scope. IMPORT_MODE applies to torrent imports and manual imports — the
cases where the source may be a live torrent payload. Everything else always
moves, because nothing is seeding it and leaving a second copy behind would just
accumulate: SABnzbd/usenet imports (librarr never removes anything from the
completed folder), web UI uploads, and direct HTTP downloads from sources like
Anna's Archive, all of which are librarr's own files.
Sources Registry
| Variable | Default | Description |
|----------|---------|-------------|
| LIBRARR_SOURCES_URL | (built-in default points at librarr-sources) | URL of a JSON sources registry, fetched at startup |
| LIBRARR_SOURCES_PATH | | Local path to a sources registry JSON file; takes precedence over URL |
Resolution order on startup: LIBRARR_SOURCES_PATH → LIBRARR_SOURCES_URL → built-in default URL → on-disk cache () → empty registry. Successful URL fetches are cached so subsequent restarts work offline. If you set LIBRARR_SOURCES_URL, your value replaces the default — it does not stack on top of it.
Legacy per-source env vars (e.g. PROWLARR_URL and other per-driver overrides) continue to be honored and override values loaded from the registry.
Search / Downloads
| Variable | Default | Description |
|----------|---------|-------------|
| MIN_TORRENT_SIZE_BYTES | 10000 | Minimum torrent size filter (10 KB) |
| MAX_TORRENT_SIZE_BYTES | 2000000000 | Maximum torrent size filter (2 GB) |
| MAX_RETRIES | 2 | Download retry attempts |
| RETRY_BACKOFF_SECONDS | 60 | Seconds between retries |
| CIRCUIT_BREAKER_THRESHOLD | 3 | Failures before disabling a source |
| CIRCUIT_BREAKER_TIMEOUT | 300 | Seconds before re-enabling a tripped source |
Feature Toggles
| Variable | Default | Description |
|----------|---------|-------------|
| RATE_LIMIT_ENABLED | true | Per-source rate limiting |
| METRICS_ENABLED | true | Prometheus metrics endpoint |
| WEBNOVEL_ENABLED | true | Web novel search (requires lncrawl container) |
| WISHLIST_CLEANUP_ENABLED | false | Periodically remove wishlist items already found in the library |
| WISHLIST_CLEANUP_INTERVAL_HOURS | 12 | Hours between wishlist cleanup scans |
| WISHLIST_CLEANUP_DRY_RUN | true | Log conservative wishlist cleanup matches without deleting |
| MANGADEX_ENABLED | true | MangaDex search |
| AUTHOR_MONITOR_ENABLED | false | Background author monitoring |
| AUTHOR_CHECK_INTERVAL_DAYS | 7 | Default check interval for newly followed authors |
| AUTHOR_MONITOR_AUTO_ADD | true | Put newly found works on the wanted list (otherwise only notify) |
| OPENLIBRARY_URL | https://openlibrary.org | Open Library origin used by the author monitor |
Wanted List & Quality Profiles
These are also editable at runtime under Settings → Wanted List & Quality;
values saved there persist in settings.json and override the environment.
| Variable | Default | Description |
|----------|---------|-------------|
| SCHEDULER_ENABLED | false | Search monitored wanted items in the background |
| SCHEDULER_INTERVAL_HOURS | 24 | Hours between passes |
| SCHEDULER_AUTO_DOWNLOAD | false | Grab the best acceptable release (otherwise only notify) |
| SCHEDULER_MIN_SCORE | 70 | Minimum match confidence (0-100); the profile ranks what passes |
| SCHEDULER_ITEM_DELAY_SECONDS | 5 | Pause between two items that hit the sources |
| AUTO_UPGRADE_ENABLED | false | Global switch: replace an existing file when a better-ranked format appears (each profile also has its own flag) |
| UPGRADE_KEEP_OLD_FILES | false | Keep the superseded file and its library row after an upgrade instead of deleting it |
Torznab
| Variable | Default | Description |
|----------|---------|-------------|
| TORZNAB_API_KEY | | API key for the Torznab endpoint |
API Endpoints
Authentication
| Method | Path | Description |
|--------|------|-------------|
| POST | /api/login | Session login |
| POST | /api/login/totp | TOTP 2FA verification |
| POST | /api/register | Register new user |
| POST | /api/logout | End session |
| GET | /api/auth/status | Current auth state |
User Management (admin)
| Method | Path | Description |
|--------|------|-------------|
| GET | /api/users | List all users |
| PATCH | /api/users/{id} | Update user role/status |
| DELETE | /api/users/{id} | Delete user |
TOTP 2FA
| Method | Path | Description |
|--------|------|-------------|
| POST | /api/totp/setup | Generate TOTP secret + QR code |
| POST | /api/totp/verify | Verify and enable TOTP |
| POST | /api/totp/disable | Disable TOTP |
| GET | /api/totp/status | Check if TOTP is enabled |
Search
| Method | Path | Description |
|--------|------|-------------|
| GET | /api/search?q= | Search ebooks across all sources |
| GET | /api/search/audiobooks?q= | Search audiobooks |
| GET | /api/search/manga?q= | Search manga |
Every result carries in_library (always present), plus library_item_id and
library_title when the book is already in your library.
Downloads
| Method | Path | Description |
|--------|------|-------------|
| POST | /api/download | Download a direct-download result |
| POST | /api/download/torrent | Download a torrent result |
| POST | /api/download/annas | Download via content-hash (MD5/key) lookup |
| POST | /api/download/audiobook | Download an audiobook |
| GET | /api/downloads | List active/completed downloads |
| DELETE | /api/downloads/torrent/{hash} | Remove a torrent download |
| DELETE | /api/downloads/novel/{jobID} | Remove a novel download job |
| POST | /api/downloads/clear | Clear finished downloads |
| POST | /api/downloads/jobs/{id}/retry | Retry a failed download |
All four download endpoints refuse a book that is already in your library with
409 Conflict and {"code": "already_in_library", "library_item_id": N}. Set
"force": true on the request to download it anyway (a different edition, a
different format); that is what the UI's Download anyway button sends.
Library
| Method | Path | Description |
|--------|------|-------------|
| GET | /api/library | List ebooks in library |
| GET | /api/library/audiobooks | List audiobooks |
| GET | /api/library/manga | List manga |
| DELETE | /api/library/book/{id} | Remove ebook |
| DELETE | /api/library/audiobook/{id} | Remove audiobook |
| DELETE | /api/library/manga/{id} | Remove a local manga library record |
| GET | /api/stats | Library statistics |
| GET | /api/activity | Recent activity log |
Removing a local library item preserves the file on disk and returns linked
wanted entries to missing. Local ebooks, audiobooks, and manga all offer a
confirmation before removal. Manga displayed from Kavita is managed through
the Open in Kavita link; its series IDs are not Librarr item IDs. If Kavita
is configured, API callers removing a local manga record must explicitly pass
?source=local with the Librarr item ID.
Requests
| Method | Path | Description |
|--------|------|-------------|
| POST | /api/requests | Create a book request |
| GET | /api/requests | List all requests |
| GET | /api/requests/{id} | Get request details |
| PUT | /api/requests/{id}/approve | Approve request (admin) |
| PUT | /api/requests/{id}/cancel | Cancel request |
| PUT | /api/requests/{id}/retry | Retry failed request (admin) |
| PUT | /api/requests/{id}/select | Select search result (admin) |
| DELETE | /api/requests/{id} | Delete request (admin) |
Wishlist
| Method | Path | Description |
|--------|------|-------------|
| GET | /api/wishlist | List wanted items with derived state, current_format, profile_name, cutoff_met |
| POST | /api/wishlist | Add a wanted item (title, author, media_type, quality_profile_id, monitored) |
| PATCH | /api/wishlist/{id} | Change monitored / quality_profile_id |
| POST | /api/wishlist/{id}/search | Run the profile decision now (admin); {"dry_run": true} explains every candidate without grabbing |
| DELETE | /api/wishlist/{id} | Remove from the wanted list |
A wanted item is never deleted by a grab. It remembers the library file that satisfies it; the scheduler skips it once the file meets the profile's cutoff (or when upgrades are off) and otherwise keeps looking for a strictly better format. Deleting the library file sends the item back to missing; files that arrive by other routes are linked on the next pass by title and author.
Notifications
| Method | Path | Description |
|--------|------|-------------|
| GET | /api/notifications | List notifications |
| GET | /api/notifications/unread | Unread count |
| PUT | /api/notifications/{id}/read | Mark as read |
| PUT | /api/notifications/read-all | Mark all as read |
| DELETE | /api/notifications/{id} | Delete notification |
Quality Profiles
| Method | Path | Description |
|--------|------|-------------|
| GET | /api/quality-profiles | List quality profiles |
| GET | /api/quality-profiles/default | Built-in profile per media type + global upgrade switches |
| GET | /api/quality-profiles/formats | Known formats per media type (for editors) |
| POST | /api/quality-profiles | Create profile (admin) |
| PUT | /api/quality-profiles/{id} | Update profile (admin) |
| DELETE | /api/quality-profiles/{id} | Delete profile (admin) |
Release Profiles
| Method | Path | Description |
|--------|------|-------------|
| GET | /api/release-profiles | List release profiles |
| POST | /api/release-profiles | Create profile (admin) |
| PUT | /api/release-profiles/{id} | Update profile (admin) |
| DELETE | /api/release-profiles/{id} | Delete profile (admin) |
Blocklist
| Method | Path | Description |
|--------|------|-------------|
| GET | /api/blocklist | List blocked items |
| POST | /api/blocklist | Add entry (admin) |
| DELETE | /api/blocklist/{id} | Remove entry (admin) |
| POST | /api/blocklist/clear | Clear all (admin) |
Series
| Method | Path | Description |
|--------|------|-------------|
| GET | /api/series | List detected series |
| GET | /api/series/{name}/missing | Find missing volumes |
| POST | /api/series/{name}/search-missing | Search for missing volumes |
Authors
| Method | Path | Description |
|--------|------|-------------|
| GET | /api/authors | List monitored authors |
| POST | /api/authors/monitor | Add author (admin) |
| DELETE | /api/authors/{id} | Remove author (admin) |
| PATCH | /api/authors/{id} | Change check interval / auto-add (admin) |
| POST | /api/authors/{id}/check | Check one author now (admin); first check records a baseline |
Reading History
| Method | Path | Description |
|--------|------|-------------|
| POST | /api/history | Add history entry |
| GET | /api/history | Get reading history |
| PATCH | /api/history/{id} | Update entry |
| DELETE | /api/history/{id} | Delete entry |
| GET | /api/history/stats | Reading statistics |
Tags
| Method | Path | Description |
|--------|------|-------------|
| GET | /api/tags | List all tags |
| POST | /api/tags | Create tag |
| DELETE | /api/tags/{id} | Delete tag |
| GET | /api/library/{id}/tags | Get item tags |
| POST | /api/library/{id}/tags | Add tags to item |
| DELETE | /api/library/{id}/tags/{tagId} | Remove tag from item |
Import / Export
| Method | Path | Description |
|--------|------|-------------|
| POST | /api/import/csv | Bulk import from CSV |
| POST | /api/import/goodreads | Import Goodreads CSV |
| POST | /api/import/storygraph | Import StoryGraph CSV |
| POST | /api/import/library | Import library JSON |
| POST | /api/import/wishlist | Import wishlist JSON |
| POST | /api/import/scan | Scan directory for files |
| POST | /api/import/files | Import scanned files |
| GET | /api/export/library | Export library as JSON |
| GET | /api/export/wishlist | Export wishlist as JSON |
| GET | /api/export/requests | Export requests as JSON |
Backup / Restore
| Method | Path | Description |
|--------|------|-------------|
| POST | /api/backup/create | Create backup |
| GET | /api/backup | Download latest backup |
| GET | /api/backup/list | List backups |
| POST | /api/restore | Restore from backup |
Scheduler
| Method | Path | Description |
|--------|------|-------------|
| GET | /api/scheduler/status | Scheduler status and next run |
| POST | /api/scheduler/run | Trigger a pass (admin); ?wait=1 runs it synchronously and returns the statistics |
| PUT | /api/scheduler/config | Update and persist scheduler / upgrade settings (admin) |
Webhooks (admin)
| Method | Path | Description |
|--------|------|-------------|
| GET | /api/webhooks | List webhook configs |
| POST | /api/webhooks | Create webhook |
| DELETE | /api/webhooks/{id} | Delete webhook |
| POST | /api/webhooks/test | Test webhook delivery |
Admin
| Method | Path | Description |
|--------|------|-------------|
| GET | /api/admin/dashboard | Dashboard stats |
| GET | /api/admin/activity | Admin activity log |
| GET | /api/admin/health | Source and system health |
| POST | /api/admin/bulk/retry | Bulk retry downloads |
| POST | /api/admin/bulk/cancel | Bulk cancel downloads |
Connection Tests (admin)
| Method | Path | Description |
|--------|------|-------------|
| POST | /api/test/prowlarr | Test Prowlarr connection |
| POST | /api/test/qbittorrent | Test qBittorrent connection |
| POST | /api/test/transmission | Test Transmission connection |
| POST | /api/test/rtorrent | Test rTorrent connection |
| POST | /api/test/audiobookshelf | Test Audiobookshelf connection |
| POST | /api/test/kavita | Test Kavita connection |
| POST | /api/test/sabnzbd | Test SABnzbd connection |
System
| Method | Path | Description |
|--------|------|-------------|
| GET | /health | Health check |
| GET | /metrics | Prometheus metrics |
Torznab / Newznab API
Librarr exposes a standard Torznab API at /torznab/api that can be added as an indexer in Prowlarr, Readarr, or any Torznab-compatible application.
Setup in Prowlarr / Readarr:
- Go to Settings > Indexers > Add
- Select "Generic Torznab" (or "Generic Newznab")
- Set the URL to
http://your-librarr-host:5050/torznab/api - Set the API Key to your
TORZNAB_API_KEYvalue - Test and save
GET /torznab/api?t=caps returns the supported search categories and capabilities.
OPDS Feed
Librarr serves an OPDS 1.2 catalog at /opds for e-reader apps (KOReader, Moon+ Reader, Librera, etc.).
| Path | Description |
|------|-------------|
| /opds | Catalog root |
| /opds/books | Browse all books |
| /opds/search?q= | Search the catalog |
| /opds/download/{id} | Download a book file |
| /opds/opensearch.xml | OpenSearch descriptor |
Setup: Add http://your-librarr-host:5050/opds as an OPDS catalog in your e-reader. If auth is enabled, enter your Librarr username and password.
When auth is enabled every /opds route requires credentials (HTTP Basic, a
browser session, or the API key). Accounts with two-factor auth enabled, and
SSO-only accounts, cannot use their password here: enter the API_KEY value as
the password instead (any username). Repeated failed logins from one address
are temporarily blocked. Basic auth sends the password with every request, so
use HTTPS when the feed is reachable beyond your LAN.
Using Librarr with Claude / MCP
Librarr's REST API is the integration surface, so any Model Context Protocol server can expose Librarr's search, download, and library tools to an LLM (Claude Desktop, Claude Code, Open WebUI, Cursor, etc.). There's no built-in MCP server in Librarr — you wire it into the MCP server you already run.
Auth: every endpoint accepts an X-Api-Key header or ?apikey= query param. Set API_KEY in your docker-compose.yml and reuse it from your MCP server.
Minimal Python example using fastmcp:
# librarr_mcp.py — run as: fastmcp run librarr_mcp.py
import os, httpx
from fastmcp import FastMCP
LIBRARR = os.environ["LIBRARR_URL"] # e.g. http://librarr.lan:5050
API_KEY = os.environ["LIBRARR_API_KEY"]
HEADERS = {"X-Api-Key": API_KEY}
mcp = FastMCP("librarr")
@mcp.tool()
async def search_books(query: str) -> list[dict]:
"""Search for an ebook across all configured sources."""
async with httpx.AsyncClient() as c:
r = await c.get(f"{LIBRARR}/api/search", params={"q": query}, headers=HEADERS)
return r.json()
@mcp.tool()
async def download_book(result: dict) -> dict:
"""Queue an ebook download. Pass a result object returned by search_books — it
already contains source, title, and the source-specific URL field
(download_url / magnet_url / md5 / etc.)."""
async with httpx.AsyncClient() as c:
r = await c.post(f"{LIBRARR}/api/download", json=result, headers=HEADERS)
return r.json()
@mcp.tool()
async def list_downloads() -> list[dict]:
"""List active and recent download jobs with status."""
async with httpx.AsyncClient() as c:
r = await c.get(f"{LIBRARR}/api/downloads", headers=HEADERS)
return r.json()
Register with Claude Desktop / Claude Code (~/.claude.json or claude_desktop_config.json):
{
"mcpServers": {
"librarr": {
"command": "fastmcp",
"args": ["run", "/path/to/librarr_mcp.py"],
"env": {
"LIBRARR_URL": "http://librarr.lan:5050",
"LIBRARR_API_KEY": "your-api-key-here"
}
}
}
}
The same pattern works for audiobooks (/api/search/audiobooks, /api/download/audiobook) and manga (/api/search/manga, /api/download/torrent). Wrap whichever subset of the API Endpoints above is useful to your assistant.
Architecture
Single static binary, zero CGO dependencies, pure-Go SQLite via modernc.org/sqlite.
cmd/librarr/main.go Entry point
internal/
config/config.go Env var configuration
db/ SQLite persistence + migrations
models/ Core types (books, downloads, wishlist, requests, etc.)
api/ HTTP handlers, router, middleware
auth.go Session auth + bcrypt
totp.go TOTP 2FA (RFC 6238)
oidc.go OpenID Connect / SSO
search.go Search endpoint handlers
download.go Download management
library.go Library CRUD
requests.go Request workflow
notifications.go In-app notifications
qualityprofile.go Quality profiles
releaseprofile.go Release profiles
blocklist.go Blocklist management
tags.go Tag management
history.go Reading history
series.go Series detection + auto-complete
importexport.go Import/export (JSON, CSV, Goodreads, StoryGraph)
backup.go Database backup/restore
webhook.go Webhook configuration
opds.go OPDS 1.2 feed
admin.go Admin dashboard
metrics.go Prometheus metrics
csv.go CSV bulk import
ratelimit.go Per-source rate limiting
router.go Route registration
search/ Search source implementations
download/ Download manager (qBit, Transmission, rTorrent, Deluge, SABnzbd)
organize/ Post-download file organization + library import
metadata/ Open Library metadata enrichment
scheduler/ Background scheduler, series detector, author monitor
webhook/ Webhook sender (Discord + generic)
torznab/ Torznab/Newznab API handler
web/
index.html Single-page web UI (Tailwind CSS)
Dockerfile Multi-stage Alpine build
.goreleaser.yml Cross-platform release builds
License
MIT
Disclaimer
This software is provided for educational and personal use only. Users are responsible for ensuring their use complies with all applicable laws and regulations in their jurisdiction. The developers do not condone or encourage copyright infringement or any illegal activity. This tool does not host, store, or distribute any copyrighted content, a
... (README truncated for length)