Files
magnus919_agent-skills/jellyfin/references/user-scoping-and-errors.md
Magnus Hedemarkandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> 2140d0d58d docs(jellyfin): thicken media-server skill against current API research
Full lastfm-model rebuild of the jellyfin skill against the 12.0-era
OpenAPI spec, core-dev authorization guidance, and server source:

- Document the researched auth sequence end to end: complete pre-token
  Authorization: MediaBrowser Client/Device/DeviceId/Version header
  required by POST /Users/AuthenticateByName (400 "Error processing
  request." without it), AccessToken returned, then Token= on the same
  header (legacy X-Emby-Token deprecated, disableable since 10.11,
  targeted for removal at 12.0).
- Extend scripts/jellyfin: new `login` subcommand demonstrating the
  pre-token header and printing session exports (password via
  stdin/prompt/env only), seasons/episodes TV navigation, next-up
  --series-id, browse --user-id (userId is required on non-API-key
  auth per the ItemsController guard), modern Token= header transport
  with X-Emby-Token fallback, 503 Retry-After handling, search
  Id/deprecated-ItemId fallback.
- Add 5 cited reference files (auth/sessions, endpoint catalog,
  user-scoping matrix, gotchas field guide, worked recipes) plus
  quick-connect; all cite api.jellyfin.org and live-verified sources.
- Upgrade relocated scripts/test_jellyfin_cli.py to the double-runner
  standard: 24 tests (was 8) covering help, argument errors, dry-run,
  mocked login header sequence, TV navigation, search-id fallback, and
  jq-executed pipeline-consumability chains; zero egress proven via
  proxy-trap rerun.
- Add evals/evals.json (6 cases incl. emby-install-not-for-jellyfin
  negative probe); rewrite SKILL.md (224 lines) and README; sync root
  README blurb and skill-triggers row; regenerate marketplace.json and
  llms.txt (description-embedding artifacts).

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-08-29 17:37:35 -04:00

5.4 KiB

Jellyfin user scoping — the userId matrix

The single most common Jellyfin integration failure after authentication: a request that authenticates fine but 404s or 400s because which user the query is about was never stated. Jellyfin's data model is per-user at the library level; most read endpoints need you to say whose library you are looking at.

Why user id is everywhere

A Jellyfin library is not one global catalog. Views, playstate, favorites, resume points, and parental-control visibility all attach to a user. The authentication layer may or may not imply a user:

  • User access token (from AuthenticateByName): implies one user. Recent servers fall back to that user when userId is omitted on some endpoints.
  • API key (from Dashboard → API Keys): implies NO user. AuthorizationInfo.User stays null and the request gets administrator role. Every user-scoped concept must be named explicitly with a userId parameter — including /UserViews, which is meaningless without one.

The bundled CLI always sends userId explicitly on user-scoped commands regardless of which credential type it holds. That is the cross-version-safe baseline.

The userId requirement matrix

Endpoint User token, userId omitted API key
GET /Items 400 — body userId is required (servers enforce if (!isApiKey && user is null) return BadRequest("userId is required")) Optional — omitted means an unrestricted, userless view; pass it anyway for UserData in DTOs
GET /Items/{itemId} Defaults to the token's user; userId supplies whose UserData embeds Pass explicitly for user data
GET /UserViews Parameter accepted; pass it REQUIRED to be meaningful
GET /Shows/{seriesId}/Seasons / Episodes ≥10.9 falls back to token user; ≤10.8 crashes on omission — always pass REQUIRED
GET /Shows/NextUp Same version split as above — always pass REQUIRED
GET /Items/Latest userId scopes "recently added for whom"; always pass REQUIRED for meaningful results
GET /Search/Hints Optional — "omit to search all" Optional
GET /Users/Me Works (returns token's user) 400 Token is not owned by a user.
GET /System/Info, /System/Info/Public, /Users No user concept Fine

Note the deliberate trap in /Users/Me: it is the natural "who am I" endpoint for a user token and returns exactly the id you need — but with an API key it is a 400, by design, because an API key is nobody.

400 vs 404: absent vs invalid userId on /Items

Two distinct failure signatures, enforced in this order by the items controller (verified at master and v10.10.7):

  1. The user lookup runs first: a supplied-but-nonexistent userId throws ResourceNotFoundException → mapped to 404 (Error processing request. body).
  2. Then the guard: an ABSENT userId on non-API-key auth returns 400 with the literal string body userId is required (not the middleware's generic text).

So: 400 userId is required = you forgot the parameter; 404 = the parameter names a user that does not exist. Mock both distinctly.

Finding a user id without logging in as one

  1. GET /Users (any valid token): array of UserDto with Name and Id. The administrator-flavored listing — API keys see everyone.
  2. GET /Users/Public (no auth): only users flagged visible on login screens.
  3. After AuthenticateByName: the response's User.Id is the documented primary.
  4. With a user token: GET /Users/Me.
# Discover user ids with an API key
curl -s -H 'Authorization: MediaBrowser Token="YOUR_API_KEY"' \
  "http://localhost:8096/Users" | jq -r '.[] | [.Name, .Id] | @tsv'
# → alice    6eec632a-ff0d-4d09-aad0-bf9e90b14bc6

Scoping errors look like 404s

The confusion this reference exists for: GET /Items (or /Shows/NextUp) called without a userId under a context where one is required does not answer "you forgot the user" on every endpoint and version — older servers crash (NextUp ≤10.8: 500 from an empty-Guid ArgumentException), and user-token fallbacks silently change results. Symptoms cluster as "endpoint exists but returns 400/404/empty" even though the token is perfectly valid. The fix is uniform: resolve the user id once, pass it explicitly on every user-scoped call.

The bundled CLI mirrors that baseline: recent, next-up, and item require JELLYFIN_USER_ID or --user-id before any network call, refuse to guess an administrator, and dry-run previews show the userId that would have been sent.

Sources