Configuration Reference¶
This page is the exhaustive, authoritative reference for every key you can set in
unshackle.yaml. For each key you will find its name, expected type, default value, and
what it does. Keys are grouped by area so you can jump straight to the part of unshackle
you are configuring.
If you are new to the config file (where it lives, how unshackle discovers it, and how to edit it), start with The Configuration File. This page assumes you already know the basics and want precise details.
How defaults work
Every key is optional. If you omit a key, unshackle uses the built-in default listed
here. There is no schema validation: unknown keys are silently ignored (they never
become settings), and the file is parsed with yaml.safe_load. An empty or missing
file means all defaults apply.
Two keys behave specially on load
curl_impersonateis a deprecated alias ofnetworkand emits aDeprecationWarning.scene_naminghas been removed. If it is present at all, unshackle exits immediately with an error telling you to useoutput_templateinstead.
See Deprecated & removed keys at the end of this page.
Quick index¶
| Area | Keys |
|---|---|
| Directories | directories, filenames |
| Services & authentication | services, credentials, firefox_cookies, remote_services, serve |
| Download & processing | dl, subtitle, audio, muxing, language_tags |
| Output & naming | output_template, tag, tag_group_name, tag_imdb_tmdb, chapter_fallback_name, unicode_filenames |
| DRM & CDM | cdm, remote_cdm, decryption |
| Network & proxy | network, headers, proxy_providers |
| Key vaults | key_vaults, vault_timeout |
| External API keys | imdb_api_enabled, omdb_api_key, tmdb_api_key, simkl_client_id, decrypt_labs_api_key, ipinfo_api_key |
| Caching & updates | title_cache_enabled, title_cache_time, title_cache_max_retention, update_checks, update_check_interval |
| Logging, privacy & debug | redact_paths, debug, debug_keys, debug_requests, set_terminal_bg |
| Deprecated & removed | curl_impersonate, downloader, scene_naming |
Directories¶
The directories key is a dict mapping directory names to filesystem paths. Only the
names listed below are honoured; a handful are protected and cannot be moved (attempts
to override them are silently skipped). All defaults are computed relative to the installed
package, and every user-settable path is passed through Path(...).expanduser(), so ~
works.
directories:
downloads: ~/Media/unshackle
temp: /mnt/fast-scratch/unshackle-temp
wvds: ~/.unshackle/WVDs
prds: ~/.unshackle/PRDs
| Key | Type | Default | Overridable | Purpose |
|---|---|---|---|---|
downloads |
path | <repo>/downloads |
✅ | Default output directory for finished files. |
temp |
path | <repo>/temp |
✅ | Temporary working files during download/decrypt/mux. |
cache |
path | <data>/cache |
✅ | Generic cache, title cache, and the update-check store. |
cookies |
path | <data>/cookies |
✅ | Per-service cookie files (and VPN cookie/token files). |
logs |
path | <data>/logs |
✅ | Log files. |
exports |
path | <data>/exports |
✅ | Export JSON files. |
wvds |
path | <data>/WVDs |
✅ | Widevine device files (.wvd). |
prds |
path | <data>/PRDs |
✅ | PlayReady device files (.prd). |
dcsl |
path | <data>/DCSL |
✅ | DCSL data. |
commands |
path | unshackle/commands |
✅ | CLI command modules. |
services |
list | path | [unshackle/services] |
✅ | Service search paths and/or remote repo specs (see below). |
vaults |
path | unshackle/vaults |
✅ | Vault backend modules. |
fonts |
path | unshackle/fonts |
✅ | Bundled fonts. |
user_configs |
path | unshackle/ |
❌ protected | Where unshackle.yaml lives. |
data |
path | unshackle/ |
❌ protected | Base for the data subdirectories above. |
core_dir |
path | unshackle/core |
❌ protected | Package core. |
namespace_dir |
path | unshackle/ |
❌ protected | Package root. |
app_dirs |
- | AppDirs("unshackle", False) |
❌ protected | Internal AppDirs instance. |
The services directory is special
services may be a list, and each entry can be either a local directory or a
repository spec: a git URL (https://..., ssh://..., git@..., or anything ending in
.git) or owner/repo shorthand. Repo specs are cloned and updated automatically; plain
paths are used as-is. List order is priority: the first source to define a service
tag wins, so put your own local service directories last to make them fallbacks.
How git-backed service repos are handled
unshackle's use of git is read-only on the remote: it only ever performs a shallow
clone, fetch, pull, and a local reset; nothing is ever pushed. Private repos
are authenticated through your existing git credential helper, and unshackle stores no
tokens of its own. Clones live under <first-local-services-dir>/_repos/<repo-name>/ (or
the bundled unshackle/services), and nothing is written to the cache directory. After
the first clone unshackle re-pulls at most once every 24 h, so it does not touch the
network on every run.
The two refresh paths diverge on purpose
The automatic 24 h-TTL refresh that happens during a normal dl/search run will
deliberately refuse to refresh and exit (naming the offending clone) if that clone
has uncommitted changes to tracked files or unpushed local commits, so a background pull can
never clobber work in progress. Untracked files (new service folders, __pycache__) never
block it. The manual unshackle util refresh-services command does the opposite: it
hard-resets the clone to upstream, discarding any local edits. Reach for the manual
command only when you actually want to throw local changes away.
Read-only installs and reinstalls
If the services directory lives inside the installed package, a reinstall can delete the
_repos clones. They are simply re-cloned on next use. On read-only installs you must
point services at a writable path, or cloning will fail.
Filenames¶
The filenames key is a dict of templated file/name patterns. Each value is used
verbatim (no path processing). Braced fields like {time} and {service} are filled in at
runtime.
| Key | Type | Default | Notes |
|---|---|---|---|
log |
str | "unshackle_{name}_{time}.log" |
Written under directories.logs. |
debug_log |
str | "unshackle_debug_{service}_{time}.jsonl" |
Structured debug log, under directories.logs. |
config |
str | "config.yaml" |
Per-service config file, under that service's directory. |
root_config |
str | "unshackle.yaml" |
The main config filename itself. |
chapters |
str | "Chapters_{title}_{random}.txt" |
Under directories.temp. |
subtitle |
str | "Subtitle_{id}_{language}.srt" |
Under directories.temp. |
Services & authentication¶
services¶
- Type:
dictkeyed by service tag · Default:{}
Per-service configuration, keyed by the canonical service tag (for example EXAMPLE1, EXAMPLE2,
EXAMPLE3). unshackle reads several well-known sub-keys out of each service's block:
proxy_map: a dict mapping"provider:query"or"query"to a proxy value, used to auto-select a proxy for that service.title_map: an exact-match rename map applied to fetched titles (source name → desired name), so a service that names a title differently from your library still matches.
Individual services may read any additional keys they define. The whole block is handed to
the service as self.config.
Why put secrets here and not in the service's own config.yaml?
A service ships a per-service config.yaml inside its own folder, and that file is
typically shared/committed and not meant to be edited by you. So anything user- or
device-specific (API keys, account IDs, device attributes) belongs in this services
block of unshackle.yaml instead, where it is merged over the service's config.yaml
at load time. That is why the two config locations exist: one for shared service defaults,
one for your private overrides.
services:
EXAMPLE1:
proxy_map:
default: us
title_map:
"My Show: The Movie": "My Show Presents The Movie"
credentials¶
- Type:
dictkeyed by service tag · Default:{}
Per-service login credentials. Each value is a username:password[:extra] string (a list or
dict form is also accepted where a service supports it). These are parsed into Credential
objects; the credential's SHA-1 is also used as an account hash for cache keys.
credentials:
EXAMPLE2: user@example.com:hunter2
EXAMPLE1:
- primary@example.com:pw1
- secondary@example.com:pw2
Cookies vs credentials
Cookies are stored as files under directories.cookies, not in this key.
A service's authenticate() accepts cookies, credentials, or both.
firefox_cookies¶
- Type:
dict· Default:{}
Settings for extracting cookies directly from a local Firefox profile. A service block is
expected to provide hosts (a list of cookie hostnames; entries shorter than 3 characters
are ignored to prevent dumping the whole cookie store) and an optional local_storage
boolean to also pull matching local-storage entries. Extraction is read-only.
Firefox does not need to be closed
The extractor copies both cookies.sqlite and its WAL file into a 0700 temp
directory; the WAL copy exists to capture writes Firefox has not yet flushed to the main
DB, so the copy is current even against a running browser. The only real constraint is that
Firefox must not hold an exclusive write lock at the exact instant of the copy. The live
profile is otherwise left untouched.
When you actually need local_storage: true
Enable it only for services that keep their auth tokens in the browser's localStorage
(webappsstore.sqlite) rather than in HTTP cookies. If a service authenticates with plain
cookies, leaving it off is correct.
Extraction fails silently to file cookies
If extraction yields no cookies or fails for any reason, unshackle silently falls back
to the normal file-based cookie path (cookies/<SERVICE>.txt or
cookies/<SERVICE>/<profile>.txt). This is a safety net, but it also means that if you
expected Firefox cookies and file cookies are being used instead, extraction quietly failed.
Check that first when troubleshooting.
remote_services¶
- Type:
dict· Default:{}
Definitions of remote unshackle service servers, used by the --remote mode. Each entry
carries a services sub-dict of remote service tags, which unshackle turns into synthetic
CLI commands that run against the remote server.
serve¶
- Type:
dict· Default:{}
Configuration for the serve command (the built-in REST API server). The full server guide
is the REST API section; these are the config keys.
| Sub-key | Type | Default | Description |
|---|---|---|---|
api_secret |
str | (unset) | Master secret accepted in the X-Secret-Key header. |
users |
dict | {} |
Per-user API keys and their allowlists (see below). |
services |
list | (unset) | Global service allowlist. Omit to allow all. |
remote_only |
bool | false |
Run without a local output_template (CORS/Cloudflare-friendly). |
session_ttl |
int (s) | 300 |
Lifetime of an interactive auth session. |
max_sessions |
int | 100 |
Maximum concurrent sessions. |
history_limit |
int | 100 |
How many finished jobs to retain in history. |
compression_level |
int | 1 |
gzip level for responses. |
cdm_overrides |
dict | (unset) | Allowed per-request CDM overrides. |
allow_job_credentials |
bool | false |
Permit clients to supply credentials per job. |
devices |
list | (auto) | Widevine devices offered; auto-filled from directories.wvds. |
playready_devices |
list | (auto) | PlayReady devices; auto-filled from directories.prds. |
country / locations |
str / dict | (unset) | Advertised region metadata. |
Each entry under users is keyed by that user's API key and may set its own services,
devices, and playready_devices allowlists, narrowing the global ones.
serve:
api_secret: change-me
remote_only: true
services: [EXAMPLE1, EXAMPLE2]
# server-wide download defaults (same keys as the `dl:` block)
downloads: 3
best_available: true
users:
a1b2c3d4: # this user's API key
services: [EXAMPLE1] # may only use EXAMPLE1
Declaring dl defaults under serve
You can set any dl flag key (downloads, workers, best_available, and so on)
directly inside serve. The point is to raise concurrency and behaviour defaults
server-wide (every request the server handles picks them up) without having to change
each client call individually.
Download & processing¶
dl¶
- Type:
dict· Default:{}
Persistent defaults for the dl command. This block is used as Click's default_map, so
any dl flag can be given a default here and it applies to every download without you
typing it. See Downloading and the
CLI Reference for what each flag does.
Key naming. A key is the flag's long name with dashes replaced by underscores
(--best-available → best_available, --sub-format → sub_format). A few keys are named
after the flag's internal destination rather than its visible name; set these exact keys:
| Flag | Config key |
|---|---|
-r / --range |
range |
--list |
list |
--tmdb |
tmdb_id |
--imdb |
imdb_id |
--animeapi |
animeapi_id |
-naa / --noatmos |
no_atmos |
-o / --output |
output_dir |
--cdm-only / --vaults-only |
cdm_only |
Precedence (highest first): an explicit CLI flag or environment variable → a per-service
services.<TAG>.dl override → this global dl: block → the built-in
default. In other words, config only fills in options you did not set on the command line.
Common keys (a useful subset; every dl flag works):
| Key | Type | Default | Sets |
|---|---|---|---|
lang |
list | ["orig"] |
Video/audio language(s); orig = original language. |
a_lang / v_lang |
list | [] |
Audio- / video-only language override. |
s_lang |
list | ["all"] |
Subtitle language(s). |
quality |
list | [] (best) |
Resolution(s), e.g. [1080]. |
vcodec |
list | [] (any) |
Video codec(s), e.g. [H265]. |
acodec |
list | [] (any) |
Audio codec(s), e.g. [EC3]. |
range |
list | ["SDR"] |
Colour range(s): SDR, HDR10, HDR10+, DV. |
channels |
float | (unset) | Audio channels, e.g. 6 for 5.1. |
sub_format |
str | (unset) | Convert subtitles to this format (srt, vtt, original, ...). |
forced_subs |
bool | false |
Include forced subtitle tracks. |
no_subs / no_audio / no_chapters |
bool | false |
Skip that track type. |
downloads |
int | 1 |
Tracks downloaded concurrently. |
workers |
int | (downloader default) | Threads per track. |
slow |
str | (unset) | Inter-title delay, e.g. "20-40". |
best_available |
bool | false |
Fall back to best quality if the request is unavailable. |
proxy |
str | (unset) | Default proxy URI or 2-letter country. |
no_folder |
bool | false |
Do not create a per-title folder. |
dl:
lang: [en]
quality: [1080]
range: [HDR10]
vcodec: [H265]
sub_format: srt
downloads: 2
Per-service overrides
Nest a dl block under a service in services to override these defaults
for just that service. It uses the same keys and wins over the global dl: block (but still
loses to an explicit CLI flag).
serve uses these same keys
The serve block accepts the same dl option keys at its top level to set
server-wide download defaults for every request it handles.
subtitle¶
- Type:
dict· Default:{}
Subtitle handling: SDH stripping, format conversion, and whether subtitles are muxed in or written as sidecar files. See Subtitles for the full guide.
| Sub-key | Type | Default | Description |
|---|---|---|---|
strip_sdh |
bool | true |
Strip SDH/CC cues (hearing-impaired annotations) into a clean track. |
sdh_method |
str | "auto" |
SDH-stripping backend to use. |
preserve_formatting |
bool | true |
Keep styling/positioning when converting or stripping. |
convert_before_strip |
bool | true |
Convert to the working format before stripping SDH. |
conversion_method |
str | "auto" |
Subtitle conversion backend. |
preferred_conversion_method |
str | (unset) | Preferred converter when several are available. |
output_mode |
str | "mux" |
mux embeds subtitles in the MKV; sidecar writes separate files. |
sidecar_format |
str | "srt" |
Format for sidecar files when output_mode: sidecar. |
type_priority |
list | (unset) | Ordered ranking of subtitle types (forced, normal, sdh; CC counts as SDH) that sorts the variants within each language. Types you leave out fall to the end. |
type_priority also picks the default subtitle
type_priority sorts the tracks within each language, and at mux time the first track
in the default subtitle language gets the default flag. The built-in order is forced,
normal, sdh, which means a forced track becomes default whenever one exists. If you
want the full dialogue track as default instead, set:
audio¶
- Type:
dict· Default:{}
Audio handling options. The most notable sub-key is codec_priority, an ordered list of
audio codecs used to break ties in track selection.
codec_priority is a soft priority: nothing is dropped
codec_priority only breaks ties among tracks that share the same bitrate and language.
Codecs not in the list keep their normal bitrate-based ordering and are simply placed after
all listed codecs, and none are ever removed. Two higher-level rules still override it: Atmos
tracks always take precedence over codec_priority, and audio-description tracks are
always forced to the end regardless of it. When codec_priority is unset, tracks sort by
bitrate alone, with those same Atmos/descriptive rules still applied.
muxing¶
- Type:
dict· Default:{}
Matroska (MKV) muxing options.
| Sub-key | Type | Default | Description |
|---|---|---|---|
set_title |
bool | true |
Write a human-readable title into the MKV container. |
default_language |
dict | {} |
Force which language is flagged default per track type, e.g. {audio: en, subtitle: en}. |
merge_audio |
bool | true |
Merge all audio into one file. --split-audio on the CLI flips this off. |
merge_video |
bool | false |
Merge all video tracks into one file. --merge-video on the CLI flips this on. |
language_tags¶
- Type:
dict· Default:{}
Language-tag remapping and the rule engine behind the {lang_tag} filename variable. When a
rules list is present, each rule is evaluated in order and the first match's tag is used.
A rule may test:
| Condition | Meaning |
|---|---|
audio |
Matches against the audio track languages. |
subs_contain |
At least one subtitle language matches. |
subs_contain_all |
Every listed language must be present (scalar or list). |
language_tags:
rules:
- audio: [en]
subs_contain_all: [en]
tag: "" # native English release, no tag
- audio: [ja]
subs_contain: [en]
tag: "SUBBED"
Output & naming¶
For the full picture of how templates render, see Output & naming. The keys are summarised here.
output_template¶
- Type:
dict· Default:{}
Filename templates keyed by media kind, using {variable} placeholders. Recognised kinds are
movies, series, and songs (and albums for folder templates). A trailing ? on a
variable (e.g. {quality?}) marks it optional: if empty, it and one adjacent separator are
dropped.
output_template:
movies: "{title}.{year}.{quality?}.{source}-{tag}"
series: "{title}.{season_episode}.{episode_name?}.{quality?}.{source}-{tag}"
songs: "{track_number}. {title}"
folder: # special nested key, see below
movies: "{title} ({year})"
series: "{title} ({year})"
The nested folder sub-key controls output subfolders and is popped out of
output_template:
- A dict sets per-kind folder templates (kinds:
movies,series,songs,albums). - A str sets a single folder template for everything.
unshackle validates templates on load and emits warnings for unknown variables, non-string
values, filesystem-unsafe characters (< > : " / \ | ? *), empty templates, and unknown
folder-kind keys.
All valid template variables
title, year, season, episode, season_episode, episode_name, date,
quality, resolution, source, tag, track_number, artist, album_artist,
album, disc, track_total, disc_total, release_type, genre, explicit,
isrc, upc, label, audio, audio_channels, audio_full, atmos, dual,
multi, video, hdr, hfr, edition, repack, lang_tag.
Tagging & naming keys¶
| Key | Type | Default | Description |
|---|---|---|---|
tag |
str | "" |
Release-group tag appended to filenames and written into MKV tags. |
tag_group_name |
bool | true |
Include the group name (tag) in the MKV Group tag. |
tag_imdb_tmdb |
bool | true |
Write IMDb/TMDB/TVDB external-ID tags into the MKV (needs metadata providers). |
chapter_fallback_name |
str | "" |
Fallback chapter-name template, e.g. "Chapter {i:02}". |
unicode_filenames |
bool | false |
Allow Unicode in filenames; when false, names are ASCII-sanitised. |
DRM & CDM¶
The full conceptual guide lives at DRM & CDM. These are the config
keys. Device files themselves live in directories.wvds (.wvd) and
directories.prds (.prd).
cdm¶
- Type:
dict· Default:{}
Maps a service tag to the CDM (Widevine/PlayReady device) to use, with a default fallback.
Resolution is case-insensitive: an explicit CLI override wins, then the per-service entry,
then default.
A value may also be a nested dict for advanced selection by quality, DRM system, or profile:
cdm:
EXAMPLE1:
">=1080": android_l1 # by track height
"<1080": chromecdm_l3
widevine: android_l1 # by DRM system
playready: sl3000
default: chromecdm_l3
Quality keys support 1080, >=1080, >720, <=576, <480 style comparisons; DRM keys
are widevine / playready.
remote_cdm¶
- Type:
list[dict]· Default:[]
A list of remote CDM server definitions. Each entry is matched by its name (referenced from
cdm), and its type selects the backend:
type |
Backend | Key fields |
|---|---|---|
decrypt_labs |
Decrypt Labs KeyXtractor | host (default https://keyxtractor.decryptlabs.com), device_name, secret (falls back to decrypt_labs_api_key) |
custom_api |
Fully YAML-configurable remote API | device, auth, endpoints, request_mapping, response_mapping, caching, timeout |
(none), Device Type: PLAYREADY |
pyplayready RemoteCdm |
host, secret, device_name, security_level (default 3000) |
| (none), otherwise | pywidevine RemoteCdm |
host, secret, device_name, device_type, system_id, security_level (default 3000) |
remote_cdm:
- name: keyxtractor
type: decrypt_labs
device_name: L1
- name: my_wv_server
host: https://cdm.example.com
secret: s3cr3t
device_name: android_l1
device_type: ANDROID
system_id: 26830
security_level: 1
decryption¶
- Type:
strordict· Default:"shaka"
Selects the tool used to physically decrypt CENC content. The value is compared
case-insensitively; "mp4decrypt" selects Bento4's mp4decrypt, and anything else
(including "shaka") selects shaka-packager.
Network & proxy¶
network¶
- Type:
dict· Default:{}
TLS-fingerprinting and HTTP client settings for the rnet-based session.
| Sub-key | Type | Default | Description |
|---|---|---|---|
browser |
str | "Chrome131" |
Impersonation preset. Must be an exact preset name (e.g. Chrome131, Firefox135, Edge101, Safari18, OkHttp4_12, OkHttp5, Opera118); unknown names raise an error. |
http1_only |
bool | (unset) | Force HTTP/1.1. |
http2_only |
bool | (unset) | Force HTTP/2. |
pool_max_idle_per_host |
int | (unset) | Connection-pool tuning. |
pool_max_size |
int | (unset) | Connection-pool tuning. |
tcp_nodelay |
bool | (unset) | Disable Nagle's algorithm. |
When to force http1_only
HTTP/2 is the right default on large CDNs, so leave it on. Force HTTP/1.1 only for a host that throttles per-connection, or a slow origin stuck behind HTTP/2 flow control. In benchmarks this won 30 to 50% on such hosts but cost up to 27% on fast CDNs, so enable it only when your own measurements justify it.
Renamed from curl_impersonate
The old curl_impersonate section is a deprecated alias. If you still use it, unshackle
honours it (only when network is absent) but emits a DeprecationWarning. Rename it to
network.
headers¶
- Type:
dict· Default:{}
Default HTTP headers merged into every session unshackle creates.
Don't set Accept-Encoding (and similar) here
Keep this block to sane cross-service defaults such as Accept-Language and User-Agent.
Compatibility headers like Accept-Encoding are set for you by the rnet HTTP backend as
part of its browser-impersonation profile, and overriding them can break the impersonation
fingerprint. Anything useful to only some services belongs in that service's config, not
here.
proxy_providers¶
- Type:
dict· Default:{}
Proxy/VPN provider configuration. Each sub-key names a provider, and its block is passed straight to that provider's constructor. See Proxies & VPN for the full provider guide. Recognised providers and their exit ports:
| Provider | Config key | Credentials | Proxy scheme/port |
|---|---|---|---|
| Basic (static) | basic |
country → URI(s) | as specified |
| NordVPN | nordvpn |
48-char service credentials | https://...:89 |
| Surfshark | surfsharkvpn |
48-char service credentials | https://...:443 |
| Windscribe | windscribevpn |
service credentials | https://...:443 |
| ExpressVPN | expressvpn |
device login (enable: true) / token cache |
https://cat:...@...:443 |
| ProtonVPN | protonvpn |
TV login or exported cookies | https://...:4443 (or :443 Secure Core) |
| Gluetun | gluetun |
per-VPN keys/creds | http://localhost:{port} (local Docker) |
| Hola | (none, auto) | none | http://...:{peer} |
proxy_providers:
basic:
us: http://user:pass@1.2.3.4:8080
de:
- http://a.example:8080
- socks5://b.example:1080
nordvpn:
username: <48-char service username>
password: <48-char service password>
Provider loading differs between CLI and REST server
The dl CLI loads all providers, including windscribevpn and gluetun. The REST API /
remote-client path uses a separate resolver that does not load windscribevpn or
gluetun. ExpressVPN and ProtonVPN also auto-load when their cached session exists, and Hola
auto-loads whenever the hola-proxy binary is present.
Key vaults¶
The full guide is at Vaults. Two keys configure them.
key_vaults¶
- Type:
list[dict]· Default:[]
An ordered list of key-vault backends. unshackle queries them in order and reuses content
keys instead of re-licensing. Each entry needs a type (the backend module name) and a
name, plus backend-specific keys.
type |
Purpose | Required keys | Notes |
|---|---|---|---|
SQLite |
Local SQLite database | name, path |
Loaded critically; a failure aborts the run. |
MySQL |
Remote MySQL database | name, host, database, username |
Extra keys (e.g. password, port) forwarded to pymysql. |
API |
RESTful JSON API | name, uri, token |
Honours vault_timeout. |
HTTP |
HTTP API with modes | name, host, one of password/api_key |
api_mode: query (default), json, decrypt_labs. |
key_vaults:
- type: SQLite
name: Local
path: ~/.unshackle/keys.db
- type: MySQL
name: Team
host: db.example.com
database: keys
username: unshackle
password: hunter2
no_push: false
Common per-entry options
no_push: truemakes a vault read-only (keys are fetched but never written to it).- A vault of
type: apiwhosenamecontainsdecrypt_labsauto-fills itstokenfromdecrypt_labs_api_keywhen not set inline. - An all-zero content key (32 zeros) is treated as "no key" everywhere and is never stored.
vault_timeout¶
- Type:
float· Default:10.0
Timeout in seconds for vault operations. Injected automatically into any backend whose
constructor accepts a timeout parameter (a per-vault timeout still wins).
External API keys¶
All default to an empty string and enable optional metadata/geolocation features.
| Key | Type | Default | Description |
|---|---|---|---|
imdb_api_enabled |
bool | false |
Use the free IMDxAPI (api.tiffara.com) metadata provider, which needs no API key. Off by default since the site has been unreliable. |
omdb_api_key |
str | "" |
OMDb API key for IMDb metadata lookups; a more reliable alternative to IMDxAPI. Free keys are available on the OMDb site. |
tmdb_api_key |
str | "" |
TMDB API key for metadata enrichment and external-ID tags. |
simkl_client_id |
str | "" |
SIMKL client ID for metadata lookups; an alternative/fallback source to TMDB. |
decrypt_labs_api_key |
str | "" |
Global Decrypt Labs API key (used by remote CDM / vault). |
ipinfo_api_key |
str | "" |
ipinfo.io API key for IP/region lookups. |
SIMKL is a fallback for TMDB
SIMKL is positioned as an alternative metadata source, and it is most valuable precisely
when no tmdb_api_key is configured. It improves title-matching and tagging
reliability in that case rather than replacing TMDB where TMDB is available.
ipinfo_api_key never touches your service sessions
The token is only ever sent to api.ipinfo.io as a per-request Authorization header; it
is never attached to your service session, so it cannot leak to a streaming provider.
Lookups degrade gracefully through a fallback chain: the authenticated Lite endpoint (higher
rate limits and richer fields) → anonymous ipinfo → ip-api.in as a last resort.
Caching & updates¶
Title cache¶
unshackle caches fetched title metadata (region- and account-aware) to avoid repeat API calls.
| Key | Type | Default | Description |
|---|---|---|---|
title_cache_enabled |
bool | true |
Master switch for the title cache. |
title_cache_time |
int (seconds) | 1800 (30 min) |
Lifetime of fresh cached titles. |
title_cache_max_retention |
int (seconds) | 86400 (24 h) |
How long stale cached data may still be served as a fallback when a live fetch fails. |
Update checks¶
| Key | Type | Default | Description |
|---|---|---|---|
update_checks |
bool | true |
Whether to check for new unshackle releases. |
update_check_interval |
int (hours) | 24 |
Minimum hours between update checks. |
Note
Update checks query the GitHub releases API with a fixed 5-second timeout and cache the
result in directories.cache/update_check.json.
Logging, privacy & debug¶
| Key | Type | Default | Description |
|---|---|---|---|
redact_paths |
bool | true |
Mask install-root / venv / home prefixes in logged paths (<unshackle>, <venv>, ~). Set false to show full paths. |
debug |
bool | false |
Global debug mode. |
debug_keys |
bool | false |
Log decryption keys. |
debug_requests |
bool | false |
Log HTTP requests. |
set_terminal_bg |
bool | false |
Append the theme's background colour to output styling. |
When to enable set_terminal_bg
When on, the theme's background colour is appended to the foreground styles so the full colour palette renders correctly on terminals whose default background differs from unshackle's theme. Turn it on if the ASCII-art banner or coloured output looks off on your terminal. Otherwise leave it off.
Key exposure
Decryption content keys are written to the structured debug log at INFO level during the
DRM handshake. Treat debug, debug_keys, and the unshackle_debug_*.jsonl files as
sensitive, and keep redact_paths enabled when sharing logs.
debug_keys affects only content-encryption keys (the content_key/key fields).
Passwords, tokens, cookies, and session tokens are always redacted regardless of this
setting, and KIDs, key counts, and other metadata are always logged either way.
Deprecated & removed keys¶
| Key | Status | Behaviour |
|---|---|---|
curl_impersonate |
Deprecated → use network |
Emits a DeprecationWarning; still honoured only if network is absent. |
downloader |
Deprecated | Any value other than "requests" emits a DeprecationWarning; the value is otherwise ignored (the unified requests downloader is always used). |
scene_naming |
Removed | If present at all, unshackle exits with an error directing you to configure output_template instead. |
A complete annotated example¶
The following config sets a handful of common keys. Every key not shown keeps its default.
directories:
downloads: ~/Media/unshackle
temp: /mnt/scratch/unshackle
network:
browser: Chrome131
headers:
Accept-Language: en-US,en;q=0.9
cdm:
default: chromecdm_l3
EXAMPLE2: android_l1
decryption: shaka
credentials:
EXAMPLE2: user@example.com:hunter2
output_template:
movies: "{title}.{year}.{quality?}.{source}-{tag}"
series: "{title}.{season_episode}.{quality?}.{source}-{tag}"
folder:
movies: "{title} ({year})"
series: "{title} ({year})"
tag: MYGRP
key_vaults:
- type: SQLite
name: Local
path: ~/.unshackle/keys.db
proxy_providers:
basic:
us: http://user:pass@1.2.3.4:8080
omdb_api_key: "your-omdb-key"
tmdb_api_key: "your-tmdb-key"
title_cache_time: 3600
redact_paths: true
For how these interact at runtime, continue to Downloading, DRM & CDM, and Output & naming.