Spotify Soloist WebSocket API

Warning:The WebSocket API has no built-in client authentication, authorization, TLS, Origin validation, CSRF protection, browser-facing security controls, or network exposure policy.

Enable the local WebSocket API with --ws ADDR:PORT:


_10
soloist --device-name "Kitchen speaker" --api-key "$SOLOIST_API_KEY" --ws 127.0.0.1:9090

Connect to:


_10
ws://ADDR:PORT

All messages are JSON text frames. Every client-to-server message must have "type": "command". Every server-to-client event has a type field.

When the server accepts a connection, Spotify Soloist sends auth_state. If a user is already logged in, it also sends playback_state.

The WebSocket server is opt-in. Without --ws, Spotify Soloist does not open a local API port and soloist ctl observe/control commands cannot connect unless you restart Spotify Soloist with --ws.

Endpoint discovery

When Spotify Soloist starts with --ws, it writes the active WebSocket address and port to runtime files in the data directory:

FileContents
ws.addrBind address, such as 127.0.0.1.
ws.portActual listening port.

Use port 0 to let the operating system choose a free port, then read these files before connecting. Spotify Soloist removes the files on shutdown. soloist ctl uses the same discovery mechanism.

Security

The Spotify Soloist WebSocket API is a local integration surface. It has no built-in client authentication, authorization, TLS, Origin validation, CSRF protection, browser-facing security controls, or network exposure policy.

Entity shape

Playback items, contexts, queue entries, parents, and creators use a common entity envelope:


_45
{
_45
"uri": "spotify:track:2JRo0gjbX4GrCqBYdRohoo",
_45
"entity_type": "track",
_45
"decorations": {
_45
"identity": {
_45
"name": "My Song"
_45
},
_45
"visual_identity": {
_45
"cover": [
_45
{
_45
"url": "https://i.scdn.co/image/ab67616d00001e02...",
_45
"size": "large"
_45
}
_45
]
_45
},
_45
"parent": {
_45
"entity": {
_45
"uri": "spotify:album:4aawyAB9vmqN3uQ7FjRGTy",
_45
"entity_type": "album",
_45
"decorations": {
_45
"identity": {
_45
"name": "Album Name"
_45
}
_45
}
_45
}
_45
},
_45
"creators": [
_45
{
_45
"entity": {
_45
"uri": "spotify:artist:...",
_45
"entity_type": "artist",
_45
"decorations": {
_45
"identity": {
_45
"name": "Artist Name"
_45
}
_45
}
_45
}
_45
}
_45
],
_45
"playback": {
_45
"duration_ms": 210000,
_45
"content_ratings": []
_45
}
_45
}
_45
}

FieldTypeDescription
uristringSpotify URI, or an empty string when no top-level entity is available. Nested related entities may omit uri when unknown.
entity_typestringKnown values include track, episode, artist, album, playlist, show, ad, and unknown. Empty when no entity is available.
decorations.identity.namestringDisplay name when known.
decorations.visual_identity.cover[]arrayOptional cover images with url and size. Current sizes are small, default, large, and xlarge when source images are available.
decorations.parent.entityobjectOptional parent entity, such as an album for a track or a show for an episode.
decorations.creators[]arrayOptional creator entities.
decorations.playback.duration_msnumberPlayable duration in milliseconds when known.
decorations.playback.content_ratings[]arrayContent rating labels when known, such as explicit or south_korea_19.

Event: auth_state

Sent on connect, authentication changes, and in response to get_auth_state.


_10
{
_10
"type": "auth_state",
_10
"logged_in": true,
_10
"is_active": true,
_10
"device_name": "Kitchen speaker"
_10
}

FieldTypeDescription
logged_inbooleanWhether Spotify Soloist has an authenticated Spotify Connect session.
is_activebooleanWhether Spotify Soloist is the active Spotify Connect device.
device_namestringSpotify Connect device name.

Event: playback_state

Full playback snapshot. Sent on connect when logged in, in response to get_state, and when broad playback changes require a refreshed snapshot. Other state changes arrive as granular events such as track_changed, playback_changed, context_changed, options_changed, queue_changed, and position_sync.


