API Reference
The Tune server exposes a JSON HTTP API and an event WebSocket. Tune's own web interface uses them. This page presents their structure and a few real examples; it is not a complete list of routes.
Principles
- All routes live under
/api/v1, on the server's port (8888 by default). There is no other API version. - Requests and responses are JSON. Responses are gzip-compressed when the client accepts it.
- Lists are paginated with
limitandoffset, and return{"items": […], "total": …, "limit": …, "offset": …}. - An error returns an HTTP status (400, 401, 403, 404, 409, 500, 503) and, in most cases, a JSON object with an
errorfield. - An unknown route under
/api/v1returns 404 with{"error": "not found", "path": …}. - Tune does not publish an OpenAPI description.
GET /api/v1/system/api-docsreturns a hand-maintained, non-exhaustive list of routes with their descriptions.
Route groups
| Prefix | Contents |
|---|---|
/api/v1/system | Version, health, statistics, library scan, settings, backups, database, logs, diagnostics |
/api/v1/library | Albums, artists, tracks, artwork, genres, history, smart playlists and collections |
/api/v1/zones | Zones and groups, playback commands, queue, zone audio settings |
/api/v1/playlists | Playlists |
/api/v1/search | Search across the library, radio and services |
/api/v1/radios, /api/v1/podcasts | Radio, radio favourites, podcasts |
/api/v1/streaming | Streaming services |
/api/v1/devices | Devices discovered on the network |
/api/v1/profiles | Profiles |
/api/v1/eq, /api/v1/crossfeed | Equaliser and crossfeed presets |
/api/v1/lyrics | Lyrics |
/api/v1/plugins, /api/v1/audio-plugins | Extensions: management of WebAssembly and built-in extensions, native audio extensions |
/api/v1/ext/{name} | Routes added by a built-in extension |
/api/v1/auth | Authentication (see below) |
Outside /api/v1, the server also serves audio streams to devices (/stream/{id}) and its UPnP services (/upnp). These paths are meant for devices, not for scripts.
Examples
Server health
curl http://localhost:8888/api/v1/system/health
Returns status (ok or degraded with HTTP 200, error with HTTP 503), plus version, uptime_seconds, the track and album counts, and component status. GET /api/v1/system/version returns {"version": …, "engine": "rust"}.
Library albums
curl "http://localhost:8888/api/v1/library/albums?limit=20&offset=0&sort=added_at&order=desc"
Parameters: limit (50 by default), offset, sort (including random), order, and filters such as format or quality.
Search
curl "http://localhost:8888/api/v1/search?q=miles&limit=10"
The response separates local (library artists, albums and tracks, with their totals), radios and services.
Playing on a zone
# Play a library album on zone 1
curl -X POST http://localhost:8888/api/v1/zones/1/play \
-H "Content-Type: application/json" -d '{"album_id": 42}'
# Volume at 35 %, or in decibels
curl -X POST http://localhost:8888/api/v1/zones/1/volume \
-H "Content-Type: application/json" -d '{"volume": 0.35}'
curl -X POST http://localhost:8888/api/v1/zones/1/volume \
-H "Content-Type: application/json" -d '{"volume_db": -18}'
# Jump to 1 min 30 s
curl -X POST http://localhost:8888/api/v1/zones/1/seek \
-H "Content-Type: application/json" -d '{"position_ms": 90000}'
The /play body accepts, among others, track_id, track_ids, album_id, playlist_id and start_index. The other zone commands are pause, resume, playpause, stop, next, previous, shuffle and repeat. The queue is read with GET /api/v1/zones/{id}/queue.
Zone status
curl http://localhost:8888/api/v1/zones/1/status
Returns, among others, state (playing, paused or stopped), now_playing, position_ms, volume, volume_db, muted, shuffle, repeat, queue_position and queue_length.
Event WebSocket
The WebSocket lives at /ws (or /api/v1/ws). It pushes state changes in real time, so there is no need to poll the API.
- On connection, the server sends a snapshot:
{"type": "snapshot", "data": {"zones": […], "groups": […]}}. - Each event looks like
{"type": "<name>", "data": {…}}. Playback events carry thezone_idindata. - A client can filter what it receives:
{"subscribe": ["playback.*", "zone.updated"]}. The server confirms with{"type": "subscribed", …}. By default a client receives everything. This is the only message a client sends: commands go through the HTTP API. - Every 15 seconds, the server sends
{"type": "ping"}. - Progress events (scan, enrichment) are spaced out so they do not flood clients.
Some event names:
| Family | Events |
|---|---|
| Playback | playback.started, playback.paused, playback.resumed, playback.stopped, playback.track_changed, playback.volume, playback.seek |
| Zones and groups | zone.created, zone.updated, zone.deleted, group.created, group.updated, group.deleted |
| Library | library.scan.started, library.scan.progress, library.scan.completed, library.updated |
| Devices | device.discovered, device.updated, device.lost |
Event names are part of the contract with clients: Tune keeps them stable.
Authentication
Authentication is off by default, which leaves the API open to the local network. Once it is turned on (POST /api/v1/auth/config):
POST /api/v1/auth/loginwith{"username", "password"}returns a session token valid for 24 hours.POST /api/v1/auth/token(admin) creates a long-lived API token, for a script or home automation.- The token goes in the
Authorization: Bearer <token>header. An API key goes inAuthorization: ApiKey <key>. - For the WebSocket, a client that cannot set headers passes the token in the address:
/ws?token=<token>. - Some routes require the admin role and return 403 otherwise.
/system/healthand/system/versionstay reachable without a token.
curl -H "Authorization: Bearer <token>" http://localhost:8888/api/v1/zones
See also
- Technical Guide: table of common routes
- Architecture: event bus, extensions
- Configuration: ports and security