Skip to content

Jellyfin Enhanced's API

Jellyfin Enhanced adds features like Bookmarks and User Reviews/Ratings that live entirely in this plugin, not in Jellyfin Server itself. A user of any other Jellyfin client (Wholphin, Streamyfin, Afinity, a custom app, etc.) only gets these features if that client talks to the endpoints on this page directly, over plain HTTP with a Jellyfin token or API key. No client-side plugin, SDK, or Jellyfin Enhanced code is required, just an HTTP client and the request shapes documented below.

Requirements before any of this works:

  • The Jellyfin Enhanced plugin must be installed and enabled on the target server
  • Your app needs a Jellyfin auth token (per-user, from that user logging in) or a server API key, exactly as it would for any other Jellyfin Server request. See Quick Start below for how to get one and which calls need which
  • Bookmarks and Reviews both use TMDB IDs, not Jellyfin item IDs, to key data server-side (bookmarks also take an itemId when adding one, see Bookmarks API endpoints)

What this lets a client build, using only these HTTP endpoints:

  • A "bookmark this moment" button on the video player, and a bookmarks list/library screen (see Bookmarks API)
  • Star ratings and written reviews on a movie/show/season/episode details page, shared across every user on the server (see Reviews API)

Jellyfin 10.11 vs 12: the auth header that works is not the same

This plugin builds one identical set of endpoints for both Jellyfin 10.11 and 12, so the request/response shapes on this page don't change between server versions, only which auth header Jellyfin Server itself accepts does. Confirmed live against both a 10.11.11 and a 12.0.0 server:

  • X-Emby-Token and X-Emby-Authorization work on Jellyfin 10.11
  • On Jellyfin 12, both of the above are rejected by default (401/400) — Jellyfin 12 ships with ServerConfiguration.EnableLegacyAuthorization off, and a server-side migration force-disables it even on servers upgraded from 10.x
  • Authorization: MediaBrowser Token="..." (and, for the one pre-login call below, Authorization: MediaBrowser Client="...", Device="...", DeviceId="...", Version="...") works on both versions, unaffected by that setting

A client that only wants to support one code path, rather than detecting the server version and switching headers, should always send Authorization, never X-Emby-Token/X-Emby-Authorization alone. Some examples below still show X-Emby-Token because that's what a plain server API key traditionally uses; substitute Authorization: MediaBrowser Token="{apiKeyOrUserToken}" for the same call if the target server might be running Jellyfin 12.

/JellyfinEnhanced

Check version

/JellyfinEnhanced/version

Bash
curl -X GET \
  "JELLYFIN_SERVER_URL/JellyfinEnhanced/version"
  • Jellyfin Server URL (JELLYFIN_URL)

Quick Start: Normal User vs Admin

There are two distinct ways to call the Bookmarks and Reviews endpoints below, depending on who is calling and whose data they are touching.

As a normal user, your own data only

Log in as that user to get a per-user access token, then use that token in Authorization: MediaBrowser Token="..." for every call. With this token you can only read or write your own bookmarks and your own review, never another user's.

How do I get JELLYFIN_USER_ACCESS_TOKEN?

Call Jellyfin's own POST /Users/AuthenticateByName with that user's username and password. This is not a Jellyfin Enhanced endpoint, it is part of Jellyfin Server's own API.

Bash
curl -X POST \
  -H 'Content-Type: application/json' \
  -H 'Authorization: MediaBrowser Client="MyApp", Device="MyApp", DeviceId="my-app-1", Version="1.0.0"' \
  -d '{"Username": "JELLYFIN_USERNAME", "Pw": "JELLYFIN_PASSWORD"}' \
  'JELLYFIN_SERVER_URL/Users/AuthenticateByName'

A client-identification header (Client, Device, DeviceId, Version, any values that identify your app) is required here, Jellyfin rejects the request without it even though no token exists yet at this point. Use the Authorization header for it, not X-Emby-Authorization: confirmed live against a Jellyfin 12 server, X-Emby-Authorization alone gets a bare 400 Error processing request there, because Jellyfin 12 disables the legacy X-Emby-*/X-MediaBrowser-* header family by default (ServerConfiguration.EnableLegacyAuthorization). Authorization: MediaBrowser ... is never gated by that setting, on 10.11 or 12, so it is the one header name safe to hard-code in a client.

The response body includes an AccessToken field, that is JELLYFIN_USER_ACCESS_TOKEN. It does not expire on its own, it stays valid until the user signs out or an admin revokes it from the Jellyfin Dashboard.

