The API, in one page

Everything on this server hangs off /rest/. One auth pattern, JSON responses, and a live console below. Base URL: https://music.smartass.app/rest

Quickstart

export LP_U=you LP_P=yourpassword
export ND=https://music.smartass.app/rest

curl -s "$ND/ping?u=$LP_U&p=$LP_P&v=1.16.1&c=myapp&f=json"

Every request carries the same five params: u, p, v (API version you target), c (your app's name), f=json.

Authentication

Two forms. Plaintext (p) for trusted scripts on your own machine; salted token for anything shared or logged.

uusername ppassword (plaintext) — or… t / stoken / salt: t = md5(password + salt), s = random string. Send these instead of p.
# token auth
S=$(openssl rand -hex 6)
T=$(printf '%s' "$LP_P$S" | md5sum | cut -d' ' -f1)
curl -s "$ND/ping?u=$LP_U&t=$T&s=$S&v=1.16.1&c=myapp&f=json"

Try it live

# your curl command appears here
# response appears here
Credentials never leave this page — the request goes straight from your browser to the API. If the browser blocks it (CORS), use the curl version; it always works.

System

pingliveness + server identity
real response from this server
{
  "subsonic-response": {
    "status": "ok", "version": "1.16.1", "type": "navidrome",
    "serverVersion": "0.64.2 (10114574)", "openSubsonic": true
  }
}
getOpenSubsonicExtensionswhat this server supports beyond stock Subsonic

Clients read this first and gate features on it. This server currently advertises:

transcodeOffset v1formPost v1songLyrics v1,v2indexBasedQueue v1transcoding v1
getLicenselegacy; always valid here

Browsing

getArtistsID3 index of all artists (tag-based)
real response (trimmed)
"artists": { "index": [
  { "name": "#", "artist": [ { "id": "1xT8p6aQ9nH7K7ytImHcHi",
    "name": "3 Doors Down", "albumCount": 2, "roles": ["albumartist","artist"] } ] },
  … 25 index letters total
]}
getArtist / getAlbum / getSongdrill down by id
idartist/album/song id from any listing call
getIndexesfolder-based browsing (if you think in directories)
search3the one you'll use: artists + albums + songs, ID3-aware
querysubstring, matches title/artist/album songCountdefault 20 artistCount / albumCountdefault 20
curl -s "$ND/search3?u=$LP_U&p=$LP_P&v=1.16.1&c=demo&f=json&query=paradise"
real response (trimmed)
"searchResult3": { "song": [
  { "title": "Gangsta's Paradise (feat. L.V.)", "artist": "Coolio • L.V.", "album": "Greatest Hits" },
  { "title": "Welcome to Paradise", "artist": "Green Day", "album": "Dookie" }
]}

Album lists

getAlbumList2shelves: recent, random, starred, by year/genre
typerandom · newest · recent · frequent · starred · highest · byYear · byGenre · alphabeticalByName size / offsetpagination fromYear / toYearwith type=byYear genrewith type=byGenre
# 10 random albums — the "surprise me" shelf
curl -s "$ND/getAlbumList2?u=$LP_U&p=$LP_P&v=1.16.1&c=demo&f=json&type=random&size=10"
real response (one album)
{ "id": "7DqQydqN4prvR7E2KEFGB4", "name": "Break the Stigma",
  "artist": "Tron Austin", "songCount": 5, "duration": 974,
  "year": 2026, "genre": "Alternative & Indie",
  "played": "2026-10-14T05:30:16.84Z", "coverArt": "al-7Dq…" }

Playlists

getPlaylists / getPlaylistlist / fetch with tracks
createPlaylistmake or replace
namenew playlist songIdrepeatable — initial contents playlistIdif updating instead of creating
# "Friday" playlist from a search
IDS=$(curl -s "$ND/search3?u=$LP_U&p=$LP_P&v=1.16.1&c=cron&f=json&query=friday" \
      | jq -r '.["subsonic-response"].searchResult3.song[].id')
QS=$(printf '&songId=%s' $IDS)
curl -s "$ND/createPlaylist?u=$LP_U&p=$LP_P&v=1.16.1&c=cron&f=json&name=Friday$QS"
updatePlaylist / deletePlaylistedit or remove by playlistId

Playback & media

streamthe audio itself — returns bytes, not JSON
idsong id maxBitRatekbps cap, triggers transcode (e.g. 128) formatmp3 · opus · aac · raw (no transcode) timeOffsetstart N seconds in ext
# stream a 128k opus transcode
curl -s "$ND/stream?u=$LP_U&p=$LP_P&v=1.16.1&c=demo&id=SONGID&maxBitRate=128&format=opus" -o song.opus

Playback is client-side — the server hands you bytes. Gapless, crossfade, EQ: all client features. Pick a client that has them.

downloadoriginal file, no transcode
getCoverArtartwork image bytes
idcoverArt id from any entity (e.g. al-…)sizesquare px, server-scaled
getLyricsBySongIdsynced/plain lyrics ext

Backed by the installed lyrics plugin (LRCLIB et al.) — coverage depends on it.

Annotation

star / unstarfavorite songs, albums, artists
idrepeatablealbumId / artistIdrepeatable alternatives
setRating0–5 per song
scrobblesubmit a play — submission only, no read-back
idsong idsubmissionfalse = "now playing" ping instead

This feeds Navidrome's internal play tracking — which is what the lastplayed scrobbler plugin listens to. Client scrobbles → plugin → your public page.

Play queue

savePlayQueue / getPlayQueuesync "now playing" queues across devices
idrepeatable song ids, in ordercurrentcurrent song idpositionms into current song

Jukebox

Remote-control the server's own audio output (when enabled) — the server becomes the speaker.

jukeboxControlactions: set · start · stop · skip · add · clear · get · status · setGain
curl -s "$ND/jukeboxControl?u=$LP_U&p=$LP_P&v=1.16.1&c=demo&f=json&action=status"

Off by default — enable with ND_JUKEBOX_ENABLED=true + audio devices on the host. This server streams from a read-only network mount, so jukebox is currently disabled.

Radio & podcasts

getInternetRadioStations / createInternetRadioStationlive streams in the same library UI
getPodcasts / getNewestPodcasts / downloadPodcastEpisodepodcast subscriptions server-side

Server extensions

This server is openSubsonic: true and advertises:

transcodingper-request format/bitrate negotiation transcodeOffsetresume mid-transcode songLyricsv2 = synced multi-lyric via plugin formPostPOST bodies instead of query strings (keeps passwords out of URLs/logs) indexBasedQueueefficient play-queue sync
# formPost: credentials in the body, not the URL
curl -s -X POST "$ND/ping" -d "u=$LP_U" -d "p=$LP_P" -d "v=1.16.1" -d "c=demo" -d "f=json"

Known gaps

getPlayHistorydoes not exist

OpenSubsonic has no play-history read. scrobble is write-only. That's why the lastplayed integration is a push plugin listening to Navidrome's internal scrobble events rather than a poller like the Plex source.

crossfade / gaplessclient-side, always

The server streams files; playback is the client's job. Symfonium, Tempo and Feishin all do crossfade.

Clients that speak this

Point any of these at https://music.smartass.app:

SymfoniumAndroid · crossfade, gapless, offline, smart playlists TempoAndroid · free, crossfade, clean Feishindesktop · crossfade, plugin-aware Supersonicdesktop · fast, minimal AmperfyiOS/macOS SoloundAndroid · what you're using now

music-docs · generated against a live Navidrome 0.64.2 · open the player