# qui > Modern web interface for qBittorrent This file contains all documentation content in a single document following the llmstxt.org standard. ## Compatibility # qBittorrent Version Compatibility :::note qui officially supports qBittorrent 4.3.9 and newer as the baseline. The features below may require newer builds as noted, and anything older than 4.3.9 might still connect, but functionality is not guaranteed. ::: qui automatically detects the features available on each qBittorrent instance and adjusts the interface accordingly. Certain features require newer qBittorrent versions and will be disabled when connecting to older instances: | Feature | Minimum Version | Notes | | --- | --- | --- | | **Rename Torrent** | 4.1.0+ (Web API 2.0.0+) | Change the display name of torrents | | **Tracker Editing** | 4.1.5+ (Web API 2.2.0+) | Edit, add, and remove tracker URLs | | **File Priority Controls** | 4.1.5+ (Web API 2.2.0+) | Enable/disable files and adjust download priority levels | | **Rename File** | 4.2.1+ (Web API 2.4.0+) | Rename individual files within torrents | | **Rename Folder** | 4.3.3+ (Web API 2.7.0+) | Rename folders within torrents | | **Per-Torrent Temporary Download Path** | 4.4.0+ (Web API 2.8.4+) | A custom temporary download path may be set when adding torrents | | **Torrent Export (.torrent download)** | 4.5.0+ (Web API 2.8.11+) | Download .torrent files via `/api/v2/torrents/export`; first appeared in 4.5.0beta1 | | **Backups (.torrent archive export)** | 4.5.0+ (Web API 2.8.11+) | qui backups rely on `/torrents/export`; the backup UI is hidden when the endpoint is unavailable | | **Subcategories** | 4.6.0+ (Web API 2.9.0+) | Support for nested category structures (e.g., `Movies/Action`) | | **Torrent Creation** | 5.0.0+ (Web API 2.11.2+) | Create new .torrent files via the Web API | | **Path Autocomplete** | 5.0.0+ (Web API 2.11.2+) | Autocomplete suggestions for path inputs when adding torrents or creating .torrent files | | **External IP Reporting (IPv4/IPv6)** | 5.1.0+ (Web API 2.11.3+) | Exposes `last_external_address_v4` / `_v6` fields | | **Tracker Health Status** | 5.1.0+ (Web API 2.11.4+) | Automatically detects unregistered torrents and tracker issues | :::note Hybrid and v2 torrent creation requires a qBittorrent build that links against libtorrent v2. Builds compiled with libtorrent 1.x ignore the `format` parameter. ::: ## Troubleshooting: Missing Features ### Create Torrent button is not visible The **Create Torrent** button in the header bar is only displayed when qui detects that your qBittorrent instance supports the torrent creation API. If you do not see the button, your qBittorrent version is below **5.0.0** (Web API v2.11.2). To resolve this, upgrade qBittorrent to version 5.0.0 or later and refresh the qui web UI. ### Hybrid and v2 torrent formats are unavailable Even with qBittorrent 5.0.0+, the **hybrid** and **v2** torrent format options require qBittorrent to be built against **libtorrent v2.x**. If your build uses libtorrent 1.x, the torrent creation dialog will display an alert indicating that only the **v1** format is available. This is a build-time dependency of qBittorrent itself and cannot be changed through qui. ### "Too many active torrent creation tasks" error There is a limit on the number of concurrent torrent creation tasks. If you see a **409 Conflict** error with this message, wait for your existing creation tasks to finish before starting new ones. You can monitor active tasks in the torrent creation task list. --- ## Metrics # Prometheus Metrics Prometheus metrics can be enabled to monitor your qBittorrent instances. When enabled, metrics are served on a **separate port** (default: 9074) with **no authentication required** for easier monitoring setup. ## Enable Metrics Metrics are **disabled by default**. Enable them via configuration file or environment variable: ### Config File (`config.toml`) ```toml metricsEnabled = true metricsHost = "127.0.0.1" # Bind to localhost only (recommended for security) metricsPort = 9074 # Standard Prometheus port range # metricsBasicAuthUsers = "user:$2y$10$bcrypt_hash_here" # Optional: basic auth ``` ### Environment Variables ```bash QUI__METRICS_ENABLED=true QUI__METRICS_HOST=0.0.0.0 # Optional: bind to all interfaces if needed QUI__METRICS_PORT=9074 # Optional: custom port QUI__METRICS_BASIC_AUTH_USERS="user:$2y$10$hash" # Optional: basic auth ``` ## Available Metrics - **Torrent counts** by status (downloading, seeding, paused, error) - **Transfer speeds** (upload/download bytes per second) - **Instance connection status** ## Prometheus Configuration Configure Prometheus to scrape the dedicated metrics port (no authentication required): ```yaml scrape_configs: - job_name: 'qui' static_configs: - targets: ['localhost:9074'] metrics_path: /metrics scrape_interval: 30s #basic_auth: #username: prometheus #password: yourpassword ``` All metrics are labeled with `instance_id` and `instance_name` for multi-instance monitoring. --- ## API # API Overview ## Documentation Interactive API documentation is available at `/api/docs` using Swagger UI. You can explore all endpoints, view request/response schemas, and test API calls directly from your browser. ## API Keys API keys allow programmatic access to qui without using session cookies. Create and manage them in Settings → API Keys. Include your API key in the `X-API-Key` header: ```bash curl -H "X-API-Key: YOUR_API_KEY_HERE" \ http://localhost:7476/api/instances ``` ## Security Notes - API keys are shown only once when created - save them securely - Each key can be individually revoked without affecting others - Keys have the same permissions as the main user account --- ## Base URL # Base URL Configuration If you need to serve qui from a subdirectory (e.g., `https://example.com/qui/`), you can configure the base URL. ## Using Environment Variable ```bash QUI__BASE_URL=/qui/ ./qui ``` ## Using Configuration File Edit your `config.toml`: ```toml baseUrl = "/qui/" ``` ## With Nginx Reverse Proxy ```nginx # Redirect /qui to /qui/ for proper SPA routing location = /qui { return 301 /qui/; } location /qui/ { proxy_pass http://localhost:7476/qui/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } ``` --- ## CLI Commands ## Generate Configuration File Create a default configuration file without starting the server: ```bash # Generate config in OS-specific default location ./qui generate-config # Generate config in custom directory ./qui generate-config --config-dir /path/to/config/ # Generate config with custom filename ./qui generate-config --config-dir /path/to/myconfig.toml ``` ## User Management Create and manage user accounts from the command line: ```bash # Create initial user account ./qui create-user --username admin --password mypassword # Create user with prompts (secure password input) ./qui create-user --username admin # Change password for existing user (no old password required) ./qui change-password --username admin --new-password mynewpassword # Change password with secure prompt ./qui change-password --username admin # Pipe passwords for scripting (works with both commands) echo "mypassword" | ./qui create-user --username admin echo "newpassword" | ./qui change-password --username admin printf "password" | ./qui change-password --username admin ./qui change-password --username admin < password.txt # All commands support custom config/data directories ./qui create-user --config-dir /path/to/config/ --username admin ``` ### Notes - Only one user account is allowed in the system - Passwords must be at least 8 characters long - Interactive prompts use secure input (passwords are masked) - Supports piped input for automation and scripting - Commands will create the database if it doesn't exist - No password confirmation required - perfect for automation ## Update Command Keep your qui installation up-to-date: ```bash # Update to the latest version ./qui update ``` ## Command Line Flags ```bash # Specify config directory (config.toml will be created inside) ./qui serve --config-dir /path/to/config/ # Specify data directory for database and other data files ./qui serve --data-dir /path/to/data/ ``` --- ## Environment Variables Configuration is stored in `config.toml` (created automatically on first run, or manually with `qui generate-config`). You can also use environment variables: ## Server ```bash QUI__HOST=0.0.0.0 # Listen address QUI__PORT=7476 # Port number QUI__BASE_URL=/qui/ # Optional: serve from subdirectory ``` ## Security ```bash QUI__SESSION_SECRET_FILE=... # Path to file containing secret. Takes precedence over QUI__SESSION_SECRET QUI__SESSION_SECRET=... # Auto-generated if not set ``` ## Logging ```bash QUI__LOG_LEVEL=INFO # Options: ERROR, DEBUG, INFO, WARN, TRACE QUI__LOG_PATH=... # Optional: log file path QUI__LOG_MAX_SIZE=50 # Optional: rotate when log file exceeds N megabytes (default: 50) QUI__LOG_MAX_BACKUPS=3 # Optional: retain N rotated files (default: 3, 0 keeps all) ``` When `logPath` is set the server writes to disk using size-based rotation. Adjust `logMaxSize` and `logMaxBackups` in `config.toml` or the corresponding environment variables to control the rotation thresholds and retention. ## Storage ```bash QUI__DATA_DIR=... # Optional: custom data directory (default: next to config) ``` ## Tracker Icons ```bash QUI__TRACKER_ICONS_FETCH_ENABLED=false # Optional: set to false to disable remote tracker icon fetching (default: true) ``` ## Metrics ```bash QUI__METRICS_ENABLED=true # Optional: enable Prometheus metrics (default: false) QUI__METRICS_HOST=127.0.0.1 # Optional: metrics server bind address (default: 127.0.0.1) QUI__METRICS_PORT=9074 # Optional: metrics server port (default: 9074) QUI__METRICS_BASIC_AUTH_USERS=user:hash # Optional: basic auth for metrics (bcrypt hashed) ``` ## External Programs Configure the allow list from `config.toml`; there is no environment override to keep it read-only from the UI. ## Default Locations - **Linux/macOS**: `~/.config/qui/config.toml` - **Windows**: `%APPDATA%\qui\config.toml` --- ## OIDC # OpenID Connect (OIDC) Set `QUI__OIDC_ENABLED=true` to hand authentication off to an external identity provider. The built-in login screen automatically offers a "Sign in with OIDC" button when the backend detects a valid OIDC configuration. ## Configuration Options | Variable | Description | |----------|-------------| | `QUI__OIDC_ISSUER` | Issuer URL from your IdP (e.g. `https://auth.example.com/realms/main`) | | `QUI__OIDC_CLIENT_ID` | Client ID registered for qui | | `QUI__OIDC_CLIENT_SECRET` | Client secret generated by the provider | | `QUI__OIDC_CLIENT_SECRET_FILE` | Path to file containing client secret. Takes precedence over `QUI__OIDC_CLIENT_SECRET` | | `QUI__OIDC_REDIRECT_URL` | Must match the redirect URI allowed by the provider | | `QUI__OIDC_DISABLE_BUILT_IN_LOGIN` | Set to `true` to hide the local username/password form when OIDC is enabled | ## Redirect URL Format For a default install, use: ``` http://localhost:7476/api/auth/oidc/callback ``` When reverse proxying, include your base URL: ``` https://host/qui/api/auth/oidc/callback ``` ## Example Configuration ```bash QUI__OIDC_ENABLED=true \ QUI__OIDC_ISSUER=https://auth.example.com/realms/main \ QUI__OIDC_CLIENT_ID=qui \ QUI__OIDC_CLIENT_SECRET=super-secret-value \ QUI__OIDC_REDIRECT_URL=https://qui.example.com/api/auth/oidc/callback \ QUI__OIDC_DISABLE_BUILT_IN_LOGIN=true ``` You can set the same options in `config.toml` using the `oidc*` keys generated by `qui generate-config`. --- ## Automations Automations are a rule-based engine that automatically applies actions to torrents based on conditions. Use them to manage speed limits, delete old torrents, organize with tags and categories, and more. ## How Automations Work Automations are evaluated in **sort order** (first match wins for exclusive actions like delete). Each rule can match torrents using a flexible query builder with nested conditions. - **Automatic** - Background service scans torrents every 20 seconds - **Per-Rule Intervals** - Each rule can have its own interval (minimum 60 seconds, default 15 minutes) - **Manual** - Click "Apply Now" to trigger immediately (bypasses interval checks) - **Debouncing** - Same torrent won't be re-processed within 2 minutes ## Query Builder The query builder supports complex nested conditions with AND/OR groups. Drag conditions to reorder them. ### Available Condition Fields #### Identity Fields | Field | Description | |-------|-------------| | Name | Torrent display name (supports cross-category operators) | | Hash | Info hash | | Category | qBittorrent category | | Tags | Set-based tag matching | | State | Status filter (see State Values below) | #### Path Fields | Field | Description | |-------|-------------| | Save Path | Download location | | Content Path | Full path to content | #### Size Fields (bytes) | Field | Description | |-------|-------------| | Size | Selected file size | | Total Size | Total torrent size | | Downloaded | Bytes downloaded | | Uploaded | Bytes uploaded | | Amount Left | Remaining bytes | | Free Space | Free space on disk (configurable source - see [Free Space Source](#free-space-source)) | #### Time Fields | Field | Description | |-------|-------------| | Seeding Time | Time spent seeding (seconds) | | Time Active | Total active time (seconds) | | Added On Age | Time since added | | Completion On Age | Time since completed | | Last Activity Age | Time since last activity | #### Progress Fields | Field | Description | |-------|-------------| | Ratio | Upload/download ratio | | Progress | Download progress (0-100%) | | Availability | Distributed copies available | #### Speed Fields (bytes/s) | Field | Description | |-------|-------------| | Download Speed | Current download speed | | Upload Speed | Current upload speed | #### Peer Fields | Field | Description | |-------|-------------| | Active Seeders | Currently connected seeders | | Active Leechers | Currently connected leechers | | Total Seeders | Tracker-reported seeders | | Total Leechers | Tracker-reported leechers | | Trackers Count | Number of trackers | #### Tracker/Status Fields | Field | Description | |-------|-------------| | Tracker | Primary tracker URL | | Private | Boolean - is private tracker | | Is Unregistered | Boolean - tracker reports unregistered | | Comment | Torrent comment field | #### Advanced Fields | Field | Description | |-------|-------------| | Hardlink Scope | `none`, `torrents_only`, or `outside_qbittorrent` (requires local filesystem access) | | Has Missing Files | Boolean - completed torrent has files missing on disk (requires local filesystem access) | ### State Values The State field matches these status buckets: | State | Description | |-------|-------------| | `downloading` | Actively downloading | | `uploading` | Actively uploading | | `completed` | Download finished | | `stopped` | Paused by user | | `active` | Has transfer activity | | `inactive` | No current activity | | `running` | Not paused | | `stalled` | No peers available | | `errored` | Has errors | | `tracker_down` | Tracker unreachable | | `checking` | Verifying files | | `moving` | Moving files | | `missingFiles` | Files not found | | `unregistered` | Tracker reports unregistered | ### Operators **String:** equals, not equals, contains, not contains, starts with, ends with, matches regex **Numeric:** `=`, `!=`, `>`, `>=`, `<`, `<=`, between **Boolean:** is, is not **State:** is, is not **Cross-Category (Name field only):** - `EXISTS_IN` - Exact name match in target category - `CONTAINS_IN` - Partial/normalized name match in target category ### Regex Support There are two ways to use regex in filter conditions: **The `matches regex` operator** is a dedicated operator where the value is always treated as a regex pattern. The condition is true if the pattern matches anywhere in the field value. **The regex toggle (`.*` button)** appears next to the value input on other string operators such as `equals`, `contains`, `not contains`, `starts with`, and `ends with`. When enabled, the value is treated as a regex pattern. :::warning Regex toggle overrides the selected operator When the regex toggle is enabled, the selected operator's logic (negation, containment, prefix/suffix matching) is **not applied**. The condition becomes a simple regex match, equivalent to `matches regex`, regardless of which operator is selected in the dropdown. This means `not contains` with the regex toggle enabled does **not** negate the match. It behaves the same as `matches regex` -- if the pattern is found, the condition evaluates to true. To negate a regex match, use the **NOT toggle** (the `IF / IF NOT` button at the start of the condition row) together with the `matches regex` operator. ::: Full RE2 (Go regex) syntax is supported. Patterns are case-insensitive by default. The UI validates patterns and shows helpful error messages for invalid regex. ### Tag conditions Each tag condition checks against a **single value**. The value field does not support comma-separated lists -- if you enter `tag1, tag2, tag3` as the value, it will be treated as one literal string, not three separate tags. Without regex enabled, tag operators (`equals`, `not equals`, `contains`, `not contains`) compare the condition value against each of the torrent's tags individually. - `equals` / `not equals`: exact tag membership (case-insensitive) - `contains` / `not contains`: substring match per tag (case-insensitive) :::warning Tag operators: `contains` is substring matching `Tags contains tag1` will also match torrents tagged `tag10`. For exact tag membership, prefer `equals` / `not equals` with one condition per tag and combine them with an **OR group**. ::: #### Matching any of multiple tags To check whether a torrent has **any one of** several tags, create an **OR group** with one condition per tag (exact match): | # | Field | Operator | Value | |---|-------|----------|-------| | 1 | Tags | equals | tag1 | | 2 | Tags | equals | tag2 | | 3 | Tags | equals | tag3 | Group these with **OR** logic so the rule matches when at least one tag is present. To **exclude** torrents that have any of several tags, create an **AND group** of `not equals` conditions (exact match): | # | Field | Operator | Value | |---|-------|--------------|-------| | 1 | Tags | not equals | tag1 | | 2 | Tags | not equals | tag2 | | 3 | Tags | not equals | tag3 | Group these with **AND** logic -- all three must be true, meaning none of those tags are present. #### Using regex for tag matching As an alternative to multiple conditions, you can use regex to match against several tags in a single condition. When regex is enabled, the pattern matches against the **full raw tag string** (e.g. `cross-seed, noHL, racing`) rather than checking each tag individually. For example, to exclude torrents tagged with `tag1` or `tag2`, use a single condition: - Field: `Tags` - Toggle: `IF NOT` (negate the match) - Operator: `matches regex` - Value: `(^|,\s*)(tag1|tag2)(\s*,|$)` This evaluates the regex against the raw tags string. The delimiter-aware pattern ensures `tag1` does not match `tag10`. The `IF NOT` toggle then negates the result, so the condition is true only for torrents that do **not** have either tag. ## Tracker Matching This is sort of not needed, since you can already scope trackers outside the workflows. But its available either way. | Pattern | Example | Matches | |---------|---------|---------| | All | `*` | Every tracker | | Exact | `tracker.example.com` | Only that domain | | Glob | `*.example.com` | Subdomains | | Suffix | `.example.com` | Domain and subdomains | Separate multiple patterns with commas, semicolons, or pipes. All matching is case-insensitive. ## Actions Actions can be combined (except Delete which must be standalone). Each action supports an optional condition override. ### Speed Limits Set upload and/or download limits. Each field supports these modes: | Mode | Value | Description | |------|-------|-------------| | No change | - | Don't modify this field | | Unlimited | 0 | Remove speed limit (qBittorrent treats 0 as unlimited) | | Custom | >0 | Specific limit in KiB/s or MiB/s | Applied in batches for efficiency. ### Share Limits Set ratio limit and/or seeding time limit. Each field supports these modes: | Mode | Value | Description | |------|-------|-------------| | No change | - | Don't modify this field | | Use global | -2 | Follow qBittorrent's global share settings | | Unlimited | -1 | No limit for this field | | Custom | >=0 | Specific value (ratio as decimal, time in minutes) | Torrents stop seeding when any enabled limit is reached. ### Pause Pause matching torrents. Only pauses if not already stopped. ### Delete Remove torrents from qBittorrent. **Must be standalone** - cannot combine with other actions. | Mode | Description | |------|-------------| | `delete` | Remove from client, keep files | | `deleteWithFiles` | Remove with files | | `deleteWithFilesPreserveCrossSeeds` | Remove files but preserve if cross-seeds detected | | `deleteWithFilesIncludeCrossSeeds` | Remove files and also delete all cross-seeded torrents sharing the same files | **Include cross-seeds mode behavior:** When a torrent matches the rule, the system finds other torrents that point to the same downloaded files (cross-seeds/duplicates) and deletes them together. This is useful when you want to fully remove content and all its cross-seeded copies at once. - **Safe expansion**: If qui can't safely confirm another torrent uses the same files, it won't be included in the deletion. - **Safety-first**: If verification can't complete for any reason, the entire group is skipped rather than risking broken torrents. - **Preview**: The delete preview shows all torrents that would be deleted, with cross-seeds marked. **Include hardlinked copies:** When "Include hardlinked copies" is enabled (only available with `deleteWithFilesIncludeCrossSeeds` mode), the system also deletes torrents that share the same underlying physical files via hardlinks, even if they have different Content Paths. - **Requires**: Local Filesystem Access must be enabled on the instance. - **Safe scope**: Only includes hardlinks that are fully contained within qBittorrent's torrent set. Never follows hardlinks to files outside qBittorrent (e.g., your media library). - **Preview**: Hardlink-expanded torrents are marked as "Cross-seed (hardlinked)" in the preview. - **Free Space projection**: When combined with Free Space conditions, hardlink groups are correctly deduplicated in the space projection - torrents sharing the same physical files are only counted once. This is useful when you have hardlinked copies of content across different locations in qBittorrent and want to clean up all copies together. ### Tag Add or remove tags from torrents. | Mode | Description | |------|-------------| | `full` | Add to matches, remove from non-matches (smart toggle) | | `add` | Only add to matches | | `remove` | Only remove from non-matches | :::note Mode does not change the way torrents are flagged, meaning, even with `mode: remove`, tags will be removed if the torrent does **NOT** match the conditions. `mode: remove` simply means that tags will not be added to torrents that do match. ::: Options: - **Use Tracker as Tag** - Derive tag from tracker domain - **Use Display Name** - Use tracker customization display name instead of raw domain ### Category Move torrents to a different category. Options: - **Include Cross-Seeds** - Also move cross-seeds (matching ContentPath AND SavePath) - **Block If Cross-Seed In Categories** - Prevent move if another cross-seed is in protected categories ### Move Move torrents to a different path on disk. This is needed to move the contents if AutoTMM is not enabled. Options: - **Skip if cross-seeds don't match the rule's conditions** - Skip the move if the torrent has cross-seeds that don't match the rule's conditions ## Cross-Seed Awareness Automations detect cross-seeded torrents (same content/files) and can handle them specially: - **Detection** - Matches via ContentPath (and SavePath for category moves) - **Delete Rules**: - Use `deleteWithFilesPreserveCrossSeeds` to keep files if cross-seeds exist - Use `deleteWithFilesIncludeCrossSeeds` to delete matching torrents and all their cross-seeds together - **Category Rules** - Enable "Include Cross-Seeds" to move related torrents together - **Blocking** - Prevent category moves if cross-seeds are in protected categories ### Hardlink Detection The `HARDLINK_SCOPE` field lets automations distinguish between torrents whose files are hardlinked into an external library (Sonarr, Radarr, etc.) and torrents that exist only within qBittorrent. This is the foundation for safe "Remove Upgraded Torrents" automations. #### How scope is determined When an automation references `HARDLINK_SCOPE`, qui builds a hardlink index by calling `Lstat()` on every file of every torrent in qBittorrent. For each file it extracts: - The **inode** and **device ID** — uniquely identifying the file on disk. - The **nlink count** — the total number of hardlinks to that inode, as reported by the filesystem. It then counts how many unique file paths across the entire qBittorrent torrent set point to each inode. The scope for each torrent is determined by comparing these two numbers: | Scope | Condition | Meaning | |---|---|---| | `none` | No file has `nlink > 1` | No hardlinks detected. | | `torrents_only` | At least one file has `nlink > 1`, and no file has `nlink > uniquePathCount` | Hardlinks exist, but only between torrents in qBittorrent. No external library links. | | `outside_qbittorrent` | Any file has `nlink > uniquePathCount` | Something outside qBittorrent has hardlinked the file — typically a Sonarr/Radarr library import. | :::note `HARDLINK_SCOPE` only reflects hardlink metadata. Cross-seeds are detected separately (ContentPath matching), so a torrent can have `HARDLINK_SCOPE = none` and still be cross-seeded. ::: #### Unknown scope and safety behavior If qui cannot `Lstat()` **any** file in a torrent — due to wrong paths, missing permissions, or inaccessible storage — that torrent receives no scope entry. All `HARDLINK_SCOPE` conditions evaluate to `false` for that torrent, regardless of the operator or value. This is a safety measure to prevent unintended deletions of torrents qui cannot fully inspect. To diagnose this, enable debug logging and look for the "hardlink index built" log message, which reports an `inaccessible` count. #### Docker volume requirements For hardlink scope detection to work in Docker: 1. **Paths must match exactly.** qui must be able to read files at the same paths qBittorrent reports. If qBittorrent says a torrent's save path is `/data/torrents/radarr/`, qui must be able to access `/data/torrents/radarr/` inside its container. 2. **Same underlying storage.** Both containers must share the same host mount so that inode numbers are consistent. If qui and qBittorrent access the same files through different host mounts or different bind-mount configurations, inode numbers may not match. 3. **Single mount, not subdivided.** Mount the common parent directory rather than mounting subdirectories separately. For example, if your data lives under `/mnt/media/data` on the host: ```yaml services: qui: volumes: - /home/user/docker/qui:/config - /mnt/media/data:/data # single mount covering both torrents and library ``` Avoid mounting both `/mnt/media/data/torrents:/data/torrents` **and** `/mnt/media/data:/data` — the overlapping mounts can cause inconsistent inode visibility. Use a single mount at the common parent. #### Filesystem limitations Hardlink scope detection depends on the kernel reporting accurate `nlink` values in stat results. Some filesystems do not do this: - **FUSE-based filesystems** (sshfs, mergerfs, rclone mount) may report `nlink = 1` for all files regardless of actual hardlink count. - **Some NAS appliance filesystems** and **overlay filesystems** (overlayfs) may behave similarly. - **Network filesystems** (NFS, CIFS/SMB) generally report accurate nlink values but behavior varies by server implementation. On affected filesystems, every torrent appears to have scope `none` because nlink is always 1. There is no workaround within qui — this is a kernel/filesystem limitation. If you suspect this issue, run `stat` on a file you know is hardlinked and check the "Links" count. Hardlinks also cannot span across different filesystems. If your torrent data and media library are on separate filesystems (or separate Docker volumes backed by different host paths), Sonarr/Radarr will copy instead of hardlink, and scope detection has nothing to detect. #### Example: Remove Upgraded Torrents This automation deletes torrents that have been replaced by an upgrade in Sonarr/Radarr. It targets torrents where the library hardlink no longer exists (the arr removed or re-linked it during upgrade), the torrent has been seeding for at least 7 days, and the category matches your arr categories. :::tip Use `HARDLINK_SCOPE` with `NOT_EQUAL` to `outside_qbittorrent` rather than `EQUAL` to `none`. This way torrents with scope `torrents_only` (cross-seeded but not in a library) are also eligible for cleanup, while any torrent still linked into your media library is protected. ::: ```json { "name": "Remove Upgraded Torrents", "trackerPattern": "*", "trackerDomains": ["*"], "conditions": { "schemaVersion": "1", "delete": { "enabled": true, "mode": "deleteWithFilesPreserveCrossSeeds", "condition": { "operator": "AND", "conditions": [ { "operator": "OR", "conditions": [ { "field": "CATEGORY", "operator": "EQUAL", "value": "radarr" }, { "field": "CATEGORY", "operator": "EQUAL", "value": "radarr.cross" }, { "field": "CATEGORY", "operator": "EQUAL", "value": "tv-sonarr" }, { "field": "CATEGORY", "operator": "EQUAL", "value": "tv-sonarr.cross" } ] }, { "field": "HARDLINK_SCOPE", "operator": "NOT_EQUAL", "value": "outside_qbittorrent" }, { "field": "SEEDING_TIME", "operator": "GREATER_THAN_OR_EQUAL", "value": "604800" } ] } } } } ``` This works because when Sonarr/Radarr upgrades a release, the old library hardlink is removed. The old torrent's files then have `nlink == 1` (scope `none`) or are only linked to other torrents (scope `torrents_only`). Either way, the scope is not `outside_qbittorrent`, so the automation matches and deletes the torrent after the seeding time requirement is met. If the automation is matching torrents you expect to be protected, verify: 1. qui can access all torrent files at the paths qBittorrent reports (check debug logs for inaccessible files). 2. Your filesystem reports accurate nlink values (`stat ` should show Links > 1 for hardlinked files). 3. Your Docker volume mounts do not overlap or subdivide the storage in a way that breaks inode consistency. ## Missing Files Detection The `Has Missing Files` field detects whether any files belonging to a completed torrent are missing from disk. - Only checks **completed torrents** - Returns `true` if **any** file is missing from its expected path :::note Requires "Local filesystem access" enabled on the instance. ::: ## Important Behavior ### Settings Only Set Values Automations apply settings but **do not revert** when disabled or deleted. If a rule sets upload limit to 1000 KiB/s, affected torrents keep that limit until manually changed or another rule applies a different value. ### Efficient Updates Only sends API calls when the torrent's current setting differs from the desired value. No-op updates are skipped. ### Processing Order - **First match wins** for exclusive actions (delete, category) - **Accumulative** for combinable actions (tags, speed limits) - Delete ends torrent processing (no further rules evaluated) ### Free Space Condition Behavior When using the **Free Space** condition in delete rules, the system uses intelligent cumulative tracking: 1. **Oldest-first processing** - Torrents are sorted by age (oldest first) for deterministic, predictable cleanup 2. **Cumulative space tracking** - As each torrent is marked for deletion, its size is added to the projected free space (only when the delete mode actually frees disk bytes) 3. **Stop when satisfied** - Once `Free Space + Space To Be Cleared` exceeds your threshold, remaining torrents no longer match 4. **Cross-seed aware** - Cross-seeded torrents sharing the same files are only counted once to avoid overestimating freed space **Preview Views for Free Space Rules** When previewing a delete rule with a Free Space condition, a toggle allows switching between two views: | View | Description | |------|-------------| | **Needed to reach target** | Shows only the torrents that would be removed right now to reach your free-space target. This is the default view and reflects actual delete behavior. | | **All eligible** | Shows all torrents this rule could remove while free space is low. Useful for understanding the full scope of what the rule could potentially delete (may include cross-seeds that don't directly match filters). | The toggle only appears for delete rules that use the Free Space condition. **Preview features:** - **Path column** - Shows the content path for each torrent with copy-to-clipboard support - **Export CSV** - Download the full preview list (all pages) as a CSV file for external analysis **Cross-seed expansion in previews:** Cross-seeds are only expanded and displayed in the preview when using `Remove with files (include cross-seeds)` mode. In this mode, the preview shows all torrents that would be deleted together, with cross-seeds clearly marked. Other delete modes don't expand cross-seeds in the preview since they either preserve cross-seeds or don't consider them specially. **Delete mode affects space projection:** | Delete Mode | Space Added to Projection | |-------------|---------------------------| | Remove with files | Full torrent size | | Preserve cross-seeds (no cross-seeds) | Full torrent size | | Preserve cross-seeds (has cross-seeds) | 0 (files kept) | **How preserve cross-seeds works:** - Cross-seed detection checks if any other torrent shares the same Content Path at evaluation time (before any removals). - If multiple torrents share the same files, removing them all in one rule run will still keep the files on disk. No disk space is freed from that group because each torrent sees the others as cross-seeds. - Only non-cross-seeded torrents contribute to the free-space projection when using preserve mode. **Example:** With 400GB free and a rule "Delete if Free Space < 500GB" using `Remove with files`, the system deletes oldest torrents until the cumulative freed space reaches 100GB, then stops. A 50GB torrent and its cross-seed (same files) only count as 50GB freed, not 100GB. :::note The UI and API prevent combining `Remove (keep files)` mode with Free Space conditions. Since keep-files doesn't free disk space, such a rule could never satisfy the free space target and would match indefinitely. ::: :::note After removing files, qui waits ~5 minutes before running Free Space deletes again to allow qBittorrent to refresh its disk free space reading. The UI prevents selecting 1 minute intervals for Free Space delete rules. ::: #### Free Space Source By default, Free Space uses qBittorrent's reported free space (based on its default download location). If you have multiple disks or want to manage a specific mount point, select "Path on server" and enter the path to that disk. | Source | Description | |--------|-------------| | Default (qBittorrent) | Uses qBittorrent's reported free space | | Path on server | Reads free space from a specific filesystem path | :::note Path on server requires "Local Filesystem Access" to be enabled on the instance. ::: If you want to manage multiple disks, create one workflow per disk and set a different Path on server for each workflow. :::note On Windows, Path on server is not supported and Free Space always uses qBittorrent's reported free space. The UI disables the option and switches legacy workflows back to the default when opened. ::: ### Batching Torrents are grouped by action value and sent to qBittorrent in batches of up to 50 hashes per API call. ## Activity Log All automation actions are logged with: - Torrent name and hash - Rule name and action type - Outcome (success/failed) with reasons - Action-specific details Activity is retained for 7 days by default. View the log in the Automations section for each instance. ## Example Rules ### Delete Old Completed Torrents Remove torrents completed over 30 days ago when disk space is low: - Condition: `Completion On Age > 30 days` AND `State is completed` AND `Free Space < 500GB` - Action: Remove with files Deletes oldest matching torrents first, stopping once enough space would be freed to exceed 500GB. ### Speed Limit Private Trackers Limit upload on private trackers: - Tracker: `*` - Condition: `Private is true` - Action: Upload limit 10000 KiB/s ### Tag Stalled Torrents Auto-tag torrents with no activity: - Tracker: `*` - Condition: `Last Activity Age > 7 days` - Action: Tag "stalled" (mode: add) ### Clean Unregistered Torrents Remove torrents the tracker no longer recognizes: - Tracker: `*` - Condition: `Is Unregistered is true` - Action: Delete (keep files) ### Maintain Minimum Free Space Keep at least 200GB free by removing oldest completed torrents: - Tracker: `*` - Condition: `Free Space < 200GB` AND `State is completed` - Action: Remove with files (preserve cross-seeds) Removes torrents from the client, oldest first, until enough space is projected to be freed. Cross-seeded torrents keep their files on disk and don't contribute to the projection. If only cross-seeded torrents match, this may remove many torrents without freeing any disk space. ### Clean Up Old Content with Cross-Seeds Remove completed torrents and all their cross-seeded copies when they're old enough: - Tracker: `*` - Condition: `Completion On Age > 30 days` AND `State is completed` - Action: Remove with files (include cross-seeds) When a torrent matches, any other torrents pointing to the same downloaded files are deleted together. Useful for complete cleanup when you no longer need any copy of the content. ### Organize by Tracker Move torrents to tracker-named categories: - Tracker: `tracker.example.com` - Action: Category "example" with "Include Cross-Seeds" enabled --- ## Backups # Backups & Restore qui can take scheduled or ad-hoc snapshots of a qBittorrent instance. Each snapshot includes the torrent archive, tags, categories (with save paths), and cached `.torrent` blobs so that you can recreate the original state later. ## Restore Modes Once backups are enabled for an instance the backlog UI exposes a **Restore** action for each run. Restores support three distinct modes: ### Incremental Safest option. Creates any categories, tags, or torrents that are missing from the live instance but never modifies or removes existing data. Use this when you just want to seed new items into an active qBittorrent without touching what is already there. ### Overwrite Performs the incremental work **and** updates existing resources to match the snapshot (e.g. adjusts category save paths or rewrites per-torrent categories/tags). It still refuses to delete anything. This works well when your live instance has drifted but you do not want to prune it. ### Complete Full reconciliation. Runs the overwrite steps and then deletes categories, tags, and torrents that are not present in the snapshot. This is ideal when you need to roll an instance back to an earlier point in time, but it should only be used when you are certain the snapshot is authoritative. ## Preview Before Restore Every restore begins with a dry-run preview so you can inspect planned changes. Unsupported differences (such as mismatched infohashes or file sizes) are surfaced as warnings; they require manual follow-up regardless of mode. ## Importing Backups Downloaded backups can be imported into any qui instance. Useful for migrating to a new server or recovering after data loss. Click **Import** on the Backups page and select the backup file. All export formats are supported. --- ## autobrr Integration qui integrates with autobrr through webhook endpoints, enabling real-time cross-seed detection when autobrr announces new releases. ## How It Works 1. autobrr sees a new release from a tracker 2. autobrr sends the torrent name to qui's `/api/cross-seed/webhook/check` endpoint 3. qui searches your qBittorrent instances for matching content 4. qui responds with: - `200 OK` – matching torrent is complete and ready to cross-seed - `202 Accepted` – matching torrent exists but still downloading; retry later - `404 Not Found` – no matching torrent exists 5. On `200 OK`, autobrr sends the torrent file to `/api/cross-seed/apply` ## Setup ### 1. Create an API Key in qui - Go to **Settings → API Keys** - Click **Create API Key** - Name it (e.g., "autobrr webhook") - Copy the generated key ### 2. Configure autobrr External Filter :::important Create a **new autobrr filter dedicated to qui**. ::: :::note The **External** webhook (`/api/cross-seed/webhook/check`) only answers: "is this ready to cross-seed?" It does **not** add a torrent to qBittorrent. You must also set up the **Action** in [Apply Endpoint](#apply-endpoint). ::: :::tip **Docker Compose:** if autobrr and qui are both containers, `localhost` inside autobrr is the autobrr container, not qui. Use your qui container hostname instead (often the Compose service name), for example: `http://qui:7476/api/cross-seed/webhook/check`. ::: In your new autobrr filter, go to **External** tab → **Add new**: | Field | Value | | ------------------------- | ---------------------------------------------------- | | Type | `Webhook` | | Name | `qui` | | On Error | `Reject` | | Endpoint | `http://localhost:7476/api/cross-seed/webhook/check` | | HTTP Method | `POST` | | HTTP Request Headers | `X-API-Key=YOUR_QUI_API_KEY` | | Expected HTTP Status Code | `200` | **Data (JSON):** ```json { "torrentName": {{ toRawJson .TorrentName }}, "instanceIds": [1] } ``` To search all instances, omit `instanceIds`: ```json { "torrentName": {{ toRawJson .TorrentName }} } ``` **Field descriptions:** - `torrentName` (required): The release name as announced - `instanceIds` (optional): qBittorrent instance IDs to scan. Omit to search all instances. - `findIndividualEpisodes` (optional): Override the global episode matching setting ### 3. Configure Retry Handling Use autobrr's **Retry** block to handle `202 Accepted` responses: - **Retry HTTP status code(s):** `202` - **Maximum retry attempts:** `10` - **Retry delay in seconds:** `4` ## Apply Endpoint When `/check` returns `200 OK`, send the torrent to `/api/cross-seed/apply`: **Action setup in autobrr:** | Field | Value | | ----------- | -------------------------------------------------------------------- | | Action Type | `Webhook` | | Name | `qui cross-seed` | | Endpoint | `http://localhost:7476/api/cross-seed/apply?apikey=YOUR_QUI_API_KEY` | **Payload (JSON):** ```json { "torrentData": "{{ .TorrentDataRawBytes | toString | b64enc }}", "instanceIds": [1], "indexerName": {{ toRawJson .IndexerName }} } ``` **Field descriptions:** - `torrentData` (required) - Base64-encoded torrent file bytes - `instanceIds` (optional) - Target instances (omit to apply to any matching instance) - `indexerName` (optional) - Indexer display name (e.g., "TorrentDB"). Only used when "Use indexer name as category" mode is enabled; ignored otherwise - `tags` (optional) - Override webhook tags from settings - `category` (optional) - Override category. Takes precedence over `indexerName` - `startPaused` (optional) - Override whether torrents are added paused - `skipIfExists` (optional) - Skip adding if the torrent already exists - `findIndividualEpisodes` (optional) - Override the global episode matching setting Cross-seeded torrents are added paused with `skip_checking=true`. qui polls the torrent state and auto-resumes if progress meets the size tolerance threshold. If progress is too low, it remains paused for manual review. ### Troubleshooting: autobrr matches, but nothing gets added to qBittorrent Use this when autobrr shows the filter accepted the release (or your Discord notification fires), but you never see a new torrent in qBittorrent. 1. **Confirm you added the `/apply` Action** - The External webhook (`/check`) does not add torrents. - You need an autobrr **Action** (Webhook) that calls `/api/cross-seed/apply` (above). 2. **Fix Docker networking if you're using containers** - `http://localhost:7476/...` only works if autobrr can reach qui on its own `localhost`. - In Docker Compose, use the qui service hostname (example): `http://qui:7476/api/cross-seed/apply?apikey=...`. 3. **Double-check auth** - `/check`: header `X-API-Key=...` - `/apply`: query string `?apikey=...` (as shown in this guide) 4. **Verify qui can talk to qBittorrent** - qui UI: **Settings → Instances → Test Connection** 5. **Check paused torrents** - Cross-seeds are often added **paused**. Look in qBittorrent's paused list (and any cross-seed tag/category you configured). If you still can't see why, jump to [Cross-Seed Troubleshooting](troubleshooting). ## Webhook Source Filters By default, the webhook endpoint scans **all** torrents on your instances when looking for matches. You can configure filters to exclude certain categories or tags from being matched: - **Exclude Categories:** Skip torrents in specific categories (e.g., `cross-seed-link`) - **Exclude Tags:** Skip torrents with specific tags (e.g., `no-cross-seed`) - **Include Categories:** Only match against torrents in these categories (leave empty for all) - **Include Tags:** Only match against torrents with these tags (leave empty for all) This is useful when: - You have a legacy cross-seed category that shouldn't be re-matched - Certain content types should never be considered for cross-seeding - You want to exclude torrents with specific metadata tags :::note Exclude filters take precedence over include filters. Tag matching is case-sensitive. When both category and tag include filters are configured, a torrent must pass both filter checks (matching at least one allowed category AND at least one allowed tag). ::: Configure in qui UI: **Cross-Seed → Auto → Webhook / autobrr** --- ## Directory Scanner Directory Scanner (Dir Scan) scans local folders to find cross-seed opportunities for content already on disk. Unlike Library Scan (which queries qBittorrent's torrent list), Dir Scan works directly with files on the filesystem. Configure it in **Cross-Seed > Dir Scan**. ## Requirements - At least one qBittorrent instance must have **Local filesystem access** enabled in Instance Settings. - qui must be able to read the files directly (same host or shared mounts as the target qBittorrent instance). - Prowlarr or Jackett must be configured with at least one enabled indexer. - Optional: Sonarr/Radarr configured in **Settings > Integrations** for external ID lookups (IMDb/TMDb/TVDb). ## How to Choose Your Scan Path Dir Scan treats each **immediate child** of your configured path as one "searchee." It does not treat the path itself as a single searchee, and it does not recurse into subfolders to create additional searchees. **Example:** If you configure `/data/media/movies`: ```plaintext /data/media/movies/ ├── Movie.2024.1080p.BluRay/ <- searchee 1 │ ├── movie.mkv │ └── movie.nfo ├── Another.Movie.2023.2160p/ <- searchee 2 │ └── movie.mkv └── standalone.mkv <- searchee 3 ``` Each immediate child (folder or file) becomes one searchee. Files within `Movie.2024.1080p.BluRay/` are grouped together as part of that searchee. ### Correct path choices | Content type | Recommended path | Why | |-------------|------------------|-----| | Movies | `/data/media/movies` | Each movie folder is one searchee | | TV Shows | `/data/media/tv` | Each show folder is one searchee | | Music | `/data/media/music` | Each album folder is one searchee | ### Incorrect path choices | Path | Problem | |------|---------| | `/data/media` containing `movies/` + `tv/` + `music/` | Only 3 searchees total (the category folders themselves) | | `/data/media/movies/Movie.2024.1080p.BluRay` | Only 1 searchee; scans that specific movie only | :::tip Create one Dir Scan entry per category folder. Don't point at a parent folder containing multiple category subfolders. ::: ## Docker and Path Mapping When qui and qBittorrent run in separate containers or see different mount points, you need path mapping. ### "Local filesystem access" explained Enabling **Local filesystem access** on a qBittorrent instance tells qui: 1. qui can read files directly from the filesystem (same paths or mapped paths). 2. qui should use file-based matching (inode checks, size verification) rather than relying solely on qBittorrent's API. This requires qui to have read access to the actual files, either on the same host or via shared network/volume mounts. ### Recommended: Use the same volume paths The simplest setup is to mount volumes at the same path in both containers: ```yaml title="docker-compose.yml" services: qui: volumes: - /mnt/storage:/mnt/storage qbittorrent: volumes: - /mnt/storage:/mnt/storage ``` When both containers see `/data/media/movies`, no path mapping is needed. Leave **qBittorrent Path Prefix** empty. ### Path mapping example (different mount points) Your setup: - qui container mounts: `-v /mnt/storage:/data` - qBittorrent container mounts: `-v /mnt/storage:/downloads` qui sees files at `/data/media/movies/Movie.2024/movie.mkv` qBittorrent sees the same file at `/downloads/media/movies/Movie.2024/movie.mkv` Configure Dir Scan: - **Directory Path**: `/data/media/movies` - **qBittorrent Path Prefix**: `/downloads/media/movies` When qui finds a match, it tells qBittorrent to add the torrent pointing at `/downloads/media/movies/Movie.2024/` instead of `/data/media/movies/Movie.2024/`. ## How It Works For each configured scan directory, qui: 1. Enumerates immediate children of the directory path. 2. For each child (folder or file), recursively collects all files within. 3. Groups files into a "searchee" with parsed release info. 4. Uses configured *arr instances to resolve external IDs when possible. 5. Searches enabled indexers via Torznab. 6. Downloads torrent files and matches their file lists against what's on disk. 7. If a match is found, adds the torrent to the target qBittorrent instance. :::info Torznab searches run through the shared scheduler at background priority, so they queue behind interactive, RSS, and completion cross-seed work. If the global scan concurrency limit is reached, new scans show as `queued` until a scan slot is available. Dir Scan may also pause between downloading candidate torrent files from an indexer. This is intentional and helps avoid hammering Prowlarr/indexers (especially for private trackers), but it can make scans take longer when many candidates need checking. ::: ### Already-seeding detection Dir Scan maintains a FileID index (inode + device on Unix) to track files already present in qBittorrent. It skips: - Files that are already part of a seeding torrent - Torrents whose infohash already exists in qBittorrent This avoids redundant searches and duplicate additions. ### Recheck Behavior - **Full matches**: Torrent is added with "skip hash check" enabled. Seeding starts immediately. - **Partial matches** (when enabled): Torrent is added without skipping hash check. qBittorrent verifies existing data and downloads missing files. ## What Gets Scanned ### Included file types **Video:** `.mkv`, `.mp4`, `.avi`, `.m4v`, `.wmv`, `.mov`, `.ts`, `.m2ts`, `.vob`, `.mpg`, `.mpeg`, `.webm`, `.flv` **Audio:** `.flac`, `.mp3`, `.wav`, `.aac`, `.ogg`, `.m4a`, `.wma`, `.ape`, `.alac`, `.dsd`, `.dsf`, `.dff` **Extras:** `.nfo`, `.sfv`, `.srt`, `.sub`, `.idx`, `.ass`, `.ssa` Extras are included in releases and can affect partial-match behavior (a torrent with an `.nfo` you don't have may trigger a partial match instead of full). ### Disc layouts Folders containing `BDMV/`, `VIDEO_TS/`, or `AUDIO_TS/` structures are treated as disc-based media. All files within these structures are included regardless of extension. ### Skipped items - **Hidden files and folders** (names starting with `.`) - **Symlinks** (explicitly skipped to avoid loops and permission issues) - **Files with permission errors** (scan continues, file is skipped) - **Non-media files** outside disc layouts ## Settings (Global) Open **Dir Scan > Settings**: | Setting | Description | |---------|-------------| | Match Mode | `Strict` matches by filename + size. `Flexible` matches by size only. | | Size Tolerance (%) | Allows small size differences when matching. | | Minimum Piece Ratio (%) | For partial matches, minimum percent of torrent data that must exist on disk. | | Max searchees per run | Limits how many eligible searchees are processed per run. `0` = unlimited. Useful for making progress across restarts. | | Allow partial matches | Add torrents even if they have extra/missing files compared to disk. | | Skip piece boundary safety check | Allow partial matches where downloading missing files could modify pieces containing existing content. | | Start torrents paused | Add injected torrents in paused state. | | Default Category / Tags | Applied to all injected torrents. Directory-level settings add to these. | ### "Max searchees per run" explained This setting limits how many **top-level folders/files** Dir Scan will process in a single run. - If your directory is a TV root like `/mnt/storage/media/tv`, then each **show folder** is one searchee (for example `Show.Name/`, `Another.Show/`). - If your directory is a movies root like `/mnt/storage/media/movies`, then each **movie folder** is one searchee (for example `Movie.Title (2024)/`, `Another.Movie (2023)/`). So if **Max searchees per run = 5**, Dir Scan will process up to **5 show folders** (TV) or **5 movie folders** (movies) per run, then stop and persist per-file progress for the next run (so already-final files won't be reprocessed). See [Incremental progress and resets](#incremental-progress-and-resets). This is **not** a cap on the total number of indexer searches. TV folders can trigger multiple searches (season-level + per-episode heuristics), even though they still count as a single top-level searchee. ## Directories Each scan directory has its own configuration: | Setting | Description | |---------|-------------| | Directory Path | The path qui scans (immediate children become searchees). | | qBittorrent Path Prefix | Path mapping for container setups. See [Docker and Path Mapping](#docker-and-path-mapping). | | Target qBittorrent Instance | Where matched torrents are added. Must have Local filesystem access enabled. | | Category override | Overrides the global Default Category for this directory. | | Additional tags | Added on top of the global Dir Scan tags. | | Scan Interval (minutes) | How often to rescan (minimum 60 minutes, default 1440 = 24 hours). | | Enabled | Enable/disable without deleting the configuration. | ## Operational Behavior ### Concurrent scans Only one scan runs per directory at a time. If a scheduled scan triggers while another scan is running, it will not start a second run for that directory. ### Incremental progress and resets Dir Scan persists per-file progress and skips unchanged searchees whose files are already in a final state (matched/no match/already seeding/in qBittorrent). This makes scans resumable across restarts. If you want to force a directory to be re-processed from scratch, use **Reset Scan Progress** for that directory in the UI. This clears the tracked file state for that directory. ### Scheduled vs manual scans - **Scheduled scans** run based on the configured interval (minimum 60 minutes). - **Manual scans** can be triggered from the UI at any time via the "Scan Now" button. Both types can be canceled from the UI while running. ### Scan phases Each scan progresses through phases: 1. **Scanning** - Reading directory contents and building searchee list 2. **Searching** - Querying indexers for each searchee 3. **Injecting** - Adding matched torrents to qBittorrent 4. **Final state** - Success, Failed, or Canceled The UI shows current phase and progress during active scans. ## Hardlink/Reflink Modes If the target qBittorrent instance has hardlink or reflink mode enabled, Dir Scan uses the same behavior as other cross-seed methods: - Builds a link tree matching the incoming torrent's layout. - Adds the torrent pointing at that tree (`contentLayout=Original`). Full matches use `skip_checking=true`; partial matches allow qBittorrent to verify existing data and download missing files safely into the link tree. See: - [Hardlink Mode](hardlink-mode) - [Link Directories](link-directories) ### Fallback to regular mode When link-tree creation fails (hardlinking across filesystems, permission issues), Dir Scan falls back to regular add behavior **if** the instance has **Fallback to regular mode** enabled. Otherwise, the candidate fails. ## Scanning Your *arr Library Dir Scan can scan Sonarr/Radarr library folders, but be careful with partial matches: :::warning With **Allow partial matches** enabled, qBittorrent may download missing files (extras like `.nfo`, subtitles) directly into your *arr-managed library folder. This can create unexpected files alongside your media. ::: For a "read-only" scan of your library: 1. Disable **Allow partial matches** (full matches only). 2. Disable **Fallback to regular mode** on the target instance so hardlink failures don't add torrents directly against your library path. The safer setup is usually: - Scan your completed downloads/staging folder instead of the final library, and/or - Use hardlink/reflink mode so cross-seeds live under your configured link-tree base directory. ## Troubleshooting ### Recent Scan Runs The **Recent Scan Runs** panel on the Dir Scan page shows: - Added count (successful injections) - Failed count (matches that couldn't be added) - Timestamps and duration Click a run to see details including failure reasons for individual items. ### Common issues **No results found:** - Verify at least one indexer is enabled and not rate-limited. - Check that the scan path contains valid media files. - Ensure the target instance has Local filesystem access enabled. **Permissions errors:** - qui must have read access to the scan path. - Check container volume mounts if running in Docker. **Wrong path mapping:** - Verify qBittorrent Path Prefix matches how qBittorrent sees the same files. - Test by checking a torrent's save path in qBittorrent's UI. **Rate limiting:** - Indexers may throttle requests. Check **Scheduler Activity** on the Indexers page. - Consider reducing scan frequency or limiting to fewer indexers. For cross-seed-wide issues (matching behavior, hardlink failures, recheck problems), see [Troubleshooting](troubleshooting). --- ## Hardlink Mode Hardlink mode is an opt-in cross-seeding strategy that creates a hardlinked copy of the matched files laid out exactly as the incoming torrent expects, then adds the torrent pointing at that hardlink tree. This can make cross-seed alignment simpler and faster, because qBittorrent can start seeding immediately without file rename alignment. ## When to Use - You want cross-seeds to have their own on-disk directory structure (per tracker / per instance / flat), while still sharing data blocks with the original download. - You want to avoid qBittorrent rename-alignment and hash rechecks for layout differences. ## Requirements - Requires **Local filesystem access** on the target qBittorrent instance. - Hardlink base directory must be on the **same filesystem/volume** as the instance's download paths (hardlinks can't cross filesystems). - qui must be able to read the instance's content paths and write to the hardlink base directory. :::warning[Docker: Local Filesystem Access] Several qui features require access to the same filesystem paths that qBittorrent uses (orphan scan, hardlinks, reflinks, automations). Mount the same paths qBittorrent uses - paths must match exactly: ```yaml volumes: - /config:/config - /data/torrents:/data/torrents # Must match qBittorrent's path ``` After mounting, enable **Local Filesystem Access** on each instance in qui's Instance Settings. ::: ## Behavior - Hardlink mode is a **per-instance setting** (not per request). Each qBittorrent instance can have its own hardlink configuration. - By default, if a hardlink cannot be created (no local access, filesystem mismatch, invalid base dir, etc.), the cross-seed **fails**. - Enable **"Fallback to regular mode"** to allow failed hardlink operations to fall back to regular cross-seed mode instead of failing. This is useful when files may occasionally be on different filesystems. - Hardlinked torrents are still categorized using your existing cross-seed category rules (category affix, indexer name, or custom category); the hardlink preset only affects on-disk folder layout. ## Directory Layout Configure in Cross-Seed → Hardlink Mode → (select instance): - **Hardlink base directory**: path on the qui host where hardlink trees are created. - **Directory preset**: - `flat`: `base/TorrentName--shortHash/...` - `by-tracker`: `base//TorrentName--shortHash/...` - `by-instance`: `base//TorrentName--shortHash/...` ### Isolation Folders For `by-tracker` and `by-instance` presets, qui determines whether an isolation folder is needed based on the torrent's file structure: - **Torrents with a root folder** (e.g., `Movie/video.mkv`, `Movie/subs.srt`) → files already have a common top-level directory, no isolation folder needed - **Rootless torrents** (e.g., `video.mkv`, `subs.srt` at top level) → isolation folder added to prevent file conflicts When an isolation folder is needed, it uses a human-readable format: `` (e.g., `My.Movie.2024.1080p.BluRay--abcdef12`). For the `flat` preset, an isolation folder is always used to keep each torrent's files separated. ## How to Enable 1. Enable "Local filesystem access" on the qBittorrent instance in Instance Settings. 2. In Cross-Seed → Hardlink Mode, expand the instance you want to configure. 3. Enable "Hardlink mode" for that instance. 4. Set "Hardlink base directory" to a path on the same filesystem as your downloads. 5. Choose a directory preset (`flat`, `by-tracker`, `by-instance`). 6. Optionally enable "Fallback to regular mode" if you want failed hardlinks to use regular cross-seed mode instead of failing. ## Pause Behavior By default, hardlink-added torrents start seeding immediately (since `skip_checking=true` means they're at 100% instantly). If you want hardlink-added torrents to remain paused, enable the "Skip auto-resume" option for your cross-seed source (Completion, RSS, Webhook, etc.). ## Notes - Hardlinks share disk blocks with the original file but increase the link count. Deleting one link does not necessarily free space until all links are removed. - Windows support: folder names are sanitized to remove characters Windows forbids. Torrent file paths themselves still need to be valid for your qBittorrent setup. - Hardlink mode supports extra files when piece-boundary safe. If the incoming torrent contains extra files not present in the matched torrent (e.g., `.nfo`/`.srt` sidecars), hardlink mode will link the content files and trigger a recheck so qBittorrent downloads the extras. If extras share pieces with content (unsafe), the cross-seed is skipped. ## Reflink Mode (Alternative) Reflink mode creates copy-on-write clones of the matched files. Unlike hardlinks, reflinks allow qBittorrent to safely modify the cloned files (download missing pieces, repair corrupted data) without affecting the original seeded files. **Key advantage:** Reflink mode **bypasses piece-boundary safety checks**. This means you can cross-seed torrents with extra/missing files even when those files share pieces with existing content—the clones can be safely modified. ### When to Use Reflink Mode - You want to cross-seed torrents that hardlink mode would skip due to "extra files share pieces with content" - Your filesystem supports copy-on-write clones (BTRFS, XFS on Linux; APFS on macOS) - You prefer the safety of copy-on-write over hardlinks ### Reflink Requirements - **Local filesystem access** must be enabled on the target qBittorrent instance. - The base directory must be on the **same filesystem/volume** as the instance's download paths. - The base directory must be a **real filesystem mount**, not a pooled/virtual mount (common examples: `mergerfs`, other FUSE mounts, `overlayfs`). - The filesystem must support reflinks: - **Linux**: BTRFS, XFS (with reflink=1), and similar CoW filesystems - **macOS**: APFS - **Windows/FreeBSD**: Not currently supported :::tip On Linux, check the filesystem type with `df -T /path` (you want `xfs`/`btrfs`, not `fuseblk`/`fuse.mergerfs`/`overlayfs`). ::: ### Behavior Differences | Aspect | Hardlink Mode | Reflink Mode | |--------|--------------|--------------| | Piece-boundary check | Skips if unsafe | Never skips (safe to modify clones) | | Recheck | Only when extras exist | Only when extras exist | | Disk usage | Zero (shared blocks) | Starts near-zero; grows as modified | ### Disk Usage Implications Reflinks use copy-on-write semantics: - Initially, cloned files share disk blocks with originals (near-zero additional space) - When qBittorrent writes to a clone (downloads extras, repairs pieces), only modified blocks are copied - In worst case (entire file rewritten), disk usage approaches full file size ### How to Enable Reflink Mode 1. Enable "Local filesystem access" on the qBittorrent instance in Instance Settings. 2. In Cross-Seed > Hardlink / Reflink Mode, expand the instance you want to configure. 3. Enable "Reflink mode" for that instance. 4. Set "Base directory" to a path on the same filesystem as your downloads. 5. Choose a directory preset (`flat`, `by-tracker`, `by-instance`). 6. Optionally enable "Fallback to regular mode" if you want failed reflinks to use regular cross-seed mode instead of failing. :::note Hardlink and reflink modes are mutually exclusive—only one can be enabled per instance. ::: --- ## Link Directories When **Hardlink mode** or **Reflink mode** is enabled for a qBittorrent instance, qui creates a directory tree that matches the incoming torrent’s expected layout, then adds the torrent pointing at that tree. This applies to: - Cross-seed searches (RSS, completion, manual, scan) - Directory scan (dirscan) injections ## Settings Configured per qBittorrent instance in **Cross-Seed → Hardlink Mode**: - **Base directory** (`HardlinkBaseDir`): root path where link trees are created. - **Directory preset** (`HardlinkDirPreset`): controls how trees are grouped below the base directory. - **Fallback to regular mode** (`FallbackToRegularMode`): if link-tree creation fails, qui can fall back to “regular mode” instead of skipping/failing. ## Directory Presets qui supports three presets: - `flat`: one folder per torrent under the base directory - Example: `base/Torrent.Name--abcdef12/...` - `by-tracker`: groups by tracker display name, then optional isolation folder - Example: `base/TrackerName/Torrent.Name--abcdef12/...` - `by-instance`: groups by instance name, then optional isolation folder - Example: `base/MyInstance/Torrent.Name--abcdef12/...` ### Tracker Names (by-tracker) For `by-tracker`, qui resolves the folder name using the same fallback chain as cross-seed statistics: 1. **Tracker customization display name** (Settings → Tracker Customizations) 2. Indexer name (from Prowlarr/Jackett) 3. Raw announce domain Folder names are sanitized to be filesystem-safe. ### Isolation Folders For `by-tracker` and `by-instance`, qui adds an isolation folder only when needed: - Torrents with a common root folder don’t need isolation. - “Rootless” torrents (top-level files) use an isolation folder to avoid collisions. For `flat`, an isolation folder is always used. ## Fallback to Regular Mode If **Fallback to regular mode** is enabled, qui will fall back to adding the torrent with a normal `savepath` (pointing at the matched source files) when link-tree creation fails. This is particularly useful when hardlinking can intermittently fail due to filesystem/device boundaries (for example: pooled mounts where two paths look the same but resolve to different underlying devices). If fallback is disabled, qui skips/fails the candidate when link-tree creation fails. --- ## Cross-Seed # Cross-Seed Overview qui includes intelligent cross-seeding capabilities that help you automatically find and add matching torrents across different trackers. This allows you to seed the same content on multiple trackers. ## How It Works When you cross-seed a torrent, qui: 1. Finds a matching torrent in your library (same content, different tracker) 2. Adds the new torrent pointing to your existing files 3. Applies the correct category and save path automatically qui supports three modes for handling files: - **Default mode**: Reuses existing files directly. No new files or links are created. May require rename-alignment if the incoming torrent has a different folder/file layout. - **Hardlink mode** (optional): Creates a hardlinked copy of the matched files laid out exactly as the incoming torrent expects, then adds the torrent pointing at that tree. Avoids rename-alignment entirely. - **Reflink mode** (optional): Creates copy-on-write clones (reflinks) of the matched files. Allows safe cross-seeding of torrents with extra/missing files because qBittorrent can write/repair the clones without affecting originals. Disc-based media (Blu-ray/DVD) requires manual verification. See [troubleshooting](troubleshooting#blu-ray-or-dvd-cross-seed-left-paused). ## Prerequisites You need Prowlarr or Jackett to provide Torznab indexer feeds. Add your indexers in **Settings → Indexers** using the "1-click sync" feature to import from Prowlarr/Jackett automatically. **Optional but recommended:** Configure Sonarr/Radarr instances in **Settings → Integrations** to enable external ID lookups (IMDb, TMDb, TVDb, TVMaze). When configured, qui queries your *arr instances to resolve IDs for cross-seed searches, improving match accuracy on indexers that support ID-based queries. ## Discovery Methods qui offers several ways to find cross-seed opportunities: ### RSS Automation Scheduled polling of tracker RSS feeds. Configure in the **Auto** tab on the Cross-Seed page. - **Run interval** - How often to poll feeds (minimum 30 minutes) - **Target instances** - Which qBittorrent instances receive cross-seeds - **Target indexers** - Limit to specific indexers or use all enabled ones RSS automation processes the full feed from every enabled indexer on each run, matching against torrents across your target instances. ### Library Scan Deep scan of torrents you already seed to find cross-seed opportunities on other trackers. Configure in the **Scan** tab. - **Source instance** - The qBittorrent instance to scan - **Categories/Tags** - Filter which torrents to include - **Interval** - Delay between processing each torrent (minimum 60 seconds) - **Cooldown** - Skip torrents searched within this window (minimum 12 hours) :::warning Run sparingly. This deep scan touches every matching torrent and queries indexers for each one. Use RSS automation or autobrr for routine coverage; reserve library scan for occasional catch-up passes. ::: ### Auto-Search on Completion Triggers a cross-seed search when torrents finish downloading. Configure in the **Auto** tab under "Auto-search on completion". - **Categories/Tags** - Filter which completed torrents trigger searches - **Exclude categories/tags** - Skip torrents matching these filters ### Manual Search Right-click any torrent in the list to access cross-seed actions: - **Search Cross-Seeds** - Query indexers for matching torrents on other trackers - **Filter Cross-Seeds** - Show torrents in your library that share content with the selected torrent (useful for identifying existing cross-seeds) --- ## Rules # Cross-Seed Rules Configure matching behavior in the **Rules** tab on the Cross-Seed page. ## Matching - **Find individual episodes** - When enabled, season packs also match individual episodes. When disabled, season packs only match other season packs. Episodes are added with AutoTMM disabled to prevent save path conflicts. - **Size mismatch tolerance** - Maximum size difference percentage (default: 5%). Also determines auto-resume threshold after recheck. - **Skip recheck** - When enabled, skips any cross-seed that would require a recheck (alignment needed, extra files, or disc layouts like `BDMV`/`VIDEO_TS`). Applies to all modes including hardlink/reflink. - **Skip piece boundary safety check** - Enabled by default. When enabled, allows cross-seeds even if extra files share torrent pieces with content files. **Warning:** This may corrupt your existing seeded data if content differs. Uncheck this to enable the safety check, or use reflink mode which safely handles these cases. :::note Disc layouts (`BDMV`/`VIDEO_TS`) are treated more strictly: they only auto-resume after a full recheck reaches 100%. ::: ## Categories Choose one of three mutually exclusive category modes: ### Category Affix (default) Adds a configurable affix to the matched torrent's category. Prevents Sonarr/Radarr from importing cross-seeded files as duplicates. AutoTMM is inherited from the matched torrent. **Affix Mode:** - **Suffix** (default): Appends the affix to the category (e.g., `movies` → `movies.cross`) - **Prefix**: Prepends the affix to the category (e.g., `movies` → `cross/movies`) **Affix Value:** The text to add (default: `.cross`). Common examples: - `.cross` using suffix mode → `tv.cross`, `movies.cross` - `cross/` using prefix mode → `cross/tv`, `cross/movies` :::tip Prefix mode with a trailing `/` creates nested categories1 in qBittorrent, making it easy to group all cross-seeds under a parent category. Filtering by `cross` returns all cross-seeds (`cross/movies`, `cross/tv`, etc.). ::: :::warning Avoid using a leading `/` in suffix mode (e.g., `/cross-seed`). This creates the cross-seed as a **child** of the original category1, so setting your category to `movies` in Radarr would also return `movies/cross-seed` torrents, potentially causing conflicts. Use prefix mode instead if you want nested categories. ::: *1 Nested categories require subcategories to be enabled (Instance Preferences → Files → Enable Subcategories).* ### Use indexer name as category Sets category to the indexer name (e.g., `TorrentDB`). AutoTMM is always disabled; uses explicit save paths. ### Custom category Uses a fixed category name for all cross-seeds (e.g., `cross-seed`). AutoTMM is always disabled; uses explicit save paths. ## Source Tagging Configure tags applied to cross-seed torrents based on how they were discovered: | Tag Setting | Description | Default | |-------------|-------------|---------| | RSS Automation Tags | Torrents added via RSS feed polling | `["cross-seed"]` | | Seeded Search Tags | Torrents added via seeded torrent search | `["cross-seed"]` | | Completion Search Tags | Torrents added via completion-triggered search | `["cross-seed"]` | | Webhook Tags | Torrents added via `/apply` webhook | `["cross-seed"]` | | Inherit source torrent tags | Also copy tags from the matched source torrent | - | ## External Program Optionally run an external program after successfully injecting a cross-seed torrent. ## Category Behavior Details ### autoTMM (Auto Torrent Management) autoTMM behavior depends on which category mode is active: | Category Mode | autoTMM Behavior | |---------------|------------------| | **Category Affix** | Inherited from matched torrent | | **Indexer name** | Always disabled (explicit save paths) | | **Custom** | Always disabled (explicit save paths) | When autoTMM is inherited (affix mode): - If matched torrent uses autoTMM, cross-seed uses autoTMM - If matched torrent has manual path, cross-seed uses same manual path When autoTMM is disabled (indexer/custom modes), cross-seeds always use explicit save paths derived from the matched torrent's location. ### Save Path Determination Priority order: 1. Base category's explicit save path (if configured in qBittorrent) 2. Matched torrent's current save path (fallback) **Examples:** *Suffix mode (default):* - `tv` category has save path `/data/tv` - Cross-seed gets `tv.cross` category with save path `/data/tv` - Files are found because they're in the same location *Prefix mode:* - `movies` category has save path `/data/movies` - Cross-seed gets `cross/movies` category with save path `/data/movies` - Nested `cross/` parent in qBittorrent groups all cross-seeds together ## Best Practices **Do:** - Use autoTMM consistently across your torrents - Let qui create cross-seed categories automatically - Keep category structures simple - Use prefix mode with `/` (e.g., `cross/`) if you want all cross-seeds grouped under one parent category **Don't:** - Manually move torrent files after adding them - Create cross-seed categories manually with different paths - Mix autoTMM and manual paths for the same content type --- ## Troubleshooting # Cross-Seed Troubleshooting ## Why didn't my cross-seed get added? ### Rate limiting (HTTP 429) Indexers limit how frequently you can make requests. If you see errors like `"indexer TorrentLeech rate-limited until..."`, qui has recorded the cooldown and will skip that indexer until it's available. Check the **Scheduler Activity** panel on the Indexers page to see which indexers are in cooldown and when they'll be ready. ### Release didn't match qui uses strict matching to ensure cross-seeds have identical files. Both releases must match on: - Title, year, and release group - Resolution (1080p, 2160p) - Source (WEB-DL, BluRay) and collection (AMZN, NF) - Codec (x264, x265) and HDR format - Audio format and channels - Language, edition, cut, and version (v2, v3) - Variants like IMAX, HYBRID, REPACK, PROPER ### Season pack vs episodes By default, season packs only match other season packs. Enable **Find individual episodes** in settings to allow season packs to match individual episode releases. ## How do I see why a release was filtered? Enable trace logging to see detailed rejection reasons: ```toml loglevel = 'TRACE' ``` Look for `[CROSSSEED-MATCH] Release filtered` entries showing exactly which field caused the mismatch (e.g., `group_mismatch`, `resolution_mismatch`, `language_mismatch`). ## When Rechecks Are Required (Reuse Mode) In reuse mode (the default), most cross-seeds are added with hash verification skipped (`skip_checking=true`) and resume immediately. Some scenarios require a recheck: ### 1. Name or folder alignment needed When the cross-seed torrent has a different display name or root folder, qui renames them to match. qBittorrent must recheck to verify files at the new paths. ### 2. Extra files in source torrent When the source torrent contains files not on disk (NFO, SRT, samples not matching allowed extra file patterns), a recheck determines actual progress. ### Auto-resume behavior - Default tolerance 5% → auto-resumes at ≥95% completion - Torrents below threshold stay paused for manual investigation - Configure via **Size mismatch tolerance** in Rules ## Hardlink mode failed Common causes: - **Filesystem mismatch**: Hardlink base directory is on a different filesystem/volume than the download paths. Hardlinks cannot cross filesystems. - **Missing local filesystem access**: The target instance doesn't have "Local filesystem access" enabled in Instance Settings. - **Permissions**: qui cannot read the instance's content paths or write to the hardlink base directory. - **Invalid base directory**: The hardlink base directory path doesn't exist and couldn't be created. ## "Files not found" after cross-seed (default mode) This typically occurs in default mode when the save path doesn't match where files actually exist: - Check that the cross-seed's save path matches where files actually exist - Verify the matched torrent's save path in qBittorrent - Ensure the matched torrent has completed downloading (100% progress) ## Reflink mode failed Common causes: - **Filesystem doesn't support reflinks**: The filesystem at the base directory doesn't support copy-on-write clones. On Linux, use BTRFS or XFS (with reflink enabled). On macOS, use APFS. - **Pooled/virtual mount**: The base directory is on a pooled/virtual filesystem (like `mergerfs`, other FUSE mounts, or `overlayfs`) which often does not implement reflink cloning. Use a direct disk mount for both your seeded data and the reflink base directory. - **Filesystem mismatch**: Base directory is on a different filesystem than the download paths. - **Missing local filesystem access**: The target instance doesn't have "Local filesystem access" enabled. - **SkipRecheck enabled**: If reflink mode would require recheck (extra files), it skips the cross-seed. ## Cross-seed skipped: "extra files share pieces with content" This only occurs when you have enabled the piece boundary safety check (disabled "Skip piece boundary safety check" in Rules). The incoming torrent has files not present in your matched torrent, and those files share torrent pieces with your existing content. Downloading them could overwrite parts of your existing files. **Solutions:** - **Use reflink mode** (recommended): Enable reflink mode for the instance—it safely clones files so qBittorrent can modify them without affecting originals - **Disable the safety check**: Check "Skip piece boundary safety check" in Rules (the default). The match will proceed but **may corrupt your existing seeded files** if content differs - If reflinks aren't available and you want to avoid any risk, download the torrent fresh ## Cross-seed stuck at low percentage after recheck - Check if the source torrent has extra files (NFO, samples) not present on disk - Verify the "Size mismatch tolerance" setting in Rules - Torrents below the auto-resume threshold stay paused for manual review ## Blu-ray or DVD cross-seed left paused Torrents containing disc-based media (Blu-ray `BDMV` or DVD `VIDEO_TS` folder structures) are always added paused. **Why?** Disc layout torrents are sensitive to file alignment. Even minor path differences can cause qBittorrent to redownload large video segments, potentially corrupting your seeded content. Leaving them paused lets you verify the recheck completed at 100% before resuming. **What to do:** 1. If **Skip recheck** is enabled in Cross-Seed Rules, disc-layout matches will be skipped. 2. Otherwise, qui triggers a recheck automatically and will only auto-resume once the recheck reaches **100%**. 3. If you have auto-resume disabled, resume manually after verifying it reaches 100%. The result message will indicate when this policy applies (example): `"disc layout detected (BDMV), full recheck required"` ## Webhook returns HTTP 400 "invalid character" error This typically means the torrent name contains special characters (like double quotes `"`) that break JSON encoding. The error often looks like: ```json {"level":"error","error":"invalid character 'V' after object key:value pair","time":"...","message":"Failed to decode webhook check request"} ``` **Solution:** In your autobrr webhook configuration, use `toRawJson` instead of quoting the template variable directly: ```json { "torrentName": {{ toRawJson .TorrentName }}, "instanceIds": [1] } ``` **Not:** ```json { "torrentName": "{{ .TorrentName }}", "instanceIds": [1] } ``` The `toRawJson` function (from Sprig) properly escapes special characters and outputs a valid JSON string including the quotes. ## Cross-seed in wrong category - Check your cross-seed settings in qui - Verify the matched torrent has the expected category ## autoTMM unexpectedly enabled/disabled - In affix mode, autoTMM mirrors the matched torrent's setting (intentional) - In indexer name or custom category mode, autoTMM is always disabled - Check the original torrent's autoTMM status in qBittorrent --- ## External Programs Launch scripts or desktop applications directly from the torrent context menu. Each program definition stores the executable path, optional arguments, and path-mapping rules so qui can pass torrent metadata to your tools. ## Security: Allow List To keep this power feature safe, define an allow list in `config.toml` so only trusted paths can be executed: ```toml externalProgramAllowList = [ "/usr/local/bin/sonarr", "/home/user/bin" # Directories allow any executable inside them ] ``` Leave the list empty to keep the previous behaviour (any path accepted). The allow list lives exclusively in `config.toml`, which the web UI cannot edit, so you retain control over what binaries are exposed. ## Where Programs Run External programs always run on the same machine (or container) that is hosting the qui backend, not on the browser client. Make sure any executable paths, mounts, or environment variables are available to that host process. When you deploy qui inside Docker, the program runs inside the container unless you mount the executable in. ## Creating and Editing a Program 1. Open qui and go to **Settings → External Programs** 2. Click **Create External Program** 3. Fill in the form fields, then press **Create**. Toggle **Enable this program** to make it available in torrent menus 4. Use the edit and delete actions in the list to maintain existing programs ### Field Reference | Field | Description | |-------|-------------| | **Name** | Display label shown in the torrent context menu and settings list. Must be unique. | | **Program Path** | Absolute path to the executable or script. Use the host path seen by the qui backend (e.g. `/usr/local/bin/my-script.sh`, `C:\Scripts\postprocess.bat`, `C:\python312\python.exe`). | | **Arguments Template** | Optional string of command-line arguments. qui substitutes torrent metadata placeholders before spawning the process. | | **Path Mappings** | Optional array of `from → to` prefixes that rewrite remote qBittorrent paths into local mount points. Helpful when qui runs locally but qBittorrent stores data elsewhere. | | **Launch in terminal window** | Opens the program in an interactive terminal (`cmd.exe` on Windows, first available emulator on Linux/macOS). Disable for GUI apps or background daemons. | | **Enable this program** | Determines whether the program shows up in the torrent context menu. | ## Torrent Placeholders Arguments are parsed with shell-style quoting and each placeholder is replaced with the corresponding torrent value before execution. | Placeholder | Value | |-------------|-------| | `{hash}` | Torrent hash (always lowercase) | | `{name}` | Torrent name | | `{save_path}` | Torrent save path after path mappings are applied | | `{content_path}` | Full content path (file or folder) after path mappings are applied | | `{category}` | Torrent category | | `{tags}` | Comma-separated list of tags | | `{state}` | qBittorrent torrent state string | | `{size}` | Size in bytes | | `{progress}` | Progress value between 0 and 1 rounded to two decimal places | | `{comment}` | Torrent comment | **Example arguments:** ```text "{hash}" "{name}" --save "{save_path}" --category "{category}" --tags "{tags}" ``` ```text D:\Upload Assistant\upload.py {save_path}\{name} ``` qui splits the template into arguments before substitutions are run, so you do not need to wrap values in extra quotes unless the called application expects them. ## Path Mappings Use path mappings when the filesystem paths reported by qBittorrent do not match the paths visible to qui. Each mapping replaces the longest matching prefix. | Remote path (from qBittorrent) | Local path seen by qui | Mapping | |--------------------------------|------------------------|---------| | `/data/torrents` | `/mnt/qbt` | `from=/data/torrents`, `to=/mnt/qbt` | | `Z:\downloads` | `/srv/downloads` | `from=Z:\downloads`, `to=/srv/downloads` | Given the template above, `{save_path}` becomes `/mnt/qbt/Movies` instead of `/data/torrents/Movies`. Be sure to use the same path separator style (`/` vs `\`) as the remote qBittorrent instance. If no mapping matches, the original path is used. ## Launch Modes - **Enable terminal window** for scripts that need interaction or visible output. - **Disable terminal window** for GUI applications or background tasks. Programs run asynchronously - qui does not wait for completion. ## Executing Programs 1. Select one or more torrents 2. Right-click to open the context menu 3. Hover **External Programs**, then click the program name 4. qui queues one execution per selected torrent. Results are reported via toast notifications (success, partial success, or failure) Execution requests include the torrents from the currently selected instance only. Disabled programs are hidden from the submenu. Command failures emitted by the host OS are logged at `info`/`debug` level through zerolog; enable debug logging to see the full command line and any non-zero exit codes. ## REST API Automation workflows can manage external programs through the backend API (all endpoints require authentication): | Method | Endpoint | Description | |--------|----------|-------------| | `GET` | `/api/external-programs` | List programs | | `POST` | `/api/external-programs` | Create a program | | `PUT` | `/api/external-programs/{id}` | Update a program | | `DELETE` | `/api/external-programs/{id}` | Remove a program | | `POST` | `/api/external-programs/execute` | Execute a program | **Example request:** ```http POST /api/external-programs/execute Content-Type: application/json { "program_id": 2, "instance_id": 1, "hashes": ["c0ffee...", "deadbeef..."] } ``` The response contains a `results` array with per-hash `success` flags and optional error messages. Treat the endpoint as fire-and-forget; it returns once the processes have been spawned. ## Troubleshooting - **Docker**: The executable must be inside the container or bind-mounted. - **Paths are wrong**: Add or adjust path mappings so `{save_path}` and `{content_path}` resolve to local mount points. - **Multiple torrents**: The program runs once per torrent. Ensure your script handles concurrent executions or uses a locking mechanism. --- ## Orphan Scan Finds and removes files in your download directories that aren't associated with any torrent. ## How It Works 1. **Scan roots are determined dynamically** - qui scans all unique `SavePath` directories from your current torrents, not qBittorrent's default download directory 2. Files not referenced by any torrent are flagged as orphans 3. You preview the list before confirming deletion 4. Empty directories are cleaned up after file deletion :::info If you have multiple **active** qBittorrent instances with `Has local filesystem access` enabled, and their torrent `SavePath` directories overlap, qui also protects files referenced by torrents from those other instances (even when scanning a single instance). To do this safely, qui must be able to determine whether scan roots overlap. If any other local-access instance is unreachable/not ready, the scan fails to avoid false positives. ::: :::warning **Disabled instances are not protected.** If you have a disabled instance with local filesystem access that shares save paths with an active instance, its files may be flagged as orphans. Enable the instance or ensure paths don't overlap before scanning. ::: :::warning[Docker: Local Filesystem Access] Several qui features require access to the same filesystem paths that qBittorrent uses (orphan scan, hardlinks, reflinks, automations). Mount the same paths qBittorrent uses - paths must match exactly: ```yaml volumes: - /config:/config - /data/torrents:/data/torrents # Must match qBittorrent's path ``` After mounting, enable **Local Filesystem Access** on each instance in qui's Instance Settings. ::: ## Important: Abandoned Directories Directories are only scanned if at least one torrent points to them. If you delete all torrents from a directory, that directory is no longer a scan root and any leftover files there won't be detected. **Example:** You have torrents in `/downloads/old-stuff/`. You delete all those torrents. Orphan scan no longer knows about `/downloads/old-stuff/` and won't clean it up. ## Settings | Setting | Description | Default | |---------|-------------|---------| | Grace period | Skip files modified within this window | 10 minutes | | Ignore paths | Directories to exclude from scanning | - | | Scan interval | How often scheduled scans run | 24 hours | | Max files per run | Limit results to prevent overwhelming large scans | 1,000 | | Auto-cleanup | Automatically delete orphans from scheduled scans | Disabled | | Auto-cleanup max files | Only auto-delete if orphan count is at or below this threshold | 100 |
Orphan Scan skips common OS/NAS metadata, recycle bin, snapshot, and Kubernetes volume-internal entries automatically (case-insensitive). **Ignored files (exact names)** - `.DS_Store` - `.directory` - `desktop.ini` - `Thumbs.db` **Ignored files (name prefixes)** - `.fuse*` (e.g. `.fuse_hidden*`) - `.nfs*` - `._*` - `.goutputstream-*` - `.#*` - `~$*` **Ignored files (name suffixes)** - `*.parts` (qBittorrent partial download files) **Ignored directories (exact names)** - `.AppleDB` - `.AppleDouble` - `.TemporaryItems` - `.Trashes` - `.Recycle.Bin` - `.recycle` - `.snapshot` - `.snapshots` - `.zfs` - `@eaDir` - `$RECYCLE.BIN` - `#recycle` - `lost+found` - `System Volume Information` **Ignored directories (name prefixes)** - `.Trash-*` - `..*` (Kubernetes internals like `..data` and timestamp dirs)
## Workflow 1. Trigger a scan (manual or scheduled) 2. Review the preview list of orphan files 3. Confirm deletion 4. Files are deleted and empty directories cleaned up ## Preview Features - **Path column** - Shows the full file path with copy-to-clipboard support - **Export CSV** - Download the full preview list (all pages) as a CSV file --- ## Reannounce # Tracker Reannounce qui can automatically fix stalled torrents by reannouncing them to trackers. This helps when a tracker fails to register a new upload immediately, ensuring your torrents start seeding without manual intervention. qBittorrent doesn't retry failed announces quickly. When a tracker is slow to register a new upload or returns an error, you may be stuck waiting for a long time. qui handles this automatically and gracefully. qui never spams trackers. While a tracker is still updating or waiting for a response, qui waits patiently. It only acts once a tracker has responded and there's an actual problem to fix. ## Quick Start 1. Go to **Services** in the main navigation. 2. Select an instance from the dropdown. 3. In the **Tracker Reannounce** section, toggle **Enabled** to turn it on. 4. Click **Save Changes**. That's it! qui will now monitor stalled torrents in the background. ## Configuration ### Timing | Setting | Description | Default | |---------|-------------|---------| | Initial Wait | How long to wait after a torrent is added before checking it | 15s | | Retry Interval | How often to retry within a single reannounce attempt | 7s | | Max Torrent Age | Stop monitoring torrents older than this | 10 mins | | Max Retries | Maximum consecutive retries within a single scan cycle | 50 | Some slow trackers need up to 50 retries at 7s intervals (~6 minutes) to register uploads. ### Monitoring Scope You can choose which torrents to monitor: - **Monitor All Stalled Torrents**: Checks every stalled torrent. - Use **Exclusions** below to ignore specific Categories, Tags, or Trackers (e.g., ignore "public" trackers). - **Custom Filter (Monitor All Disabled)**: - Only checks torrents that match your **Include** rules. - You can still add **Exclusions** to block specific items within those allowed groups. ### Quick Retry By default, qui waits about **2 minutes** between reannounce attempts for the same torrent (a per-torrent cooldown between scans). - **Enable Quick Retry** to use the **Retry Interval** (default 7s) as the cooldown instead. This helps stalled torrents recover faster. - The **Retry Interval** controls both the spacing of retries inside each scan attempt and, with Quick Retry enabled, the cooldown between scans. This is especially useful on trackers that are slow to register new uploads. Some sites take a moment before they recognize a new torrent, which can cause initial stalls—Quick Retry helps work around this automatically. ## Activity Log To see what's happening: 1. Go to **Services** and select your instance. 2. Click the **Activity Log** tab in the Tracker Reannounce section. You will see a real-time feed of every torrent checked, whether the reannounce succeeded, failed, or was skipped (e.g., because the tracker is actually working fine). --- ## Reverse Proxy # Reverse Proxy for External Applications qui includes a built-in reverse proxy that allows external applications like autobrr, Sonarr, Radarr, and other tools to connect to your qBittorrent instances **without needing qBittorrent credentials**. ## How It Works qui maintains a shared session with qBittorrent and proxies requests from your external apps. This eliminates login thrash - automation tools reuse the live session instead of racing to re-authenticate. ## Setup Instructions ### 1. Create a Client Proxy API Key 1. Open qui in your browser 2. Go to **Settings → Client Proxy Keys** 3. Click **"Create Client API Key"** 4. Enter a name for the client (e.g., "Sonarr") 5. Choose the qBittorrent instance you want to proxy 6. Click **"Create Client API Key"** 7. **Copy the generated proxy url immediately** - it's only shown once ### 2. Configure Your External Application Use qui as the qBittorrent host with the special proxy URL format: **Complete URL example:** ``` http://localhost:7476/proxy/abc123def456ghi789jkl012mno345pqr678stu901vwx234yz ``` ## Application-Specific Setup ### Sonarr / Radarr 1. Go to `Settings → Download Clients` 2. Select `Show Advanced` 3. Add a new **qBittorrent** client 4. Set the host and port of qui 5. Add URL Base (`/proxy/...`) - remember to include `/qui/` if you use custom baseurl 6. Click **Test** and then **Save** once the test succeeds ### autobrr 1. Open `Settings → Download Clients` 2. Add **qBittorrent** (or edit an existing one) 3. Enter the full url like: `http://localhost:7476/proxy/abc123def456ghi789jkl012mno345pqr678stu901vwx234yz` 4. Leave username/password blank and press **Test** 5. Leave basic auth blank since qui handles that For cross-seed integration with autobrr, see the [Cross-Seed](/docs/features/cross-seed/autobrr) section. ### cross-seed 1. Open cross-seed config file 2. Add or edit the `torrentClients` section 3. Append the full url following the documentation: ``` torrentClients: ["qbittorrent:http://localhost:7476/proxy/abc123def456ghi789jkl012mno345pqr678stu901vwx234yz"], ``` 4. Save the config file and restart cross-seed ### Upload Assistant 1. Open the Upload Assistant config file 2. Add or edit `qui_proxy_url` under the qBitTorrent client settings 3. Append the full url like: `"qui_proxy_url": "http://localhost:7476/proxy/abc123def456ghi789jkl012mno345pqr678stu901vwx234yz",` 4. All other auth type can remain unchanged 5. Save the config file ## Supported Applications This reverse proxy will work with any application that supports qBittorrent's Web API. ## Security Features - **API Key Authentication** - Each client requires a unique key - **Instance Isolation** - Keys are tied to specific qBittorrent instances - **Usage Tracking** - Monitor which clients are accessing your instances - **Revocation** - Disable access instantly by deleting the API key - **No Credential Exposure** - qBittorrent passwords never leave qui ## Intercepted Endpoints The proxy intercepts certain qBittorrent API endpoints to improve performance and enable qui-specific features. Most requests are forwarded transparently to qBittorrent. ### Read Operations (Served from qui) These endpoints are served directly from qui's sync manager for faster response times: | Endpoint | Description | |----------|-------------| | `/api/v2/torrents/info` | Torrent list with standard qBittorrent filtering | | `/api/v2/torrents/search` | Enhanced torrent list with fuzzy search (qui-specific) | | `/api/v2/torrents/categories` | Category list from synchronized data | | `/api/v2/torrents/tags` | Tag list from synchronized data | | `/api/v2/torrents/properties` | Torrent properties | | `/api/v2/torrents/trackers` | Torrent trackers with icon discovery | | `/api/v2/torrents/files` | Torrent file list | These endpoints proxy to qBittorrent and update qui's local state: | Endpoint | Description | |----------|-------------| | `/api/v2/sync/maindata` | Full sync data (updates qui's cache) | | `/api/v2/sync/torrentPeers` | Peer data (updates qui's peer state) | ### Write Operations | Endpoint | Behavior | |----------|----------| | `/api/v2/auth/login` | No-op, returns success if instance is healthy | | `/api/v2/torrents/reannounce` | Delegated to reannounce service when tracker monitoring is enabled | | `/api/v2/torrents/setLocation` | Forwards to qBittorrent, invalidates file cache | | `/api/v2/torrents/renameFile` | Forwards to qBittorrent, invalidates file cache | | `/api/v2/torrents/renameFolder` | Forwards to qBittorrent, invalidates file cache | | `/api/v2/torrents/delete` | Forwards to qBittorrent, invalidates file cache | All other endpoints are forwarded transparently to qBittorrent. --- ## Tracker Icons Cached icons live in your data directory under `tracker-icons/` (next to `qui.db`). Icons are stored as 16×16 PNGs; anything larger than 1024×1024 is rejected. qui automatically downloads a favicon the first time it encounters a tracker host and caches it for future sessions. Failed downloads are retried automatically. Set `trackerIconsFetchEnabled = false` in `config.toml` (or `QUI__TRACKER_ICONS_FETCH_ENABLED=false`) to disable these network fetches. ## Add Icons Manually Copy PNGs named after each tracker host (e.g. `tracker.example.com.png`) into the `tracker-icons/` directory. Files are served as-is, so trimming or resizing is up to you, but matching the built-in size (16×16) keeps them crisp and avoids extra scaling. ## Preload a Bundle of Icons If you have a library of icons, preload them via a mapping file: `tracker-icons/preload.json` (also accepts `.js` variants). ### Format The file can be either a plain JSON object or a snippet exported as `const trackerIcons = { ... };`. - Keys must be the real tracker hostnames (e.g. `tracker.example.org`) - If you include a `www.*` host, qui automatically mirrors the icon to the bare hostname when missing - On startup qui decodes each data URL, normalises the image to 16×16, and writes the PNG to `.png` ### JSON Example ```json { "tracker.example.org": "data:image/png;base64,AAA...", "www.tracker.org": "data:image/png;base64,BBB..." } ``` ### JavaScript Example ```js const trackerIcons = { "tracker.example.org": "data:image/png;base64,CCC...", "www.tracker.org": "data:image/png;base64,DDD..." }; ``` ### Community Resources See [Audionut/add-trackers](https://github.com/Audionut/add-trackers/blob/8db05c0e822f9b3afa46ca784644c4e7e400c92b/ptp-add-filter-all-releases-anut.js#L768) for an example icon bundle. --- ## Docker ## Docker Compose {DockerCompose} ```bash docker compose up -d ``` ## Standalone ```bash docker run -d \ -p 7476:7476 \ -v $(pwd)/config:/config \ ghcr.io/autobrr/qui:latest ``` ## Local Filesystem Access :::warning[Docker: Local Filesystem Access] Several qui features require access to the same filesystem paths that qBittorrent uses (orphan scan, hardlinks, reflinks, automations). Mount the same paths qBittorrent uses - paths must match exactly: ```yaml volumes: - /config:/config - /data/torrents:/data/torrents # Must match qBittorrent's path ``` After mounting, enable **Local Filesystem Access** on each instance in qui's Instance Settings. ::: ## Unraid Our release workflow builds multi-architecture images (`linux/amd64`, `linux/arm64`, and friends) and publishes them to `ghcr.io/autobrr/qui`, so the container should work on Unraid out of the box. ### Deploy from the Docker tab 1. Open **Docker → Add Container** 2. Set **Name** to `qui` 3. Set **Repository** to `ghcr.io/autobrr/qui:latest` 4. Keep the default **Network Type** (`bridge` works for most setups) 5. Add a port mapping: **Host port** `7476` → **Container port** `7476` 6. Add a path mapping: **Container Path** `/config` → **Host Path** `/mnt/user/appdata/qui` 7. Enable **Advanced View** (top right) 8. Set **Icon URL** to `https://raw.githubusercontent.com/autobrr/qui/main/web/public/icon.png` 9. Set **WebUI** to `http://[IP]:[PORT:7476]` 10. (Optional) add environment variables for advanced settings (e.g., `QUI__BASE_URL`, `QUI__LOG_LEVEL`, `TZ`) 11. Click **Apply** to pull the image and start the container The `/config` mount stores `config.toml`, the SQLite database, and logs. Point it at your preferred appdata share so settings persist across upgrades. If the app logs to stdout, check logs via Docker → qui → Logs; if it writes to files, they'll be under `/config`. ### Updating - Use Unraid's **Check for Updates** action to pull a newer `latest` image - If you pinned a specific version tag, edit the repository field to the new tag when you're ready to upgrade - Restart the container if needed after the image update so the new binary is loaded ## Updating ```bash docker compose pull && docker compose up -d ``` --- ## Installation ## Quick Install (Linux x86_64) ```bash # Download and extract the latest release wget $(curl -s https://api.github.com/repos/autobrr/qui/releases/latest | grep browser_download_url | grep linux_x86_64 | cut -d\" -f4) ``` ### Unpack Run with root or sudo. If you do not have root, or are on a shared system, place the binaries somewhere in your home directory like `~/.bin`. ```bash tar -C /usr/local/bin -xzf qui*.tar.gz ``` This will extract qui to `/usr/local/bin`. Note: If the command fails, prefix it with `sudo` and re-run again. ## Manual Download Download the latest release for your platform from the [releases page](https://github.com/autobrr/qui/releases). ## Run ```bash # Make it executable (Linux/macOS) chmod +x qui # Run ./qui serve ``` The web interface will be available at http://localhost:7476 ## Updating qui includes a built-in update command that automatically downloads and installs the latest release: ```bash ./qui update ``` ## First Setup 1. Open your browser to http://localhost:7476 2. Create your account 3. Add your qBittorrent instance(s) 4. Start managing your torrents --- ## Seedbox Installers One-line installers for popular seedbox providers. These scripts automatically configure qui for your specific environment. ```bash wget -O installer.sh https://get.autobrr.com/qui/feral && chmod +x installer.sh && ./installer.sh ``` ```bash wget -O installer.sh https://get.autobrr.com/qui/seedhost && chmod +x installer.sh && ./installer.sh ``` ```bash wget -O installer.sh https://get.autobrr.com/qui/ultra && chmod +x installer.sh && ./installer.sh ``` ```bash wget -O installer.sh https://get.autobrr.com/qui/whatbox && chmod +x installer.sh && ./installer.sh ``` ```bash wget -O installer.sh https://get.autobrr.com/qui/hostingbydesign && chmod +x installer.sh && ./installer.sh ``` ```bash wget -O installer.sh https://get.autobrr.com/qui/bytesized && chmod +x installer.sh && ./installer.sh ``` :::note This installer has not been tested. ::: --- ## Windows # Windows Installation In this guide we will download qui, set it up, and create a Windows Task so it runs in the background without needing a command prompt window open 24/7. ## Download 1. Download the latest Windows release from [GitHub Releases](https://github.com/autobrr/qui/releases/latest). - For most systems, download `qui_x.x.x_windows_amd64.zip`. 2. Extract the archive and place `qui.exe` in a directory, for example `C:\qui`. :::tip Avoid placing qui in `C:\Program Files` — it can cause permission issues with the database and config files. ::: ## Initial Setup 1. Open **Command Prompt** or **PowerShell** and navigate to the directory: ```powershell cd C:\qui ``` 2. Start qui for the first time to generate the default config and create your account: ```powershell .\qui.exe serve ``` 3. Open your browser to [http://localhost:7476](http://localhost:7476) and create your account. 4. Once you've verified it works, stop qui with `Ctrl+C`. We'll set it up as a background task next. ### Configuration qui stores its configuration and database in `%APPDATA%\qui\` by default. For more details, see the [Configuration](/docs/configuration/environment) section. ## Create a Windows Task To run qui in the background, we'll use **Task Scheduler**. 1. Press the **Windows key** and search for **Task Scheduler**. 2. Click **Create Basic Task** in the right sidebar. 3. **Name:** `qui` — optionally add a description like: *qui torrent management service*. 4. **Trigger:** Select **When the computer starts**. 5. **Action:** Select **Start a Program**. - **Program/script:** Browse to `C:\qui\qui.exe` - **Add arguments:** `serve` - **Start in:** `C:\qui` 6. Check **Open the Properties dialog** before finishing, then click **Finish**. ### Configure the task properties In the Properties dialog: - Under **General**, select **Run whether user is logged on or not**. - Enter your Windows password when prompted. - Optionally check **Run with highest privileges** if you encounter permission issues. Click **OK** to save. ### Start the service Right-click on **qui** in the Task Scheduler list and click **Run**. :::tip To restart the service, click **End** and then **Run** in the right sidebar of Task Scheduler. ::: ## Updating qui has a built-in update command. You must stop the Task Scheduler job first, otherwise Windows will lock the executable and the update will fail. 1. Open **Task Scheduler**, right-click the **qui** task and click **End**. 2. Run the updater: ```powershell .\qui.exe update ``` 3. Right-click the **qui** task again and click **Run** to restart it. ## Reverse Proxy (optional) For remote access, it's recommended to run qui behind a reverse proxy like [Caddy](https://caddyserver.com/) or nginx for TLS and additional security. See the [Base URL](/docs/configuration/base-url) section for reverse proxy configuration examples. ## Finishing Up Once the task is running, qui will be available at [http://localhost:7476](http://localhost:7476). Add your qBittorrent instance(s) and start managing your torrents. --- ## Introduction # qui A web interface for qBittorrent. Manage multiple qBittorrent instances from a single application. ## Features - **Single Binary**: No dependencies, just download and run - **Multi-Instance Support**: Manage all your qBittorrent instances from one place - **Large Collections**: Handles thousands of torrents efficiently - **Themeable**: Multiple color themes available - **Base URL Support**: Serve from a subdirectory (e.g., `/qui/`) for reverse proxy setups - **OIDC Single Sign-On**: Authenticate through your OpenID Connect provider - **External Programs**: Launch custom scripts from the torrent context menu - **Tracker Reannounce**: Automatically fix stalled torrents when qBittorrent doesn't retry fast enough - **Automations**: Rule-based torrent management with conditions, actions (delete, pause, tag, limit speeds), and cross-seed awareness - **Orphan Scan**: Find and remove files not associated with any torrent - **Backups & Restore**: Scheduled snapshots with incremental, overwrite, and complete restore modes - **Cross-Seed**: Automatically find and add matching torrents across trackers with autobrr webhook integration - **Reverse Proxy**: Transparent qBittorrent proxy for external apps like autobrr, Sonarr, and Radarr—no credential sharing needed ## Browser Extensions Right-click any magnet or torrent link to add it directly to your qBittorrent instances: - [Chrome Extension](https://chromewebstore.google.com/detail/kbjnjgihepmcoilegnghgpmijbecoili) - [Firefox Add-on](https://addons.mozilla.org/en-US/firefox/addon/qui/) ## Quick Start Get started in minutes: 1. [Install qui](/docs/getting-started/installation) 2. Open your browser to http://localhost:7476 3. Create your admin account 4. Add your qBittorrent instance(s) 5. Start managing your torrents ## Community Join our friendly and welcoming community on [Discord](https://discord.autobrr.com/qui)! Connect with fellow autobrr users, get advice, and share your experiences. ## License GPL-2.0-or-later ## Supported Torrent Clients qui currently only supports qBittorrent. It communicates directly with the qBittorrent Web API. Support for other torrent clients such as Deluge, rTorrent, and Transmission is not yet available, but we hope to support them all in the future. For details on which qBittorrent versions are compatible, see the [qBittorrent Version Compatibility](./advanced/compatibility.md) page. --- ## Support Development qui is developed and maintained by volunteers. Your support helps us continue improving the project. ## License Key Donate what you want (minimum $4.99) to unlock premium themes: - Use any donation method below - After donating, DM soup or ze0s on Discord (whoever you donated to) - For crypto, include the transaction hash/link - You'll receive a 100% discount code - Redeem the code on [Polar](https://buy.polar.sh/polar_cl_yyXJesVM9pFVfAPIplspbfCukgVgXzXjXIc2N0I8WcL) (free order) to receive your license key - Enter the license key in Settings → Themes in your qui instance - License is lifetime ## Donation Methods - **soup** - [GitHub Sponsors](https://github.com/sponsors/s0up4200) - [Buy Me a Coffee](https://buymeacoffee.com/s0up4200) - **zze0s** - [GitHub Sponsors](https://github.com/sponsors/zze0s) - [Buy Me a Coffee](https://buymeacoffee.com/ze0s) ### Cryptocurrency #### Bitcoin (BTC) #### Ethereum (ETH) #### Litecoin (LTC) #### Monero (XMR) --- All methods unlock premium themes — use whichever works best for you. For other currencies or donation methods, [reach out on Discord](https://discord.autobrr.com/qui).