Alternatively, Quick Connect gets a token without the app ever handling the user's password.

  • Post your own review: POST /JellyfinEnhanced/reviews/{mediaType}/{tmdbId}
  • Delete your own review: DELETE /JellyfinEnhanced/reviews/{mediaType}/{tmdbId}
  • Add a bookmark for yourself: POST /JellyfinEnhanced/user-settings/{yourUserId}/bookmark.json/add
  • Remove a bookmark for yourself: DELETE /JellyfinEnhanced/user-settings/{yourUserId}/bookmark.json/{bookmarkId}

As an admin, any user's data

Generate a server API key from the Jellyfin Dashboard (Settings > API Keys), created by an Administrator account. Use that key in X-Emby-Token (Reviews) or Authorization: MediaBrowser Token="..." (Bookmarks), and put the target user's ID directly in the URL. This is the pattern a 3rd-party app should use to manage bookmarks and reviews for every user with a single credential, with no per-user login required.

  • Post a review as a specific user: POST /JellyfinEnhanced/reviews/admin/{userIdN}/{mediaType}/{tmdbId}
  • Delete a specific user's review: DELETE /JellyfinEnhanced/reviews/admin/{userIdN}/{mediaType}/{tmdbId}
  • Add a bookmark for a specific user: POST /JellyfinEnhanced/user-settings/{userId}/bookmark.json/add
  • Remove a bookmark for a specific user: DELETE /JellyfinEnhanced/user-settings/{userId}/bookmark.json/{bookmarkId}

A non-admin token used against another user's {userId} is rejected. Full request and response details for every endpoint are below.

Ratings vs. text reviews

{tmdbId} in every Reviews endpoint above also accepts {tmdbId}:s{season} or {tmdbId}:s{season}:e{episode}, so a TV show can carry its own review plus separate ones per season/episode. A review's rating (1-5) and content are independent: send rating alone for a star-rating-only UI, content alone for text-only, or both. See Rating and content rules in the Reviews API section.

Bookmarks API

New in v11.13.0.0

The additive bookmark.json/add and bookmark.json/{bookmarkId} endpoints below, and admin access to any user's bookmarks via a server API key, are new in v11.13.0.0. Earlier versions only support reading or replacing the whole file with a per-user access token.

Bookmarks Storage Directory

Bookmarks are stored as bookmark.json (singular), inside a folder named after the user's ID, under the plugin's own configuration directory:

bookmark.json
{JELLYFIN_DATA_DIR}/plugins/configurations/Jellyfin.Plugin.JellyfinEnhanced/JELLYFIN_USER_ID/bookmark.json

JELLYFIN_USER_ID here is the user's GUID with hyphens stripped and lowercased (e.g. 9285e8b541494149...), regardless of the casing or format used in the URL.

userID? JELLYFIN_USER_ID?

userID is Jellyfin Server's unique ID for each user

JELLYFIN_USER_ID is a placeholder for the user's userID


Reference:

Jellyfin's API documentation on /Users

Tip

A simple way to find which userID belongs to which user:

  • Jellyfin Dashboard: Users
  • Click a user's profile
  • The userID for that user appears in the URL. For example: 9285e8b54149414941494edb9e464dcd in the URL http://localhost:8096/#/dashboard/users/profile?userId=9285e8b54149414941494edb9e464dcd
bookmark.json: Example Data structure
{
  "Bookmarks": {
    "unique-bookmark-id": {
      "ItemId": "jellyfin-item-id",
      "TmdbId": "12345",
      "TvdbId": "67890",
      "MediaType": "movie" | "tv",
      "Name": "Item Name",
      "Timestamp": 123.45,
      "Label": "Epic scene",
      "CreatedAt": "2026-01-03T12:00:00.000Z",
      "UpdatedAt": "2026-01-03T12:00:00.000Z",
      "SyncedFrom": "original-item-id"
    }
  }
}

Field casing is PascalCase, except on add