_49
{
_49
"type": "playback_state",
_49
"status": "playing",
_49
"item": {
_49
"uri": "spotify:track:2JRo0gjbX4GrCqBYdRohoo",
_49
"entity_type": "track",
_49
"decorations": {
_49
"identity": {
_49
"name": "My Song"
_49
},
_49
"playback": {
_49
"duration_ms": 210000,
_49
"content_ratings": []
_49
}
_49
}
_49
},
_49
"context": {
_49
"uri": "spotify:playlist:37i9dQZF1DXcBWIGoYBM5M",
_49
"entity_type": "playlist",
_49
"decorations": {
_49
"identity": {
_49
"name": "Today's Top Hits"
_49
}
_49
}
_49
},
_49
"position": {
_49
"position_ms": 45000,
_49
"timestamp_ms": 1747654321000,
_49
"speed": 1.0
_49
},
_49
"volume": 65,
_49
"is_active": true,
_49
"options": {
_49
"shuffle": false,
_49
"repeat": "off",
_49
"playback_speed": 1.0,
_49
"modes": {}
_49
},
_49
"available_actions": {
_49
"pause": {},
_49
"seek": {},
_49
"seek_forward": {
_49
"step_ms": 15000
_49
},
_49
"seek_backward": {
_49
"step_ms": 15000
_49
}
_49
}
_49
}

FieldTypeDescription
statusstringidle, playing, paused, or buffering.
itemobjectCurrent playable entity.
contextobjectCurrent playback context entity.
position.position_msnumberPlayback position at position.timestamp_ms.
position.timestamp_msnumberServer wall-clock time as Unix epoch milliseconds.
position.speednumberPosition speed multiplier. 0.0 means position is not advancing.
volumenumberVolume percentage from 0 to 100.
is_activebooleanWhether Spotify Soloist is the active Spotify Connect device.
options.shufflebooleanWhether shuffle is enabled.
options.repeatstringoff, context, or track.
options.playback_speednumberRequested playback speed.
options.modesobjectPublic playback mode values. Current values may include context_enhancement with NONE or RECOMMENDATION.
available_actionsobjectActions currently available from this playback state.

Current available_actions keys include pause, play, seek, seek_forward, seek_backward, skip_next, skip_prev, add_to_queue, shuffle, and set_repeat. Action values are empty objects unless parameters are advertised, such as step_ms for relative seek actions.

available_actions uses player action names. The command table below maps those actions to WebSocket command names; for example, shuffle maps to set_shuffle and set_repeat maps to the repeat commands.

Playback update events

EventPayload
track_changed{ "type": "track_changed", "item": Entity }
playback_changed{ "type": "playback_changed", "status": "paused" }
volume_changed{ "type": "volume_changed", "volume": 42 }
device_changed{ "type": "device_changed", "is_active": true, "device_name": "Kitchen speaker" }
context_changed{ "type": "context_changed", "context": Entity }
options_changed{ "type": "options_changed", "options": PlaybackOptions }

Event: position_sync

Sent whenever the playback position anchor changes. Use it to interpolate position locally between updates.


_10
{
_10
"type": "position_sync",
_10
"position": {
_10
"position_ms": 45000,
_10
"timestamp_ms": 1747654321000,
_10
"speed": 1.0
_10
}
_10
}

FieldTypeDescription
position.position_msnumberPosition at position.timestamp_ms.
position.timestamp_msnumberServer wall-clock time as Unix epoch milliseconds.
position.speednumberPosition speed multiplier. 0.0 means paused or stopped.

Event: queue_changed

Sent when the play queue changes and in response to get_queue.


_29
{
_29
"type": "queue_changed",
_29
"previous": [
_29
{
_29
"uid": "spotify:track:previous",
_29
"source": "context",
_29
"item": {
_29
"uri": "spotify:track:previous",
_29
"entity_type": "track",
_29
"decorations": {}
_29
}
_29
}
_29
],
_29
"upcoming": [
_29
{
_29
"uid": "spotify:track:upcoming",
_29
"source": "queue",
_29
"item": {
_29
"uri": "spotify:track:upcoming",
_29
"entity_type": "track",
_29
"decorations": {
_29
"identity": {
_29
"name": "Next Song"
_29
}
_29
}
_29
}
_29
}
_29
]
_29
}

