Skip to content

IPC Protocol

The daemon protocol is length-delimited JSON over local IPC. Unix builds use Unix-domain sockets. Windows builds use Tokio named pipes. CLI, TUI, scripts, and MCP bridge through the same frame codec and request/response shape.

PlatformTransportNotes
macOSUnix-domain socketResolved under the app support/runtime path for the active instance.
LinuxUnix-domain socketPrefers $XDG_RUNTIME_DIR, then /run/user/$uid, then a private /tmp/spotuify-$uid fallback.
WindowsNamed pipeUses a \\.\pipe\... path and keeps the next pipe instance ready before handing the accepted stream to a task.

The transport is intentionally below the protocol. A Windows client still sends the same JSON request envelope as a macOS or Linux client.

{
"id": 1,
"payload": {
"type": "Request",
"cmd": "playback-get"
}
}
Terminal window
spotuify status --format json
spotuify devices --format json
spotuify search "quiet storm" --format json
spotuify queue --format json

Representative request variants:

RequestCLI surface
ClientSeedTUI/event clients only; cached startup seed
PlaybackGetspotuify status
PlaybackCommandpause, resume, toggle, next, previous, seek, volume, shuffle, repeat
DevicesListspotuify devices
DeviceTransferspotuify transfer
Searchspotuify search
QueueGetspotuify queue
QueueAddspotuify queue add
PlaylistsListspotuify playlists
PlaylistTracksspotuify playlist tracks
PlaylistAddItemsspotuify playlist add
ArtistAlbumsspotuify artist albums
FollowedArtistsspotuify artist followed
LibrarySavespotuify like, spotuify save
ShowEpisodesspotuify show episodes
EpisodeFeedspotuify episodes
CoverArtTUI art fetch, spotuify refresh-media
LyricsGetspotuify lyrics show, spotuify lyrics follow, spotuify refresh-media
SubscribeEventsspotuify lyrics follow, TUI/event clients
SetVizEnabledspotuify viz enable/disable
ReminderCreate / RemindersList / ReminderCancelspotuify reminder ...
NotificationsList / NotificationActspotuify notifications ...
CheckUpdatespotuify update, TUI/app update banners

ClientSeed is deliberately client-specific. It hydrates event-driven clients from cached playback, queue, devices, recent items, and visualizer state. It must not trigger Spotify refreshes; live refreshes belong to daemon warm/sync loops or explicit CLI requests.

refresh-media is a CLI convenience over PlaybackGet, CoverArt, and a force-refresh LyricsGet for the current track. It does not clear existing client media while the new fetch is in flight.

lyrics follow is a watch client over existing protocol calls. It subscribes to PlaybackChanged, fetches lyrics with LyricsGet on track change, and advances the active lyric line locally from playback time.

ArtistAlbums returns the full discography in one response. The daemon tags each album with album_group (album, single, compilation, or appears-on) and in_library by intersecting against the cached saved-album set. Clients section and filter from that single payload, so the “in library” toggle never needs a refetch. FollowedArtists is cache-backed and falls back to a live fetch when the cache is cold. See JSON Output for the tagged row shape.

EpisodeFeed merges the first page of episodes from followed shows, caches the feed for quick repeat reads, and supports --refresh when you want a live re-fetch. CheckUpdate returns the cached GitHub release observation and an upgrade hint for the current install method; the background daemon loop refreshes that observation on startup and every few hours.

Terminal window
spotuify analytics top --kind tracks --format json
spotuify analytics import lastfm --user your-lastfm-user --from 2024-01-01 --format json
spotuify analytics import status 018f... --format json

Representative request variants:

RequestCLI surface
AnalyticsEventsspotuify analytics events
AnalyticsTopspotuify analytics top
AnalyticsHabitsspotuify analytics habits
AnalyticsSearchspotuify analytics search
AnalyticsRediscoveryspotuify analytics rediscovery
AnalyticsRebuildspotuify analytics rebuild
AnalyticsPrunespotuify analytics prune
AnalyticsImportspotuify analytics import lastfm and the --target lastfm compatibility alias
AnalyticsImportStatusspotuify analytics import status
AnalyticsImportUnresolvedspotuify analytics import unresolved
AnalyticsImportUndospotuify analytics import undo

Last.fm import requests carry optional credentials and date bounds:

{
"type": "analytics-import",
"target": "last_fm",
"username": "your-lastfm-user",
"api_key": "lastfm-api-key",
"from_ms": 1704067200000,
"to_ms": 1735689600000,
"apply": false
}

Use apply: false for preview. The daemon resolves config/env defaults when username or api_key are omitted.

Terminal window
spotuify daemon status --format json
spotuify doctor --format json
spotuify cache status --format json
spotuify reindex --format json

Representative request variants:

RequestCLI surface
GetDaemonStatusspotuify daemon status
GetDoctorReportspotuify doctor
Reindexspotuify reindex
CacheStatusspotuify cache status
Syncspotuify sync
LogsTailspotuify logs tail
Reloadspotuify reload
Reconnectspotuify reconnect
{
"Ok": {
"data": {
"kind": "Playback",
"playback": {}
}
}
}

Analytics import responses are wrapped in ResponseData variants over IPC. The CLI unwraps these payloads for --format json.

{
"kind": "AnalyticsImportSummary",
"summary": {
"run_id": "018f...",
"provider": "lastfm",
"username": "your-lastfm-user",
"dry_run": true,
"fetched": 1200,
"stored": 0,
"duplicates": 0,
"resolved": 1138,
"promoted": 0,
"unresolved": 62,
"started_at_ms": 1735689600000,
"finished_at_ms": 1735689660000
}
}

Import status, unresolved, and undo responses use:

AnalyticsImportRunStatus { status }
AnalyticsImportUnresolved { entries }
AnalyticsImportUndoSummary { summary }

Errors are typed:

{
"Error": {
"message": "no active device",
"kind": "provider",
"code": "provider",
"retryable": false
}
}

Error kinds:

auth
invalid_request
network
provider
rate_limited
unsupported
internal

The daemon broadcasts state changes so clients do not have to poll forever.

playback-changed
queue-changed
devices-changed
playlists-changed
library-changed
search-updated
search-page
search-complete
search-failed
event-stream-lagged
sync-started
sync-finished
mutation-finished
analytics-import-progress
rate-limited
auth-error
mutation-accepted
mutation-finalized
schema-compat
player-ready
player-degraded
premium-required
session-disconnected
player-failed
listen-qualified
operation-recorded
operation-undone
config-reloaded
spectrum-frame
viz-source-changed
reminder-due
reminders-changed
update-available

Import progress events are daemon-owned and broadcast to subscribers:

{
"type": "analytics-import-progress",
"run_id": "018f...",
"provider": "lastfm",
"username": "your-lastfm-user",
"phase": "resolving",
"fetched": 1200,
"stored": 800,
"resolved": 760,
"promoted": 760,
"unresolved": 40,
"message": "resolving Last.fm scrobbles"
}