Skip to content

Configuration

The API reads its configuration from environment variables (prefix APP_), the worker from SNATCH_*. Everything else is stored in Postgres and edited through the API or the UI.

API (snatcharr)

Variable Default Meaning
APP_DB_DSN required Postgres DSN, e.g. postgres://snatcharr:secret@postgres:5432/snatcharr?sslmode=disable
APP_HTTP_ADDR :8080 HTTP listen address (API, SSE, embedded UI)
APP_GRPC_LISTEN :9090 gRPC listen address for workers
APP_GRPC_KEEPALIVE_MAX_CONNECTION_AGE 2m After this age a worker connection takes no new RPCs and new ones move to a fresh connection; negative = never
APP_GRPC_KEEPALIVE_MAX_CONNECTION_AGE_GRACE unlimited How long RPCs already running on an aged connection may finish. A finite value must be at least 35s (the 30 s LeaseRun long-poll plus margin) or the API refuses to start; note that a finite value can still cut a run's ReportEvents stream
APP_CRYPTO_KEY required in prod Key that encrypts *arr API keys and download-client secrets at rest (openssl rand -hex 32). The dev profile without it runs on a development key published in the source and warns that stored secrets are unprotected; prod refuses to start on that key
APP_HTTP_CSRF_SECRET not read No longer used: the API refuses cross-site requests from the browser's Sec-Fetch-Site and Origin headers, which needs no secret and cannot be switched off. A value that is still set is ignored
APP_LEADER_ENABLED false Elect one replica (Postgres advisory lock) to run the planner, Seerr sync and Configarr poll; leave off for a single replica
APP_LEADER_NAME snatcharr Lock name; one per deployment
APP_LEADER_IDENTITY hostname This replica's name in logs
APP_LEADER_PG_RETRY 2s How often a follower retries the lock
APP_SNATCHARR_PROFILE prod prod or dev; dev relaxes cookie security and uses the built-in dev key
APP_SNATCHARR_PUBLIC_URL External base URL (cookies, links)
APP_SNATCHARR_WORKER_TOKEN Shared bearer the worker presents over gRPC
APP_SNATCHARR_SESSION_TTL 8h Cookie session lifetime
APP_SNATCHARR_SESSION_SECURE true Secure flag on the session cookie; while it is on, responses also carry Strict-Transport-Security (one year), so leave it on only when browsers reach SnatchArr over HTTPS
APP_SNATCHARR_APIKEY_SECRET required in prod HMAC secret for API keys
APP_SNATCHARR_CONFIGARR_CONFIG Path to Configarr config.yml (linked mode)
APP_SNATCHARR_CONFIGARR_DOWNLOAD_CLIENTS Path to download clients YAML file (linked mode)
APP_SNATCHARR_CONFIGARR_SECRETS Path to Configarr secrets.yml
APP_SNATCHARR_CONFIGARR_WATCH true Re-import on file change
APP_SNATCHARR_PROWLARR_LIMIT_THRESHOLD 0.9 Fraction (0, 1] of an indexer's Prowlarr query limit at which SnatchArr treats the indexer as exhausted (Prowlarr)
APP_SNATCHARR_LOGS_RING_SIZE 2000 Log records the API keeps in memory for the admin Logs page; clamped to 100 to 20000
APP_SNATCHARR_WEB_DEV false Disable the embedded SPA (Vite dev server proxies instead)
APP_SNATCHARR_PROXY_TRUSTED Comma-separated CIDRs of trusted reverse proxies (e.g. 10.0.0.0/8). Enables client IP resolution from X-Forwarded-For for login throttling
APP_SNATCHARR_SETUP_SECRET Optional bootstrap secret required by POST /auth/setup. Generated by default in Helm chart; empty allows setup without a secret until the first admin user is created
APP_SNATCHARR_OIDC_ENABLED false Enable OIDC SSO (Single Sign-On)
APP_SNATCHARR_OIDC_ISSUER_URL OIDC issuer URL (https, or http in dev profile)
APP_SNATCHARR_OIDC_CLIENT_ID OAuth2 client ID
APP_SNATCHARR_OIDC_CLIENT_SECRET OAuth2 client secret
APP_SNATCHARR_OIDC_REDIRECT_URL OAuth2 callback URL (defaults to public.url + /api/v1/auth/oidc/callback)
APP_SNATCHARR_OIDC_SCOPES openid,email,profile Comma-separated scopes (openid always present)
APP_SNATCHARR_OIDC_ALLOWED_SUBJECTS Comma-separated allowed IdP sub claims
APP_SNATCHARR_OIDC_ALLOWED_EMAILS Comma-separated allowed emails (email_verified==true required)
APP_SNATCHARR_OIDC_DISCOVERY_TIMEOUT 10s IdP discovery timeout
APP_SNATCHARR_PROXY_AUTH_ENABLED false Enable trusted proxy-header auth (Single Sign-On)
APP_SNATCHARR_PROXY_AUTH_HEADER Remote-User Name of header containing authenticated username
APP_SNATCHARR_PROXY_AUTH_TRUSTED_CIDRS Comma-separated CIDRs of reverse proxies trusted for proxy-header auth
APP_SNATCHARR_PROXY_AUTH_ALLOWED_USERS Comma-separated allowed usernames
APP_OTEL_ENABLED true OpenTelemetry export (no-op without an endpoint)
APP_LOG_FORMAT json json or text