The Get, Replace-all, and Remove endpoints below all read/write BookmarkItem fields in PascalCase (ItemId, TmdbId, MediaType, Timestamp, ...) as shown above — confirmed against a live server, not just the source. The one exception is the Add one Bookmark endpoint: its request body accepts either casing (ASP.NET Core's model binder is case-insensitive), but its own success response is a separate small object using camelCase ({"success": true, "id": "..."}). Don't assume one casing convention across every Bookmarks endpoint.

Authentication: two ways to call these

  • As the user themselves: pass a per-user access token (obtained by that user logging in) in Authorization: MediaBrowser Token="...". Works for the user's own JELLYFIN_USER_ID only.
  • As an admin, for any user: pass a server Administrator API key. The plugin will let an Administrator key act on any JELLYFIN_USER_ID, which is what lets a 3rd-party app manage bookmarks for every user with a single credential.

A non-admin user calling with someone else's JELLYFIN_USER_ID is rejected.

Bookmarks API endpoints

bookmark.json is one of several per-user settings files served under /user-settings/{userId}/{file} (the same pattern covers settings.json, shortcuts.json, elsewhere.json, and hidden-content.json).

Get Bookmarks

/JellyfinEnhanced/user-settings/{userId}/bookmark.json

Bash
curl -X GET \
  -H 'Authorization: MediaBrowser Token="JELLYFIN_TOKEN"' \
  'JELLYFIN_SERVER_URL/JellyfinEnhanced/user-settings/JELLYFIN_USER_ID/bookmark.json'
GET /JellyfinEnhanced/user-settings/JELLYFIN_USER_ID/bookmark.json
Authorization: MediaBrowser Token="JELLYFIN_TOKEN"
  • JELLYFIN_TOKEN: a per-user access token for JELLYFIN_USER_ID, or an Administrator API key
  • Jellyfin Server URL JELLYFIN_SERVER_URL

Returns the bookmark.json contents directly, not wrapped. An empty {"Bookmarks": {}} if the user has none yet.

Replace all Bookmarks

/JellyfinEnhanced/user-settings/{userId}/bookmark.json

Overwrites the whole file. Use the additive endpoint below instead if you only want to add or remove one bookmark.

Bash
curl -X POST \
  -H 'Content-Type: application/json' \
  -H 'Authorization: MediaBrowser Token="JELLYFIN_TOKEN"' \
  -d '{
        "Bookmarks": {}
      }' \
  'JELLYFIN_SERVER_URL/JellyfinEnhanced/user-settings/JELLYFIN_USER_ID/bookmark.json'
POST /JellyfinEnhanced/user-settings/JELLYFIN_USER_ID/bookmark.json
Authorization: MediaBrowser Token="JELLYFIN_TOKEN"
Content-Type: application/json

{ "Bookmarks": { ... } }
  • JELLYFIN_TOKEN: a per-user access token for JELLYFIN_USER_ID, or an Administrator API key
  • Jellyfin Server URL JELLYFIN_SERVER_URL
  • The request body is the raw bookmarks object itself, not wrapped in { "fileName": ..., "data": ... }. Each bookmark item's fields are PascalCase (ItemId, TmdbId, MediaType, Timestamp, Label, ...), matching the Example Data structure above.
Add one Bookmark

/JellyfinEnhanced/user-settings/{userId}/bookmark.json/add

Adds a single bookmark without touching the rest of the file. The server generates the bookmark ID and timestamps and returns the new ID.

Bash
curl -X POST \
  -H 'Content-Type: application/json' \
  -H 'Authorization: MediaBrowser Token="JELLYFIN_TOKEN"' \
  -d '{
        "itemId": "jellyfin-item-id",
        "tmdbId": "1949",
        "mediaType": "movie",
        "name": "Zodiac",
        "timestamp": 1234.5,
        "label": "Great scene"
      }' \
  'JELLYFIN_SERVER_URL/JellyfinEnhanced/user-settings/JELLYFIN_USER_ID/bookmark.json/add'
  • JELLYFIN_TOKEN: a per-user access token for JELLYFIN_USER_ID, or an Administrator API key
  • itemId is required. tmdbId, tvdbId, mediaType, name, timestamp, label, and syncedFrom are all optional.

Response: {"success": true, "id": "Bm_1234567890_abc123xyz"}

Remove one Bookmark

/JellyfinEnhanced/user-settings/{userId}/bookmark.json/{bookmarkId}

Bash
curl -X DELETE \
  -H 'Authorization: MediaBrowser Token="JELLYFIN_TOKEN"' \
  'JELLYFIN_SERVER_URL/JellyfinEnhanced/user-settings/JELLYFIN_USER_ID/bookmark.json/BOOKMARK_ID'
  • BOOKMARK_ID is the ID returned by the add endpoint, or a key from the Bookmarks object returned by the get endpoint
  • Returns 404 with {"success": false, "removed": false} if no bookmark with that ID exists

Reviews API

New in v11.13.0.0

Admin access to any user's reviews via a server API key is new in v11.13.0.0. Earlier versions only support reading reviews or writing your own review with a per-user access token.

User reviews and ratings, shared across all users on the server. Each review is a content string and an optional 1-5 star rating, keyed by author + item, and reviews for one item are stored together in a single shared reviews.json (not a per-user file). This is the API to use if you want to build star ratings, review lists, or an average-rating badge for a client.

Rating and content rules

