| title | API reference |
|---|---|
| description | Every route the server serves, generated from the router itself. |
A self-hosted music library. Everything the first-party front end does, it does through this API and nothing else — there is no privileged route, which is the only way that guarantee means anything.
Five rules shape it: cursor pagination and never OFFSET, revision-based deltas, collection ETags, one round trip per page with presence and renditions embedded, and server-sent events rather than polling.
A Subsonic-compatible API is also served at /rest, which this document does not describe: it is somebody else's specification and is documented by them.
Base URL: /api/v1
Authentication is a bearer token, or ?token= for the places that cannot set
a header — <audio src> being the reason that exists. A server with no
accounts answers everything, so a fresh install is usable before anyone has
created a login.
| Path | What it does | |
|---|---|---|
GET |
/users |
Every account. Admin only; no secret ever leaves here. |
POST |
/users |
Creates an account. Roles: admin, user, guest — a guest plays and nothing else. |
DELETE |
/users/{id} |
Removes an account and its tokens. Not the last admin. |
PATCH |
/users/{id} |
Changes a role or a password. Refuses to demote the last admin. |
GET |
/users/{id}/sources |
Which sources this account may see. all when it has not been narrowed. |
PUT |
/users/{id}/sources |
Narrows an account to some sources. An empty list restores all of them. |
POST /users
Body: { username, password, role? }
PATCH /users/{id}
Body: { role?, password? }
PUT /users/{id}/sources
Body: { sourceIds: [] }
| Path | What it does | |
|---|---|---|
POST |
/auth/login |
Exchanges a password for a token. |
GET |
/auth/me |
The signed-in user, with the capabilities it has and the sources it may see. |
POST |
/auth/setup |
Claims a fresh install. Refused once any account exists. |
GET |
/auth/state |
Whether this install has been claimed yet. |
GET |
/auth/tokens |
Tokens belonging to the signed-in user. Secrets are never returned. |
POST |
/auth/tokens |
Issues a token. The secret is shown once and only its hash is kept. |
DELETE |
/auth/tokens/{id} |
Revokes a token. |
POST /auth/login
Body: { username, password }
POST /auth/setup
Body: { username, password }
| Path | What it does | |
|---|---|---|
GET |
/backup |
Everything a rescan cannot rebuild. Secrets excluded unless asked. |
POST |
/restore |
Applies a backup. Adds; never replaces. |
GET /backup
Query: secrets
| Path | What it does | |
|---|---|---|
GET |
/devices |
Connected devices. |
POST |
/devices |
Satellite registration. Idempotent on the device id. |
PATCH |
/devices/{id} |
Renames it or changes what it syncs. |
POST |
/devices/{id}/backup |
Backs the device up. |
POST |
/devices/{id}/eject |
Disconnects without forgetting anything. |
POST |
/devices/{id}/import |
Pulls tracks the device has and the library does not. |
GET |
/devices/{id}/stats |
Counts and orphans on the device. |
POST |
/devices/{id}/sync |
Syncs. With dryRun returns the plan and writes nothing. |
GET |
/devices/{id}/tracks |
What the device actually holds, library or not. |
PUT |
/devices/{id}/tracks |
A satellite reporting the device contents. |
DELETE |
/devices/{id}/wanted |
Un-picks them. |
POST |
/devices/{id}/wanted |
Hand-picks tracks for a device. They join the sync rules. |
| Path | What it does | |
|---|---|---|
GET |
/events |
Server-sent events. One stream; clients never poll. |
GET |
/jobs |
Background work. |
DELETE |
/jobs/{id} |
Cancels it. |
GET |
/jobs/{id} |
One job. |
PATCH |
/jobs/{id} |
Pauses or resumes it. |
GET |
/jobs/{id}/items |
What it did item by item, with counts over the whole job. |
| Path | What it does | |
|---|---|---|
GET |
/artwork/{id} |
Cover art, extracted on demand and ETagged on the file. |
GET |
/facets |
Distinct genres, artists, albums and formats, with counts. |
GET |
/stream/{id} |
The audio, honouring Range. Converts on the fly when the library holds nothing the client accepts — that response carries no Range and an X-Jukebox-Transcoded header. |
GET |
/tracks |
One page of tracks, with device presence and renditions. |
PATCH |
/tracks |
Edits one or many. Tag writing to disk becomes a job. |
GET |
/tracks/{id} |
One track. |
GET |
/tracks/{id}/memberships |
Every playlist and device holding this track. Smart playlists are asked, not read. |
POST |
/tracks/{id}/play |
Records a listen. Half the length or four minutes, never under thirty seconds. |
GET |
/tracks/count |
How many tracks a query matches. |
GET |
/tracks/delta |
What changed since a revision. The main network win. |
GET |
/tracks/missing |
Tracks whose file the scanner can no longer find. |
POST |
/tracks/tags |
Adds and removes tags on a set of tracks. Add/remove rather than replace, so tagging a selection cannot silently clear what the tracks did not have in common. |
GET /stream/{id}
Query: rendition, format, accept, seek
GET /tracks
Query: sort, cursor, limit, q, genre, artist, album, format, tag, kind, sourceId, rating, ratingMin, lossless, onDevice, notOnDevice, match
PATCH /tracks
Body: { ids, patch, writeToFiles? }
POST /tracks/{id}/play
Body: { played, startedAt? }
GET /tracks/delta
Query: since, limit
POST /tracks/tags
Body: { ids, add?, remove? }
| Path | What it does | |
|---|---|---|
GET |
/outputs |
Renderers on the network — UPnP by SSDP, AirPlay by multicast DNS — plus satellites that registered themselves. |
DELETE |
/outputs/{id} |
Forgets a registered output. Discovered ones come back on their own. |
POST |
/outputs/{id}/pause |
Pauses it. |
POST |
/outputs/{id}/play |
Points a renderer at a track and starts it. |
POST |
/outputs/{id}/stop |
Stops it. |
POST |
/outputs/{id}/volume |
Sets its volume, 0 to 100. AirPlay answers 501: its volume lives in RTSP, not this protocol. |
POST |
/outputs/register |
A satellite announcing it can play. Re-registering is also its heartbeat. |
GET /outputs
Query: refresh
POST /outputs/register
Body: { id, name, url, formats? }
| Path | What it does | |
|---|---|---|
GET |
/player |
The shared queue: what is playing, where, and who last changed it. |
PATCH |
/player |
Changes the output, repeat or shuffle. |
POST |
/player/goto |
Jumps to a track already in the queue. |
POST |
/player/next |
Next track. Stops at the end unless repeat is on. |
POST |
/player/pause |
Pauses. |
POST |
/player/play |
Resumes. |
POST |
/player/previous |
Previous track, or restarts this one. |
DELETE |
/player/queue |
Empties it. |
POST |
/player/queue |
Adds to it. next: true puts them after the current track. |
PUT |
/player/queue |
Replaces the queue and starts it. |
POST |
/player/report |
A renderer saying where it actually is. It may not reorder anything. |
POST |
/player/seek |
Moves the playhead. |
PATCH /player
Body: { target?, repeat?, shuffle? }
POST /player/goto
Body: { trackId }
POST /player/queue
Body: { trackIds, next? }
PUT /player/queue
Body: { trackIds, startAt? }
POST /player/report
Body: { position, playing? }
POST /player/seek
Body: { position }
| Path | What it does | |
|---|---|---|
GET |
/playlists |
Every playlist, manual and smart. |
POST |
/playlists |
Creates one. |
DELETE |
/playlists/{id} |
Deletes it. |
GET |
/playlists/{id} |
One playlist. |
PATCH |
/playlists/{id} |
Renames it. |
PUT |
/playlists/{id}/order |
Moves a batch, preserving its relative order. |
DELETE |
/playlists/{id}/tracks |
Removes tracks. |
GET |
/playlists/{id}/tracks |
Its contents. A smart playlist runs its query. |
POST |
/playlists/{id}/tracks |
Adds tracks, deduplicated. |
POST /playlists
Body: { name, smart?, rules?, trackIds? }
| Path | What it does | |
|---|---|---|
GET |
/plugins |
Installed plugins, and what each can be asked to do right now. |
GET |
/plugins/{id} |
One plugin. |
PATCH |
/plugins/{id} |
Enables, disables or configures it. |
POST |
/plugins/{id}/command |
Runs something a plugin contributed. |
POST |
/plugins/scan |
Re-reads the plugin folder. Failures are listed with their reason. |
GET |
/store |
Browses a plugin index. There is no default store, on purpose. |
DELETE |
/store/{id} |
Uninstalls it. |
POST |
/store/install |
Downloads, verifies and installs one. |
POST /plugins/{id}/command
Body: { command, trackIds? }
GET /store
Query: index
POST /store/install
Body: { index, id }
| Path | What it does | |
|---|---|---|
GET |
/podcasts |
Subscriptions. |
POST |
/podcasts |
Subscribes and fetches straight away. |
DELETE |
/podcasts/{id} |
Unsubscribes. |
GET |
/podcasts/{id} |
One subscription. |
PATCH |
/podcasts/{id} |
Changes its refresh, retention or destination. |
GET |
/podcasts/{id}/episodes |
Episodes, newest first. |
POST |
/podcasts/{id}/refresh |
Refreshes now. Conditional: unchanged feeds cost no body. |
POST /podcasts
Body: { feedUrl, cron?, keepLast?, autoDownload? }
| Path | What it does | |
|---|---|---|
GET |
/radios |
Stations. |
POST |
/radios |
Adds one, discovering its name and logo unless told not to. |
DELETE |
/radios/{id} |
Removes it. |
GET |
/radios/{id} |
One station. |
PATCH |
/radios/{id} |
Edits it. |
POST |
/radios/{id}/discover |
Re-runs discovery, filling blanks only. |
| Path | What it does | |
|---|---|---|
GET |
/schedules |
Recurring work. |
POST |
/schedules |
Adds one. The cron expression is validated now, not at fire time. |
DELETE |
/schedules/{id} |
Removes it. |
PATCH |
/schedules/{id} |
Edits it. |
POST |
/schedules/{id}/run |
Runs it now, without moving its next occurrence. |
| Path | What it does | |
|---|---|---|
GET |
/health |
Liveness. The one route that never needs credentials. |
GET |
/openapi.json |
This document. |
GET |
/stats |
Library totals, computed in SQL over the whole library rather than a page. |
| Path | What it does | |
|---|---|---|
GET |
/duplicates |
Rows that look like one song. Proposes; never merges. |
POST |
/duplicates/merge |
Folds tracks into one, moving their files across as renditions. |
POST |
/organize |
What moving files into a pattern would do. Dry unless apply. |
POST |
/organize/{jobId}/undo |
Puts a reorganisation back, newest move first. |
GET |
/organize/log |
Every file a reorganisation moved. |
GET |
/sources |
Where the music lives. |
POST |
/sources |
Adds a source. rclone sources carry config: { url, fs }. |
POST |
/sources/{id}/scan |
Indexes a source. full re-reads every file; prune confirms deleting every track when the source turns up empty, which an unmounted share also does. |
POST |
/sources/{id}/test |
Does this source answer? Better asked before a scan than read from its error afterwards. |
POST |
/transcode |
Converts a selection. replace: false keeps both as renditions. |
GET |
/transcode/capabilities |
Whether ffmpeg is present, and what it can write. |
POST /duplicates/merge
Body: { keeperId, ids }
POST /organize
Body: { sourceId, pattern, apply? }
POST /sources/{id}/scan
Query: full, prune
POST /transcode
Body: { ids, format, quality?, replace }
A Subsonic-compatible API is served at /rest. It is somebody else's
specification and they document it; every client written for it in the last
twenty years works against this server unchanged.