The native v2 library administration operations are acting-admin operations. Their
complete request and response schemas are generated in contracts/api/v2/openapi.json.
The frozen v1 bridge keeps its existing responses.
GET /api/v2/libraries/roots, GET /api/v2/libraries/skipped-roots, and
GET /api/v2/libraries/stale-ids return {items, page, total} collections, where total
counts the matches across every page. limit defaults to 50 and is
at most 200. Continue with page.next_cursor while page.has_more is true. A cursor
is bound to the acting administrator and query filters; changing a filter starts a
new listing without a cursor.
All three operations accept a trimmed, case-insensitive substring query q. Search
runs before database pagination, so a match can be found without loading preceding
pages. Percent signs and underscores are literal characters.
| Operation | Search fields | Additional filters |
|---|---|---|
| Roots | Root path, title, sample file path | Required library_id; optional state |
| Skipped roots | Root path, library name, reason | None |
| Stale IDs | Title, provider, provider ID, library name | Actionable provider IDs only |
The web administration page loads diagnostics when their section opens. It requests more results through Load more and starts a new first page when the search changes. Sorting controls on diagnostic tables sort the loaded results.
Library poster uploads allow a file of up to 10 MiB plus 1 MiB of multipart framing.
The file limit is checked separately from the total request size. Accepted library
deletion and metadata-refresh jobs return their canonical job URI in Location.
The v2 library resource carries realtime_monitoring, the library's real-time
monitoring switch: scan automatically when files in the library's folders
change. It appears on every library read, is optional on createLibrary
(omitted means true), and is an optional, non-nullable member of the
updateLibrary body (omitted leaves it unchanged). Existing libraries have it
on. Monitoring takes effect only while the server-wide
scanner.realtime_monitoring setting is on and the library is enabled.
The frozen /api/v1 library routes neither return nor accept the member, and a
v1 update leaves it unchanged.
GET /api/v2/libraries/realtime-monitoring (getLibraryRealtimeMonitoring,
administrators only) reports whether monitoring works for each library. The body
is server_enabled (the server-wide setting) and libraries, one entry per
library ordered by sort_order then ID. Each entry carries library_id,
enabled (the library's own switch), state, backend (inotify, or empty
when the node records no folder), detail, and directories. node_id and updated_at name the
server node whose report the state comes from, and are omitted when the state
does not come from a node report.
Monitoring is node-local: every API or integrated node monitors the library
folders it can see, stores one status row per library, refreshes it every 60
seconds, and deletes its rows on clean shutdown. A report older than 3 minutes
is ignored. state is the first that applies:
| State | Meaning |
|---|---|
server_disabled |
scanner.realtime_monitoring is off. |
library_disabled |
The library is disabled. |
monitoring_off |
The library's realtime_monitoring switch is off. |
not_reporting |
No node has a fresh report; no node can see the library's folders. |
| node report | The best state any node reported: monitoring, then starting, limit_reached, root_unavailable, unsupported_filesystem, unsupported_platform, error. |
GET /api/v2/libraries/capabilities (getLibraryCapabilities, administrators
only) is the feature-detection document for these library features; it answers
realtime_monitoring: true and trickplay: true alongside the common capability
members. trickplay_supported reports whether public asset storage is configured,
so administrators can enable seek previews when creating the first library.
Scans the monitor queues carry the trigger realtime_monitor.
DELETE /api/v2/libraries/{id} and library metadata refresh return 202, a polling
Retry-After: 5, and the canonical job body with an origin-relative
Location: /api/v2/library-jobs/{job_id}. This corrects the previously emitted
unimplemented v2 /admin/jobs/{id} monitor URL. The monitor survives deletion of its
library. Failed persistence never returns acceptance. Library deletion disables its
folder and inserts the job in one transaction; repeated acceptance for the same active
delete conflicts, while deletion of different libraries remains independent.
POST /api/v2/libraries/{id}/refresh-metadata takes an optional body with mode.
quick, the default when the body or mode is absent, refreshes only matched items that
need it, such as ones never refreshed, lacking an overview or artwork, with a failed
refresh, or with incomplete episodes. full refreshes every matched item and re-scans
items that have no provider IDs.
A refresh holds a per-library PostgreSQL advisory lock, shared with the
refresh_all_library_metadata task; a job that starts while another refresh of the
same library holds it fails rather than refreshing the library twice. A job recovered
after its worker stopped heartbeating waits for the lock instead: its earlier attempt may
still hold it until that worker notices the recovery, or until PostgreSQL closes the
session of a server that disappeared. The wait counts toward the job's six-hour limit
and can be cancelled.
The job contains id, kind, state, terminal, cancelable, created_at, optional
started_at and finished_at, and optional progress measured in items for metadata
refresh. Deletion stages have no honest shared work denominator and omit progress.
Successful work exposes a named refresh_result or deletion_result. A failed job
exposes safe JobFailure data in failure; polling itself still returns 200.
Internal request documents, storage details, operator messages, and raw errors are
never included. Clients use terminal rather than an exhaustive state list.
GET /api/v2/library-jobs/{job_id} is available to administrator accounts, supports
ETag/If-None-Match, and sends Retry-After while nonterminal. The validator covers
the entire authorized body and is deterministic across API replicas. Authorization
precedes conditional evaluation. Unknown jobs and jobs hidden from the caller return
404. The structured response retains the default Cache-Control: no-store policy.
POST /api/v2/library-jobs/{job_id}/cancel retains the acting-admin gate, including
the primary-profile requirement when a profile is supplied; gate denials return 403.
After that gate, hidden or unknown jobs return 404. It accepts refresh cancellation with 202
and state: canceling. Pending retries coalesce, an already canceled job returns
200, and succeeded, failed, or noncancelable deletion jobs return
409 job_not_cancelable. Cancellation is best-effort: already refreshed metadata
remains. Intent is persisted in the job row, is observed by remote workers at their
heartbeat interval, and survives worker restart. Queued intent is acknowledged by the
ordinary worker without executing the refresh. The database serializes cancellation
and completion; once a terminal outcome wins, a stale worker cannot overwrite it.
Jobs remain retrievable for at least 24 hours after completion, failure, or cancellation; the runner normally retains them for seven days. Cleanup may then remove the monitor. The same canonical job shape is returned when work completes before the acceptance response is sent. Retrying terminal work submits a new operation under that operation's retry policy. Jellyfin behavior is unchanged; native Apple and Android clients must use the canonical v2 monitor during their coordinated v2 migration.
libraries:read allows GET /api/v2/user/libraries and its /capabilities
endpoint. It reuses the existing account/profile visibility rules and response:
library IDs, names, types, sort order and optional poster URLs. It grants no
access to administrator storage metadata, library management or media playback.
The credential owner supplies the account identity; this is not discovery on
behalf of an arbitrary user.
Existing web, Apple and Android library callers keep the same response and access rules. The new API-key scope requires no changes to those clients or Jellyfin.