Every write endpoint below (self or admin) validates the same ReviewPayload body:

  • content (string, optional): trimmed server-side; rejected with 400 if longer than 2000 characters
  • rating (number, optional): must be 1-5 inclusive in 0.5 increments (e.g. 4.5), or omitted; 0, 6, or a value not on a half-star boundary (e.g. 4.3) is rejected with 400
  • At least one of content or rating must be present, an empty payload ({}) is rejected with 400 {"success": false, "message": "A rating or review text is required."}
  • This means a ratings-only review ({"rating": 4}, no text) and a text-only review ({"content": "..."}, no rating) are both valid

Reviewing a season or episode of a TV show

{tmdbId} in the URL is not just the bare TMDB ID, it also accepts two extended forms so a single mediaType: tv item can carry separate reviews per season or per episode:

  • {tmdbId} — the show as a whole, e.g. 1399
  • {tmdbId}:s{season} — a specific season, e.g. 1399:s1
  • {tmdbId}:s{season}:e{episode} — a specific episode, e.g. 1399:s1:e1

Any other shape (letters, missing digits, extra segments) is rejected with 400 {"message": "Invalid TmdbId."}. mediaType must always be movie or tv, both movie:s1 and any mediaType other than movie/tv are rejected the same way. URL-encode the colons (%3A) if your HTTP client doesn't do it for you.

Get reviews for an item

/JellyfinEnhanced/reviews/{mediaType}/{tmdbId}

Bash
curl -X GET \
  -H "X-Emby-Token: JELLYFIN_API_KEY" \
  "JELLYFIN_SERVER_URL/JellyfinEnhanced/reviews/movie/TMDB_ID"
  • Jellyfin Server API key (JELLYFIN_API_KEY)
  • mediaType: movie or tv
  • TMDB_ID: the item's TMDB ID, or one of the season/episode forms above

A plain API key is enough to read reviews, there is no per-user identity involved here. Any authenticated caller (per-user token or API key) sees every non-hidden reviewer's review; an admin caller always sees every review, including ones from hidden or disabled authors, for moderation.

Response:

200 OK
{
  "reviews": [
    {
      "userId": "9285e8b541494149...",
      "userName": "alice",
      "tmdbId": "TMDB_ID",
      "mediaType": "movie",
      "content": "Great movie!",
      "rating": 5,
      "createdAt": "2026-01-03T12:00:00.000Z",
      "updatedAt": "2026-01-03T12:00:00.000Z"
    }
  ]
}
  • reviews is [], never missing, when nobody has reviewed the item yet
  • rating is omitted from the object entirely for a text-only review, it is not present as rating: null. Check "rating" in review (or the equivalent in your language) rather than a null check if you need to tell "no rating given" apart from "rated 0". content behaves differently: a rating-only review still gets content: "" rather than omitting the key
  • Average rating and review count are not pre-computed by the server, a client wanting those should reduce over the rating field of the returned array itself, skipping entries where the key is absent
Add or edit your own review

/JellyfinEnhanced/reviews/{mediaType}/{tmdbId}

Creates your review if you don't have one for this item yet, or updates it (both content and rating are replaced, not merged) if you do.

Bash
curl -X POST \
  -H "Content-Type: application/json" \
  -H 'Authorization: MediaBrowser Token="JELLYFIN_USER_ACCESS_TOKEN"' \
  -d '{"content": "Great movie!", "rating": 5}' \
  "JELLYFIN_SERVER_URL/JellyfinEnhanced/reviews/movie/TMDB_ID"
  • A per-user access token (JELLYFIN_USER_ACCESS_TOKEN), this writes the review under whichever user the token belongs to
  • rating is optional, omit it to leave a text-only review; see Rating and content rules above for the accepted values
  • Response: {"success": true}. A 400 with {"success": false, "message": "..."} explains which validation rule failed
Delete your own review

/JellyfinEnhanced/reviews/{mediaType}/{tmdbId}

Bash
curl -X DELETE \
  -H 'Authorization: MediaBrowser Token="JELLYFIN_USER_ACCESS_TOKEN"' \
  "JELLYFIN_SERVER_URL/JellyfinEnhanced/reviews/movie/TMDB_ID"
  • Response: {"success": true} whether or not a review existed, this endpoint does not report 404 for a no-op delete (unlike the bookmark and admin-review delete endpoints below)
Add or edit a review as a specific user (admin)

/JellyfinEnhanced/reviews/admin/{userIdN}/{mediaType}/{tmdbId}

Lets an Administrator API key create or update a review for any user, identified by an explicit userIdN. This is what a 3rd-party app should use to manage reviews for multiple users with one credential. Creates the review if none exists yet for that user and item, or updates it otherwise.