FieldTypeDescription
previousarrayPrevious queue entries, ordered from most recently played to oldest.
upcomingarrayUpcoming queue entries, ordered from next to play onward.
previous[].uid, upcoming[].uidstringStable queue entry identifier when available, otherwise the item URI.
previous[].source, upcoming[].sourcestringcontext, queue, or autoplay.
previous[].item, upcoming[].itemobjectQueue item entity.

Broadcast queue_changed events are capped at 10 entries. Use get_queue with limit to request a different number. Omit limit or set it to 0 to request all available entries.

Result and error events

Accepted control commands return:


_10
{
_10
"type": "command_result",
_10
"command": "pause"
_10
}

Malformed messages, unknown commands, invalid command fields, and commands that require login return an error to the originating client:


_10
{
_10
"type": "error",
_10
"message": "command requires authentication"
_10
}

The connection stays open after an error.

command_result means the command was accepted and dispatched. It does not guarantee that playback state has already changed. Playback changes are asynchronous, so clients should still listen for events such as playback_changed, volume_changed, options_changed, queue_changed, and position_sync.

WebSocket commands

All commands use:


_10
{ "type": "command", "command": "get_state" }

Query commands

CommandAuthenticationDescription
get_auth_stateNoRequest auth_state.
get_stateYesRequest playback_state.
get_queueYesRequest queue_changed. Optional non-negative limit field.

Example:


_10
{ "type": "command", "command": "get_queue", "limit": 10 }

Query commands return their corresponding event payload and do not send command_result.

Control commands

CommandFieldsDescription
playOptional uriResume playback or start playing a playable Spotify URI.
pauseNonePause playback.
skip_nextNoneSkip to next track.
skip_prevNoneSkip to previous track or restart current track.
seekposition_msSeek to position in milliseconds.
set_volumevolumeSet volume from 0 to 100.
set_shuffleenabledEnable or disable shuffle.
set_repeat_contextenabledEnable or disable context repeat.
set_repeat_trackenabledEnable or disable track repeat.
add_to_queueuriAdd a Spotify track URI to the queue.
activateNoneMake Spotify Soloist the active Spotify Connect device.
deactivateNoneGive up active-device status.

Examples:


_10
{ "type": "command", "command": "play" }
_10
{ "type": "command", "command": "play", "uri": "spotify:playlist:37i9dQZF1DXcBWIGoYBM5M" }
_10
{ "type": "command", "command": "seek", "position_ms": 30000 }
_10
{ "type": "command", "command": "set_volume", "volume": 50 }
_10
{ "type": "command", "command": "set_shuffle", "enabled": true }
_10
{ "type": "command", "command": "add_to_queue", "uri": "spotify:track:6rqhFgbbKwnb9MLmUQDhG6" }

All control commands require a logged-in Spotify Connect session.

play.uri accepts a playable Spotify URI such as a track, album, playlist, or episode URI. add_to_queue.uri accepts only a Spotify track URI.

To set repeat mode from a raw WebSocket client, coordinate the two repeat commands:

Repeat modeCommands
offSend set_repeat_track with enabled: false, then set_repeat_context with enabled: false.
contextSend set_repeat_track with enabled: false, then set_repeat_context with enabled: true.
trackSend set_repeat_context with enabled: false, then set_repeat_track with enabled: true.

Error handling

The server responds with error for malformed JSON, unknown message types, unknown commands, missing or invalid command fields, unauthenticated access to authenticated commands, and command dispatch errors reported synchronously by the API.

Errors are sent only to the client that sent the invalid message. The connection stays open, so clients can recover by sending a valid command.

Lifecycle

  1. Start Spotify Soloist with --ws ADDR:PORT.
  2. Spotify Soloist starts the WebSocket server and writes ws.addr and ws.port in the data directory.
  3. Clients connect and receive auth_state.
  4. After Spotify Connect login, connected clients receive auth_state with logged_in: true; authenticated commands become available.
  5. On logout or session replacement, connected clients receive a new auth_state.
  6. On shutdown, Spotify Soloist removes ws.addr and ws.port.