Worker (snatch-worker)

Variable Default Meaning
SNATCH_API_GRPC http://snatcharr:9090 API gRPC endpoint
SNATCH_WORKER_TOKEN Bearer token (must match APP_SNATCHARR_WORKER_TOKEN)
SNATCH_CONCURRENCY 2 Snatches executed in parallel
RUST_LOG info Log filter

The worker never holds state. It leases a snatch, heartbeats while working, reports events and completes; a restart loses nothing.

Snatch policy

Every instance has one policy (GET/PUT /api/v1/instances/{id}/policy).

Field Default Range
missing_per_cycle 1 0 to 100 (0 disables)
upgrade_per_cycle 0 0 to 100
cycle_interval_s 900 at least 60
hourly_cap (stamina) 20 1 to 500
selection random random, sequential, recent
monitored_only true
skip_future_releases true
radarr_release_type physical physical, digital, cinema
sonarr_missing_mode episodes episodes, season_packs, shows
sonarr_upgrade_mode episodes episodes, season_packs
lidarr_missing_mode artist artist, album
processed_ttl_h (afterglow) 168 1 to 8760
max_queue_size -1 -1 disables the *arr queue ceiling
await_command false Poll the search command until it completes (5 min max)
page_size 100 10 to 1000
recent_grab_window_h 24 0 to 720; items grabbed this recently (and everything queued) are skipped
afterglow_max_h 720 1 to 8760; cap for the doubling afterglow
include_tags empty up to 100 *arr tag ids; when not empty, only items with one of these tags
exclude_tags empty up to 100 *arr tag ids; items with any of these tags are skipped; an id may not be in both tag lists
include_quality_profiles empty up to 100 *arr quality profile ids; when not empty, only items using one of these profiles
exclude_quality_profiles empty up to 100 *arr quality profile ids; items using any of these are skipped; an id may not be in both profile lists
release_delay_h 0 0 to 8760; skip items released less than this many hours ago (0 = off; items without a date are kept)
dry_run false When true, scheduled and periodic runs only preview without dispatching searches
skip_unavailable_movies true Radarr only: skip movies whose minimum availability is not reached (isAvailable false); they cost no stamina, and the run's candidates_filtered event counts them. Movies without the flag are searched

An episode, album or book has the tags and quality profile of its series, artist or author. GET /api/v1/instances/{id}/policy/filter-options lists the tags and quality profiles the instance offers (admins only); the policy form picks from that list.

Queue policy

Each instance has a queue policy (GET/PUT /api/v1/instances/{id}/queue-policy; PUT is admin-only). When enabled is true, the elected replica sweeps the instance's *arr download queue and counts consecutive strikes per download. GET /api/v1/instances/{id}/queue-strikes lists the downloads that hold a strike now; downloads the last sweep found clean are left out. The rules and their order are described in ADR-0012.