Bash
curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-Emby-Token: JELLYFIN_API_KEY" \
  -d '{"content": "Great movie!", "rating": 5}' \
  "JELLYFIN_SERVER_URL/JellyfinEnhanced/reviews/admin/JELLYFIN_USER_ID/movie/TMDB_ID"
  • Jellyfin Server Administrator API key (JELLYFIN_API_KEY)
  • JELLYFIN_USER_ID here is the 32-character hex form with no dashes (the same format returned by the get-bookmarks endpoint, not the dashed form shown in the Jellyfin dashboard URL)
  • Same rating and content rules as the self-review endpoint above; rating still only allows 1-5 in 0.5 increments or omitted, an admin key does not bypass validation
  • Response: {"success": true}. A non-admin key gets 403 Forbidden, a malformed userIdN gets 400
Delete a specific user's review (admin)

/JellyfinEnhanced/reviews/admin/{userIdN}/{mediaType}/{tmdbId}

Bash
curl -X DELETE \
  -H "X-Emby-Token: JELLYFIN_API_KEY" \
  "JELLYFIN_SERVER_URL/JellyfinEnhanced/reviews/admin/JELLYFIN_USER_ID/movie/TMDB_ID"

Returns 404 with {"success": false, "removed": false} if no review exists for that user and item.

List every review on the server (admin)

/JellyfinEnhanced/reviews/admin/all

Returns all reviews from every user in one call, for headless consumers (a scrobbler, a Letterboxd/Trakt sync job, a backup script) that want the whole store without knowing its on-disk layout or calling the per-item endpoint once per TMDB ID. Ordered by updatedAt ascending, so a poller can keep the newest updatedAt it has seen and pass it back as since next time.

Bash
curl -X GET \
  -H "X-Emby-Token: JELLYFIN_API_KEY" \
  "JELLYFIN_SERVER_URL/JellyfinEnhanced/reviews/admin/all?since=2026-01-01T00:00:00Z&limit=500&offset=0"
  • Jellyfin Server Administrator API key (JELLYFIN_API_KEY); a non-admin caller gets 403 Forbidden
  • since (optional): ISO 8601 timestamp; only reviews whose updatedAt is at or after it are returned. Inclusive, so a poller that passes back the last updatedAt it saw will see that review again and should treat the pair userId + mediaType + tmdbId as the identity when de-duplicating
  • userId (optional): restrict to one author, 32-character hex form with no dashes
  • limit (optional, default 500, max 1000) and offset (optional, default 0) page through the result; total in the response is the count of reviews matching the filters, not the page size
  • Hidden and disabled authors are not filtered out here: this is an admin surface and returns the whole store, the same as what an admin sees on an item page

Response:

200 OK
{
  "reviews": [
    {
      "userId": "9285e8b541494149...",
      "userName": "alice",
      "tmdbId": "1399:s1:e1",
      "mediaType": "tv",
      "content": "Great episode!",
      "rating": 4.5,
      "createdAt": "2026-01-03T12:00:00.000Z",
      "updatedAt": "2026-01-05T09:30:00.000Z"
    }
  ],
  "total": 1,
  "offset": 0,
  "limit": 500
}
  • Each entry has the same shape as the per-item endpoint, including rating being omitted for a text-only review; tmdbId carries the season/episode suffix for season and episode reviews
  • A review whose stored updatedAt cannot be parsed is always returned (and sorts first) rather than being silently dropped by the since filter

Seerr Integration API

/JellyfinEnhanced/jellyseerr

Jellyfin Server's API

These values are from Jellyfin Server's API:

  • X-Emby-Token:
    • API key
Check Seerr connection

/JellyfinEnhanced/jellyseerr/status

Bash
curl -X GET \
  -H "X-Emby-Token: JELLYFIN_API_KEY" \
  "<JELLYFIN_URL>/JellyfinEnhanced/jellyseerr/status"
  • Jellyfin Server API key (JELLYFIN_API_KEY)
  • Jellyfin Server User ID (JELLYFIN_USER_ID)
  • Jellyfin Server URL (JELLYFIN_URL)

Using Seerr configuration:

  • URL(s)
  • API keys
Check if user X-Jellyfin-User-Id has a successfully linked Seerr account

/JellyfinEnhanced/jellyseerr/user-status

Bash
curl -X GET \
  -H "X-Emby-Token: JELLYFIN_API_KEY" \
  -H "X-Jellyfin-User-Id: JELLYFIN_USER_ID" \
  "JELLYFIN_SERVER_URL/JellyfinEnhanced/jellyseerr/user-status"
  • Jellyfin Server API key X-Emby-Token (JELLYFIN_API_KEY)
  • Jellyfin Server User ID X-Jellyfin-User-Id (JELLYFIN_USER_ID)
  • Jellyfin Server URL (JELLYFIN_URL)

Example:

