Season Packs
qui can assemble season-pack torrents from individual episodes you already seed. When autobrr announces a season pack, qui checks your qBittorrent instances for completed, release-compatible episodes and, if enough local data is present, builds a linked directory tree, adds the torrent, and lets qBittorrent download anything still missing.
How It Works
- autobrr sees a season pack release
- autobrr sends the torrent name (and optionally the torrent file) to qui's
/api/cross-seed/season-pack/checkendpoint - If a torrent file is provided, qui parses its file list to determine playable episode files. If not, qui uses metadata providers for episode counts.
- qui scans your qBittorrent instances for completed individual episodes that match the season pack's release details
- qui computes coverage from completed, matching local episodes:
- When torrent data is provided, the pack torrent's playable episode files define the expected pack layout
- qui asks Sonarr for the season total first, when Sonarr can resolve the show
- If Sonarr cannot resolve it, qui falls back to metadata providers: TVDB when configured, then TVMaze
- With torrent data, qui never uses a total lower than the playable episode count inside the pack torrent
- qui responds with:
200 OK- coverage meets the threshold, ready to apply404 Not Found- local coverage is too low, the release is not a season pack, or the feature is disabled
- On
200 OK, autobrr sends the torrent file to/api/cross-seed/season-pack/apply - qui links the matched episodes, applies your configured season-pack tags, and adds the season pack torrent
- If episodes or extras are still missing, qui adds the torrent paused, attempts an automatic recheck, and queues automatic resume. After recheck, qui resumes the torrent when qBittorrent reports progress at or above your configured season-pack coverage threshold. If recheck finishes below that threshold, qui leaves the torrent paused for manual review. Best-effort fallbacks are reported by name, including
automatic recheck failed,automatic resume is unavailable, andautomatic resume queue is full.
Coverage Model
qui uses a provider-first episode total with the pack torrent as the layout source.
For /check without torrent data:
- qui asks Sonarr for the season episode total first
- If Sonarr fails or cannot resolve the show, qui asks TVDB when configured, otherwise TVMaze
- If no provider returns a total, qui skips threshold enforcement and only verifies that matching episodes exist
For /check or /apply with torrent data:
- The torrent file is the source of truth for the pack layout and playable episode files
- qui still asks Sonarr, then TVDB/TVMaze, for a season total
- If the provider total is lower than the playable episode count in the torrent, qui uses the playable file count instead
The apply endpoint always requires the torrent file and enforces the threshold.
When qui falls back to the pack torrent, it:
- Counts only playable video files (mkv, mp4, avi, etc.)
- Ignores subtitles, NFOs, samples, and other extras
- Deduplicates episodes that appear more than once
- Rejects packs with zero usable episode files
Coverage is then: matchedLocalEpisodes / coverageTotalEpisodes
For an episode to count toward coverage, it must:
- Be fully downloaded (
100%progress) - Pass the same release-compatibility checks used by normal cross-seeding
- Belong to the same episode in the season pack
This means mixed variants do not count toward coverage. For example, 720p WEB episodes do not satisfy a 1080p BluRay season pack.
The default threshold is 75%. Change it in Cross-Seed > Rules > Season packs in the qui UI.
Matching Settings
These settings only affect season-pack checks and applies. They do not change normal cross-seed matching in the Rules tab.
Defaults are chosen to match common seasonpackarr expectations.
| Setting | Default | Effect | Example |
|---|---|---|---|
| Ignore REPACK/PROPER differences | On | Treat REPACK and PROPER episodes as compatible with the season pack. | Show.S01E01.REPACK matches Show.S01E01.PROPER |
| Simplify HDR matching | Off | Treat HDR10, HDR10+, and HDR+ as HDR for season-pack matching. | HDR10+ matches HDR10 |
| Simplify WEB source matching | Off | Treat WEB-DL as WEB for season-pack matching. | WEB-DL matches WEB |
| Ignore year differences | Off | Allow matches when release dates differ or one side omits the year. | Show.2024.S01E01 matches Show.2025.S01E01 |
Apply Model
Passing the threshold does not require 100% local coverage.
When /apply runs, qui:
- Links every matched episode file it can verify locally
- Leaves unmatched episodes and extras for qBittorrent to download
- Adds the torrent paused when anything is still missing
- Attempts an automatic recheck so qBittorrent can discover the linked bytes
- Queues automatic resume after recheck. qui resumes the torrent when qBittorrent reports progress at or above the configured season-pack coverage threshold, so qBittorrent can download the remaining files or pieces. If recheck finishes below that threshold, qui leaves the torrent paused for manual review.
If automatic recheck or resume queueing cannot be started, qui reports automatic recheck failed, automatic resume is unavailable, or automatic resume queue is full.
If Skip Recheck is enabled and the pack is incomplete, qui skips the apply instead of adding a broken torrent.
In hardlink mode, incomplete packs are also subject to piece-boundary protection. If pending files share torrent pieces with linked episode files, qui blocks the apply unless Skip piece boundary safety check is enabled. Reflink mode avoids that hardlink corruption risk because qBittorrent writes to cloned files instead of the original seeded files.
Prerequisites
- Local filesystem access must be enabled on the target instance
- Hardlink or reflink mode must be enabled on the target instance - season packs always use linked trees
- The instance's link-mode base directory must be configured and writable. In the current UI/API this is the same base-directory field used by hardlink/reflink mode.
Instances without local filesystem access or a link mode are skipped during eligibility checks.
See Hardlink Mode for setup instructions.
Setup
1. Enable Season Packs in qui
- Go to Cross-Seed > Rules > Season packs
- Enable the feature
- Set the coverage threshold (default 75%)
- Optionally, add a TVDB API key for improved episode count accuracy. TVMaze is used automatically as a free fallback without any configuration.
- Optionally, set a Category override for season pack injects. If you use Sonarr, set this to the same category Sonarr is watching on its qBittorrent download client (for example
tv-hdortv-uhd). Sonarr will pick up the assembled pack and hardlink-import its files into your library, so the same on-disk bytes back both the library and every seeded episode. If left empty, season packs use the global Category Mode configured under Cross-Seed > Rules > Categories.
2. Create an API Key
If you don't already have one for autobrr:
- Go to Settings > API Keys
- Click Create API Key
- Copy the generated key
3. Configure autobrr External Filter
Create a separate autobrr filter for season packs. Do not reuse your existing cross-seed filter - the endpoints and payload are different.
Docker Compose: use your qui container hostname instead of localhost (often the Compose service name), for example: http://qui:7476/api/cross-seed/season-pack/check.
In your new autobrr filter, go to External tab > Add new:
| Field | Value |
|---|---|
| Type | Webhook |
| Name | qui season pack |
| On Error | Reject |
| Endpoint | http://localhost:7476/api/cross-seed/season-pack/check |
| HTTP Method | POST |
| HTTP Request Headers | X-API-Key=YOUR_QUI_API_KEY |
| Expected HTTP Status Code | 200 |
Data (JSON):
{
"torrentName": {{ toRawJson .TorrentName }},
"instanceIds": [1],
"indexer": {{ toRawJson .Indexer }}
}
To search all instances, omit instanceIds:
{
"torrentName": {{ toRawJson .TorrentName }},
"indexer": {{ toRawJson .Indexer }}
}
The check endpoint does not require the torrent file. Sending only the release name avoids downloading the .torrent for every season pack announce. qui uses Sonarr, TVDB, or TVMaze to determine the episode count for threshold enforcement. To include the torrent file in the check request, add "torrentData": "{{ .TorrentDataRawBytes | toString | b64enc }}" to the payload.
Field descriptions:
torrentName(required) - The release name as announcedtorrentData(optional) - Base64-encoded torrent file. When provided, qui parses it to determine playable pack files. When omitted, qui uses metadata providers for episode counts.instanceIds(optional) - qBittorrent instance IDs to scan. Omit to search all eligible instances.indexer(optional) - autobrr indexer identifier. Used when Use indexer name as category is enabled.
4. Configure the Apply Action
When /check returns 200 OK, send the torrent to /api/cross-seed/season-pack/apply:
Action setup in autobrr:
| Field | Value |
|---|---|
| Action Type | Webhook |
| Name | qui season pack apply |
| Endpoint | http://localhost:7476/api/cross-seed/season-pack/apply?apikey=YOUR_QUI_API_KEY |
Payload (JSON):
{
"torrentName": {{ toRawJson .TorrentName }},
"torrentData": "{{ .TorrentDataRawBytes | toString | b64enc }}",
"instanceIds": [1],
"indexer": {{ toRawJson .Indexer }}
}
Field descriptions:
torrentName(required) - The release nametorrentData(required) - Base64-encoded torrent fileinstanceIds(optional) - Target instances (omit to apply to any matching instance)indexer(optional) - autobrr indexer identifier. Used when Use indexer name as category is enabled.
API Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /api/cross-seed/season-pack/check | Check if a pack can be assembled |
| POST | /api/cross-seed/season-pack/apply | Assemble and add the pack |
| GET | /api/cross-seed/season-pack/runs | List recent activity |
The /runs endpoint accepts an optional limit query parameter (default 20, max 200). qui keeps the most recent 200 season-pack runs and prunes older rows when new check/apply activity is recorded.
/check returns 404 Not Found for expected skips such as below-threshold coverage, disabled season packs, non-season-pack releases, or no eligible instances. /apply returns 500 Internal Server Error when the pack cannot be applied, including skipped recheck-required packs, layout mismatch, add failure, or operational failures while reading qBittorrent state.
Added Torrent Behavior
When qui applies a season pack, it:
- Always adds the torrent with an explicit
savepathpointing at the linked tree - Applies the tags configured in Cross-Seed > Rules > Season packs
- Adds incomplete packs paused, then best-effort attempts automatic recheck and queues automatic resume. After recheck, qui resumes at or above the configured season-pack coverage threshold; below that threshold, the torrent stays paused for manual review.
- Uses the Category override from Cross-Seed > Rules > Season packs when set (recommended for Sonarr integration so the pack lands in Sonarr's download-client category and inherits hardlink-aware imports)
- Otherwise uses the global cross-seed category rules:
- Custom category, if enabled
- Otherwise category affix mode, if enabled
- Otherwise indexer-name category, if enabled
Instance Selection
When instanceIds is omitted or contains multiple instances:
- qui filters to instances with local filesystem access and hardlink/reflink mode
- Existing webhook source filters are applied
- The instance with the highest coverage is selected
- Ties are broken by highest matched episode count, then lowest instance ID
Activity
Each check and apply request records a season-pack run. qui keeps the most recent 200 runs. Recent runs are shown in Cross-Seed > Rules > Season packs. The panel shows the torrent name, phase (check or apply), status, reason, message, selected instance, matched episodes, total episodes, coverage, link mode, and timestamp.
You can also query recent runs directly:
curl -H "X-API-Key: YOUR_QUI_API_KEY" "http://localhost:7476/api/cross-seed/season-pack/runs?limit=20"
Debugging
Start with autobrr:
- A rejected check usually appears as
[external webhook status code] not matching: got 404 want: 200 - That means qui answered the season-pack check but did not consider the release ready to apply
- Confirm the release used the season-pack filter, not the regular cross-seed filter
Then check qui:
- Open Cross-Seed > Rules > Season packs and find the recent row for the torrent name
- Check the phase (
checkorapply), status, reason, message, coverage, matched episodes, total episodes, selected instance, and link mode - If the row is missing, autobrr probably did not reach qui or used the wrong endpoint/API key. You can confirm with
/api/cross-seed/season-pack/runs?limit=20.
For deeper logs, set:
loglevel = 'DEBUG'
Look for messages containing the torrent name and these clues:
season pack: failed to resolve Sonarr season total- Sonarr lookup failed, so qui fell back to metadata providers or skipped threshold enforcementseason pack: metadata provider lookup failed- TVDB/TVMaze lookup failedload cached torrents for instance- qBittorrent cache lookup failed, so the check/apply is an operational failureunsafe piece boundary with pending files- hardlink mode blocked an incomplete pack for safetytorrent added paused; recheck queued- qui added the pack and queued automatic resumeRecheck completed below threshold, torrent left paused for manual review- qBittorrent rechecked below the configured season-pack coverage threshold
Use TRACE when you need field-level matching details. Then look for [CROSSSEED-MATCH] Release filtered entries to see which release field caused an episode to be rejected.