Tune by Mozaiklabs

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 limit and offset, 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 error field.
  • An unknown route under /api/v1 returns 404 with {"error": "not found", "path": …}.
  • Tune does not publish an OpenAPI description. GET /api/v1/system/api-docs returns a hand-maintained, non-exhaustive list of routes with their descriptions.

Route groups

PrefixContents
/api/v1/systemVersion, health, statistics, library scan, settings, backups, database, logs, diagnostics
/api/v1/libraryAlbums, artists, tracks, artwork, genres, history, smart playlists and collections
/api/v1/zonesZones and groups, playback commands, queue, zone audio settings
/api/v1/playlistsPlaylists
/api/v1/searchSearch across the library, radio and services
/api/v1/radios, /api/v1/podcastsRadio, radio favourites, podcasts
/api/v1/streamingStreaming services
/api/v1/devicesDevices discovered on the network
/api/v1/profilesProfiles
/api/v1/eq, /api/v1/crossfeedEqualiser and crossfeed presets
/api/v1/lyricsLyrics
/api/v1/plugins, /api/v1/audio-pluginsExtensions: management of WebAssembly and built-in extensions, native audio extensions
/api/v1/ext/{name}Routes added by a built-in extension
/api/v1/authAuthentication (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 the zone_id in data.
  • 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:

FamilyEvents
Playbackplayback.started, playback.paused, playback.resumed, playback.stopped, playback.track_changed, playback.volume, playback.seek
Zones and groupszone.created, zone.updated, zone.deleted, group.created, group.updated, group.deleted
Librarylibrary.scan.started, library.scan.progress, library.scan.completed, library.updated
Devicesdevice.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/login with {"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 in Authorization: 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/health and /system/version stay reachable without a token.
curl -H "Authorization: Bearer <token>" http://localhost:8888/api/v1/zones

See also