Bash
curl -X POST \
  -H "X-Emby-Token: <API_KEY>" \
  -H "X-Jellyfin-User-Id: <USER_ID>" \
  -H "Content-Type: application/json" \
  -d '{"mediaType": "movie", "mediaId": 27205}' \
  "<JELLYFIN_URL>/JellyfinEnhanced/jellyseerr/request"

Admin Hidden Content API

Admin-only endpoints that let an administrator view and manage what other users have hidden. Every endpoint requires a Jellyfin administrator token and the Let admins view and manage other users' hidden content toggle (Pages → Hidden Content → Admin Controls) to be enabled; otherwise it returns 403. <USER_ID> is the 32-character hex ("N" format) Jellyfin user id.

List Users With Hidden Content

Returns each user (except the caller) who has hidden at least one item, with their hidden-item count, used to populate the admin user-filter dropdown.

curl -X GET \
  -H "X-Emby-Token: <ADMIN_API_KEY>" \
  "<JELLYFIN_URL>/JellyfinEnhanced/admin/hidden-content-users"

Get A User's Hidden Content

Returns a single user's hidden content (read-only).

curl -X GET \
  -H "X-Emby-Token: <ADMIN_API_KEY>" \
  "<JELLYFIN_URL>/JellyfinEnhanced/admin/hidden-content/<USER_ID>"

Unhide Items For A User

Removes one or more items from a user's hidden list. The body is a JSON array of item keys (an itemId, or tmdb-<id> for items not in the library).

curl -X POST \
  -H "X-Emby-Token: <ADMIN_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '["a1b2c3d4e5f6...", "tmdb-27205"]' \
  "<JELLYFIN_URL>/JellyfinEnhanced/admin/hidden-content/<USER_ID>/unhide"

Hide Items For A User

Adds one or more items to a user's hidden list (max 200 per call; an item the user hid themselves is never overwritten). The body is a JSON array of hidden-content items.

curl -X POST \
  -H "X-Emby-Token: <ADMIN_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '[{"TmdbId": "27205", "Name": "Inception", "Type": "Movie", "PosterPath": "/edv5CZvWj09upOsy2Y6IwDhK8bt.jpg"}]' \
  "<JELLYFIN_URL>/JellyfinEnhanced/admin/hidden-content/<USER_ID>/hide"

Full Endpoint Index

The sections above document Bookmarks, Reviews, Seerr, and Admin Hidden Content in full (auth, request/response shapes, edge cases). Everything else this plugin exposes is listed here as a map, method + path + a one-line purpose, so a client author knows what exists before reading Controllers/JellyfinEnhancedController.cs for the exact request/response shape. All paths are relative to /JellyfinEnhanced/. [Authorize] means any authenticated Jellyfin user or API key; admin-only endpoints are noted individually.

Not fully documented yet

This index is deliberately terse. If you're building against one of these and need the exact request/response shape, the fastest source of truth is the corresponding controller method (grep the path string in JellyfinEnhancedController.cs) - contributions expanding any of these into full sections like the ones above are welcome.

Seerr Discovery & Requests

