Assignments and routing defaults
HTTP routes and behavior for assignments and routing defaults.
GET /api/config advertises instance_assignments:true. Regular users see only explicit grants for Radarr, Sonarr, Chaptarr, and Lidarr. An explicit authorized instance wins; otherwise routing chooses an authorized personal preference, an accessible global default, or the first assigned instance ordered by sort_order, name, id. Administrators retain all-instance access. Each config instance also reports assigned for an explicit personal grant and request_default for the effective default within that assigned set. The app uses these fields for personal request choices without limiting administrator navigation. Defaults do not grant access. Missing or unavailable explicit destinations never silently fall back.
Automation instance create/update accepts auto_add_users. Omitted creates default true; omitted updates preserve the stored value. Other services reject true. The setting grants this instance once, in the transaction that creates a regular account through invitation, import, OIDC, or Plex. Post-commit observers run only after success. Reissuing invitations, linking identities, and repeat sign-ins do not regrant access. It does not modify existing accounts.
GET /api/instances/{instanceID}/assignments returns {user_id, assigned, preferred_instance_id, effective_default_id} rows. PATCH accepts {"action":"add","user_ids":[2,3]} or remove. Both require instances:manage. All selected users must exist and be regular or administrator accounts; unknown users, unsupported instances, and empty selections return 400. Administrator grants select personal request destinations without changing their management access. effective_default_id resolves only among actual assignments, including for administrators. Only selected pairs change, atomically; duplicate IDs are harmless and retries are idempotent. Removal clears preferences naming that instance. The legacy /users and /default-instances endpoints change preferences only and reject unassigned targets.
A one-time transactional migration records old Radarr/Sonarr inherited access and valid personal pins as grants, preserves other grants, records the old implicit video defaults, and binds resolvable legacy pending requests. Existing instances start with auto_add_users=false. A settings marker prevents restarts from replaying assignments. Pending requests keep their saved destination and require current requester access again at approval and dispatch; unresolved legacy destinations stop for administrator attention.
Radarr, Sonarr, Chaptarr, and Lidarr create/update payloads may include media_path_mappings, an ordered array of { "arr_path": "...", "cantinarr_path": "..." } objects. Each row translates an absolute path prefix reported by that one instance into a server-visible directory beneath CANTINARR_MEDIA_ROOTS; source prefixes may use POSIX, Windows drive, or UNC syntax independently of the server OS. An empty array disables completed-media downloads for that instance. The admin-only media-roots endpoint supplies the approved target roots used by the editor. Mapping paths are admin-only instance configuration and never appear in the requester /api/config payload.
Jellyfin, Emby, Plex, and Audiobookshelf create/update payloads may include media_server_config: { "public_address": "https://jellyfin.example.com", "library_ids": [], "machine_identifier": "...", "auto_approve": false }. public_address is the client-reachable address shown to granted users so they know where to sign in (absolute http/https, no credentials, query, or fragment; empty = unset, except on Plex where it defaults to https://app.plex.tv): the only instance field a requester ever receives, and only because an admin typed it. library_ids are the server’s own library identifiers (Jellyfin’s VirtualFolders.ItemId, Emby’s folder Guid, Plex’s plex.tv-global section ids) the accounts Cantinarr creates may see; an empty list shares every library, including ones added later. machine_identifier (Plex, required) names which of the linked account’s owned servers the instance shares, and auto_approve (Plex) grants the instance to anyone who shares a Plex email and invites them at once. Omitting the field keeps the stored config, the admin list echoes it, and /api/config never carries it. plex_owner_id, plex_owner_username, and plex_owner_email are server-managed like the client identifier: recorded by the PIN link, backfilled from the stored token by the first Plex sign-in when an older instance lacks them, and never served; they are what lets the provider answer for the owner as an administrator account.
A Plex instance has no URL or API key to type. POST /api/instances/plex/link/begin mints a plex.tv PIN ({ pin_id, code, url }), the admin approves it in the browser, and POST /api/instances/plex/link/check { pin_id } answers { linked, account } once they have; the token the approval yields is held server-side for fifteen minutes and never reaches the app. The instance’s create/update/test/libraries bodies then carry plex_link_pin instead of api_key, and POST /api/instances/plex/servers (same candidate body: a pin, or an id whose stored token is used) lists the account’s owned servers for the picker. The stored url is plex.tv itself (a lab may point it at a proxy); the X-Plex-Client-Identifier the token was minted under is kept with the instance.
The proxy is also a transport trust boundary. CONNECT, TRACE, and TRACK are rejected before instance resolution or any upstream contact, and an upstream protocol upgrade (101) is refused, so the HTTP proxy can never become an opaque tunnel or reflect the injected X-Api-Key. Inbound Cantinarr session cookies and every forwarded credential, client-identity assertion (reverse-proxy X-Auth-*/Remote-*/mTLS headers), routing/method-override, and request-trailer header terminate here; only the instance’s own X-Api-Key is added outbound. Upstream responses are marked private and non-cacheable, and nginx/lighttpd internal-redirect controls (X-Accel-*, X-Sendfile, X-Reproxy-URL) plus response trailers are stripped so a fronting web server cannot be steered by an upstream header.