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
System
real response from this server
{
"subsonic-response": {
"status": "ok", "version": "1.16.1", "type": "navidrome",
"serverVersion": "0.64.2 (10114574)", "openSubsonic": true
}
}Clients read this first and gate features on it. This server currently advertises:
transcodeOffset v1formPost v1songLyrics v1,v2indexBasedQueue v1transcoding v1Browsing
real response (trimmed)
"artists": { "index": [
{ "name": "#", "artist": [ { "id": "1xT8p6aQ9nH7K7ytImHcHi",
"name": "3 Doors Down", "albumCount": 2, "roles": ["albumartist","artist"] } ] },
… 25 index letters total
]}ididartist/album/song id from any listing call
Search
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
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
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"playlistIdPlayback & media
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.
idcoverArt id from any entity (e.g. al-…)sizesquare px, server-scaledBacked by the installed lyrics plugin (LRCLIB et al.) — coverage depends on it.
Annotation
idrepeatablealbumId / artistIdrepeatable alternativesidsong idsubmissionfalse = "now playing" ping insteadThis feeds Navidrome's internal play tracking — which is what the lastplayed scrobbler plugin listens to. Client scrobbles → plugin → your public page.
Play queue
idrepeatable song ids, in ordercurrentcurrent song idpositionms into current songJukebox
Remote-control the server's own audio output (when enabled) — the server becomes the speaker.
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
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
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.
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