Method Path Purpose
GET jellyseerr/status Seerr connection/reachability check (answer cached for 30 s)
GET jellyseerr/validate Validate configured Seerr URL(s) + API key
POST jellyseerr/trigger-recently-added-scan Kick off a Seerr library scan
GET jellyseerr/user-status Is the calling Jellyfin user linked to a Seerr account
GET jellyseerr/permission-audit Admin: audit every Jellyfin user's Seerr permission bits
GET jellyseerr/search Proxy a Seerr search query
GET jellyseerr/sonarr / jellyseerr/radarr List Sonarr/Radarr instances known to Seerr (read-only profile/folder discovery, no credentials)
GET jellyseerr/sonarr/lookup/{tmdbId} Sonarr title-lookup candidates for a series TMDB has no TVDB ID for; backs the "match series" block in the season modal. 404 when Seerr has no Sonarr configured
GET jellyseerr/{type}/{serverId} Sonarr/Radarr server details by Seerr service id
GET jellyseerr/settings/{type} Admin-only: Seerr's own Radarr/Sonarr instance connection settings (hostname, port, apiKey, externalUrl, ...). Backs the *arr tab's "Import from Seerr" button; not the same endpoint as jellyseerr/sonarr/jellyseerr/radarr above
GET jellyfin-urls Admin-only: Jellyfin's own detected internal LAN URL and "Published server URIs" external/all override from Dashboard → Networking → Advanced, if configured. Backs the Seerr-import URL Mapping pre-fill
POST / GET jellyseerr/request Create / list Seerr requests
GET jellyseerr/quota Calling user's Seerr request quota
GET jellyseerr/tv/{tmdbId}, jellyseerr/movie/{tmdbId} TV/movie detail proxy
GET .../season/{seasonNumber} Season detail proxy
GET .../similar, .../recommendations Similar / recommended titles
GET jellyseerr/movie/{tmdbId}/ratingscombined, jellyseerr/tv/{tmdbId}/ratings Combined critic/audience ratings
GET jellyseerr/discover/movies, jellyseerr/discover/tv Discovery feed
GET .../upcoming Upcoming releases
GET .../trending Trending feed
GET .../network/{networkId}, .../studio/{studioId} Discovery filtered by network/studio
GET jellyseerr/discover/tv/studio/{studioId} Series produced by a TMDB company, from TMDB (needs the TMDB key) in Seerr's discover shape; only rows in the caller's library carry a status (Seerr's own)
GET .../genre/{genreId}, .../keyword/{keywordId} Discovery filtered by genre/keyword
GET jellyseerr/discover/genreslider/movie, .../tv Genre-slider rows for the discovery UI
GET jellyseerr/person/{personId} Person detail proxy
GET jellyseerr/person/{personId}/combined_credits Person's filmography
GET jellyseerr/collection/{collectionId} Collection detail proxy
GET jellyseerr/overrideRule Seerr auto-approval override rules
GET jellyseerr/user Calling user's Seerr profile
POST jellyseerr/request/tv/{tmdbId}/seasons Request specific seasons of a show
GET jellyseerr/watchlist Calling user's Seerr watchlist
POST jellyseerr/sync-watchlist Admin: run the Sync Watchlist from Seerr to Jellyfin task's pass now, with the same settings. Uses the first configured Seerr URL whose user list loads, and returns 502 when none does. The response has success, itemsProcessed, itemsAdded and errors. Users missing from Seerr aren't imported; the Import Jellyfin Users to Seerr task does that
POST jellyseerr/import-users Admin: import Jellyfin users into Seerr
GET jellyseerr/settings/partial-requests Whether Seerr allows partial season requests
GET jellyseerr/issue, jellyseerr/issue/{id} List / get Seerr issues
POST jellyseerr/issue File a Seerr issue

TMDB Proxy & Metadata

Method Path Purpose
GET studio/{studioId} Studio metadata (non-Seerr TMDB proxy)
GET boxset/{boxsetId} Collection/boxset metadata
GET person/{personId} Person metadata
GET genre/{genreId} Genre metadata
GET tmdb/search/person, tmdb/search/keyword TMDB search proxy
GET tmdb/genres/movie, tmdb/genres/tv TMDB genre lists
GET tmdb/validate Validate the configured TMDB API key
GET tmdb/{**apiPath} Generic pass-through TMDB proxy (catch-all). Responses are cached server-side (6 h for single resources, 30 min for lists, persisted across restarts); a cold movie/tv title and its release_dates, watch/providers and reviews are fetched in one upstream call

Client Bootstrap & Config

Method Path Purpose
GET script The injected client <script> bundle
GET js/{**path} Individual JS module files
GET Configuration/configPage.css Admin config page stylesheet
GET version Plugin version
GET private-config Admin-only: full plugin config
GET public-config Curated, non-sensitive config subset the client script reads
GET locales, locales/{lang}.json Available languages / one language's translation bundle
GET cdn/{source}/{**path} Local mirror of third-party static assets (icons, fonts, logos)

Per-User Settings Files

Same user-settings/{userId}/{file} pattern as Bookmarks (see above) for every other per-user JSON store:

Method Path Purpose
GET / POST user-settings/{userId}/settings.json Enhanced panel settings
GET / POST user-settings/{userId}/shortcuts.json Keyboard shortcut overrides
GET / POST user-settings/{userId}/elsewhere.json Elsewhere (streaming provider) preferences
GET / POST user-settings/{userId}/hidden-content.json Hidden Content list
GET / POST user-settings/{userId}/spoilerblur.json Spoiler Guard per-user state

Spoiler Guard

Method Path Purpose
GET spoiler-blur/health Diagnostic: blur filter health
DELETE spoiler-blur/health/{targetUserId} Reset a user's blur-health state
GET spoiler-blur/series Series currently under Spoiler Guard
GET / POST spoiler-blur/user-prefs Per-user Spoiler Guard preferences
POST / DELETE spoiler-blur/series/{seriesId} Enable/disable Spoiler Guard for a series
POST / DELETE spoiler-blur/movies/{movieId} Enable/disable for a movie
POST / DELETE spoiler-blur/collections/{collectionId} Enable/disable for a collection
POST / DELETE spoiler-blur/pending/{mediaType}/{tmdbId} Pre-arm Spoiler Guard for a title not yet in the library
GET spoiler-blur/apply-existing/preview?skipStarted=false Admin: dry run of "apply to existing titles" with the saved auto-enable scope (per-user counts, nothing written; 409 if Spoiler Guard is off or a run is in progress, 500 with the error message if the count fails)
POST spoiler-blur/apply-existing?skipStarted=false Admin: start the "Spoiler Guard: apply to existing titles" scheduled task with these options (202 with completedRuns; 409 if already running or Spoiler Guard is off; 503 if the task isn't registered)
GET spoiler-blur/apply-existing/status Admin: task state and progress, completedRuns and the last run's summary (lastRun) since server start

Continue Watching / Next Up

Method Path Purpose
POST / DELETE continue-watching/hide/{itemId} Hide/unhide an item from Continue Watching
POST / DELETE next-up/hide/{itemId} Hide/unhide an item from Next Up

Tags, Cache & Watch Data

Method Path Purpose
POST tag-cache/rebuild Admin: force a full tag-cache rebuild
GET tag-cache/{userId} This user's tag cache (?since=<timestamp> returns only entries changed since, plus any under the user's Spoiler Guard). Includes servedAt (server time the response was captured, for ordering stored copies), filterRevision, a fingerprint of how this user's entries are filtered (library access) and Spoiler Guard-stripped (a client holding a copy made under another revision must replace it), and reviewRatings, the average user-review rating per mediaType:tmdbKey visible to this user, when user ratings on posters are on
POST tag-data/{userId} Batch tag lookup by item ids
GET item-stats/{userId}/{itemId} File size and watch progress for an item in one response (what the details page uses)
GET file-size/{userId}/{itemId} File size for an item
GET watch-progress/{userId}/{itemId} Watch progress for an item
GET audio-language-tags?codes= Audio-language tag names (base and regional) for comma-separated stream language codes
GET awards/{mediaType}/{tmdbId} Wikidata award wins/nominations
GET mdblist-ratings/{mediaType}/{tmdbId} MDBList ratings for a title
GET mdblist-ratings/account-status MDBList account quota/status

Activity Feed & What's New

Method Path Purpose
GET activity The Activity Feed (recently watched/favorited/reviewed)
GET whats-new Admin: pending "What's New" settings for the config page
POST whats-new/dismiss Admin: acknowledge the current "What's New" state

Branding

Method Path Purpose
POST UploadBrandingImage Admin: upload a custom logo/banner/favicon
GET BrandingImage Serve a custom branding image
POST DeleteBrandingImage Admin: remove a custom branding image

*arr (Sonarr/Radarr/Bazarr)

Method Path Purpose
GET arr/validate/sonarr, arr/validate/radarr Validate an instance's URL/API key
GET arr/identify-url Resolve which configured instance owns a given item
GET arr/series-slug, arr/series-slugs Sonarr URL slug lookup, single/batch
GET arr/movie-instances Which Radarr instance(s) a movie belongs to
GET arr/queue Sonarr/Radarr download queue
GET arr/history Sonarr/Radarr import history
GET arr/requests Requests page data (Seerr + arr queue merged)
POST arr/requests/{requestId}/approve, .../decline Admin: approve/decline a Seerr request
GET arr/calendar Calendar page data
POST arr/calendar/user-data Per-user calendar preferences (favorites/watched highlighting)

Active Streams & Proxy

Method Path Purpose
GET active-streams/sessions Currently active playback sessions
POST active-streams/broadcast Admin: broadcast a message to active sessions
GET proxy/avatar Cached avatar image proxy
GET items/by-providers Look up Jellyfin items by external provider id

Maintenance Mode

All admin-only.

Method Path Purpose
GET MaintenanceMode/Status Current maintenance-mode state (includes Source: manual or schedule, EndsAt, and the server's local time / UTC offset)
POST MaintenanceMode/Enable Enable maintenance mode (message, notificationMessage, durationMinutes, action, affectedUserIds). While a manual window is running, re-sending the same durationMinutes keeps its end time; a different value restarts the clock
POST MaintenanceMode/Disable Disable maintenance mode. A scheduled window ended this way stays off until the next day's window; ?includeScheduled=false (what the config page sends while the daily schedule is on) leaves it running instead
GET MaintenanceMode/Users Users affected by the current maintenance-mode config
POST MaintenanceMode/Broadcast Send the maintenance notification immediately ({countdown} / {ends_at} are resolved)

The anonymous public-config payload carries MaintenanceModeEnabled, MaintenanceModeMessage and MaintenanceModeEndsAt (UTC, or null when open-ended) for the banner countdown.

Misc

Method Path Purpose
POST reset-all-users-settings Admin: overwrite every user's settings with the current defaults
GET {viewName} Catch-all: serves a Plugin Pages HTML view by name