Every rule starts in observe mode: a strike is recorded and shown, and the queue is left alone. A rule in act mode removes the download once its strike count reaches the threshold; every rule except unwanted_unmonitored also blocklists the release so the *arr searches again. Whisparr has no queue API in goenvoy and is skipped.

Rules

Rule Matches when Default threshold
stalled Downloading, and the remaining size did not shrink since the last sweep 6
slow Downloading slower than min_bytes_per_second since the last sweep 6
failed Queue status or tracked state is failed 2
import_blocked The *arr cannot import the finished download 6
unwanted_score Custom format score below min_custom_format_score 1
unwanted_unmonitored The episode, movie, album or book is no longer monitored 1

Queued, paused and delayed downloads, downloads being imported, and downloads younger than the grace period are never evaluated.

Policy fields

Field Default Range Meaning
enabled false bool Sweep this instance's queue
grace_seconds 900 0 to 86400 Minimum age before any rule looks at a download (the UI edits it in minutes)
min_bytes_per_second 0 >= 0 Floor for slow; 0 turns slow off
min_custom_format_score unset integer Floor for unwanted_score; unset turns unwanted_score off
max_removals_per_sweep 10 1 to 100 Most downloads one sweep may remove

Runtime settings

GET/PUT /api/v1/settings:

Field Default Meaning
history_retention_days 90 1 to 3650; how long events and finished runs are kept. The elected replica deletes older ones once an hour
user_agent SnatchArr/1.0 (...) Sent to every *arr and download client
global_hourly_cap 0 Stamina shared by every instance (0 = off)

The same hourly pass also forgets afterglow entries 90 days after they expired, spent stamina buckets after two hours, expired login sessions and workers that have not called for history_retention_days. It deletes at most 100,000 rows per table per pass and logs one retention: purge pass line with the counts. A larger backlog shrinks over the following hours.

Configarr

Point APP_SNATCHARR_CONFIGARR_CONFIG (and _SECRETS) at your Configarr files and every sonarr, radarr, lidarr, readarr (legacy) and whisparr instance is imported on start and kept in sync: the files are polled every 30 seconds (inotify misses Kubernetes ConfigMap swaps, polling does not). Only base_url, api_key and the enabled flags are read; !secret, !env and !file values are resolved like Configarr does. Whisparr entries are probed to tell v2 from v3. An instance that disappears from the file is switched off, not deleted. Imported instances are read-only in SnatchArr except for their snatch policy. Deleting one that the file still declares, enabled or not, is refused with 403; once it is gone from the file and switched off, it can be deleted.

POST /api/v1/configarr/import runs a one-shot import (with config_path and secrets_path in the body, or the linked files); GET /api/v1/configarr/status shows linked mode and the last result.

Declarative Download Clients

When APP_SNATCHARR_CONFIGARR_DOWNLOAD_CLIENTS points to a YAML file, download clients are declaratively provisioned and kept in sync alongside instances:

qbittorrent:            # section = domain.DownloadClientKind value; accept every kind ParseDownloadClientKind accepts
  main:                 # entry name -> key "qbittorrent/main", Name "main"
    base_url: http://qbittorrent.media.svc:8080
    username: !env QBIT_USER
    password: !file /etc/snatcharr/configarr-secrets/qbit-password
    instance: sonarr/main   # optional Configarr instance key; omitted = applies to every instance
    max_active: 0           # optional int
    bandwidth_budget_bps: 0 # optional int64
    enabled: true           # optional, default true
sabnzbd:
  main:
    base_url: http://sabnzbd.media.svc:8080
    api_key: !secret sabnzbd_api_key   # secret = api_key or password, exactly one, both -> parse error

!secret (resolved from APP_SNATCHARR_CONFIGARR_SECRETS), !env and !file tags work identically to instance secrets. When an entry disappears from the file, it is pruned. Provisioned clients are marked with source configarr; mutating or deleting them via the UI or API is rejected with 403 Forbidden.