# Navidrome

> This skill should be used when the user wants to interact with their Navidrome self-hosted music server — checking server status, browsing artists or albums, searching the music library, listing playlists, viewing now-playing or recently played tracks, or accessing starred items. Triggers include: "what's playing on Navidrome", "show my playlists", "search for an artist", "recent albums", "Navidrome status".

- Skill: `dinglebear-ai/navidrome` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add dinglebear-ai/navidrome`
- Raw SKILL.md: https://api.skillmd.com/api/skills/dinglebear-ai/navidrome/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: dinglebear-ai (https://skillmd.com/u/dinglebear-ai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/dinglebear-ai/navidrome

---


# Navidrome

Self-hosted music streaming server. Navidrome implements the **Subsonic API**, served under `/rest/*` and returning JSON when `f=json` is passed. This skill calls it directly with `curl`.

## Configuration

The user sets URL / username / password in the plugin's user configuration when the plugin is enabled. A `SessionStart` hook writes those into a 600-mode file:

```
${XDG_CONFIG_HOME:-$HOME/.config}/lab-navidrome/config.env
```

> Why a file and not env vars: Claude Code injects `CLAUDE_PLUGIN_OPTION_*` only into plugin subprocesses (hooks/MCP/LSP), **not** into the Bash tool that runs these commands. The hook (a subprocess) reads them and materializes this file; the skill sources it.

Load it first. If it is missing, fall back to `~/.lab/.env`; this docs/homelab
setup keeps `NAVIDROME_URL`, `NAVIDROME_USERNAME`, and `NAVIDROME_PASSWORD`
there. If neither source has all three values, tell the user to set the
Navidrome URL/username/password in plugin settings or `~/.lab/.env`.

```bash
CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/lab-navidrome/config.env"
if [ -f "$CONFIG" ]; then
  . "$CONFIG"
elif [ -f "$HOME/.lab/.env" ]; then
  set -a; . "$HOME/.lab/.env"; set +a
fi
[ -n "${NAVIDROME_URL:-}" ] && [ -n "${NAVIDROME_USERNAME:-}" ] && [ -n "${NAVIDROME_PASSWORD:-}" ] || {
  echo "navidrome not configured — set URL/username/password in plugin settings or ~/.lab/.env"
}
```

If Subsonic requests through `NAVIDROME_URL` fail with proxy/auth errors, use a
direct backend URL from plugin settings or local env for API calls.

## Authentication (do this first, every session)

Prefer `scripts/navidrome-api.sh` for common read-only requests. It sources the
generated Navidrome config or `~/.lab/.env`, derives salted Subsonic auth for
each call, and exposes `ping`, `artists`, `albums`, `album`, `artist`, `search`,
`playlists`, `playlist`, `now-playing`, `starred`, and `scan-status`.

Subsonic auth is token-based: generate a random `salt`, then `token = md5(password + salt)`. The raw password never goes over the wire. Build shared auth args once and reuse them with `curl --get`; this keeps usernames and query terms URL-encoded safely:

```bash
URL="${NAVIDROME_URL%/}"
SALT="$(openssl rand -hex 8)"
TOKEN="$(printf '%s%s' "$NAVIDROME_PASSWORD" "$SALT" | md5sum | cut -d' ' -f1)"
AUTH_ARGS=(
  --data-urlencode "u=${NAVIDROME_USERNAME}"
  --data-urlencode "t=${TOKEN}"
  --data-urlencode "s=${SALT}"
  --data-urlencode "v=1.16.1"
  --data-urlencode "c=lab"
  --data-urlencode "f=json"
)
```

`v` is the Subsonic protocol version, `c` is the client name (any string), `f=json` requests JSON. Every call below passes `"${AUTH_ARGS[@]}"`.

> Never echo `$NAVIDROME_PASSWORD` or the derived `$TOKEN`. A fresh salt+token per session is fine; you do not need a new one per request.

## Common operations

| Intent | Request |
|---|---|
| Health / auth check | `curl -sS --get "$URL/rest/ping.view" "${AUTH_ARGS[@]}"` |
| List all artists | `curl -sS --get "$URL/rest/getArtists.view" "${AUTH_ARGS[@]}"` |
| List albums (newest 20) | `curl -sS --get "$URL/rest/getAlbumList2.view" "${AUTH_ARGS[@]}" --data-urlencode type=newest --data-urlencode size=20` |
| Album details + tracks | `curl -sS --get "$URL/rest/getAlbum.view" "${AUTH_ARGS[@]}" --data-urlencode "id=<albumId>"` |
| Search everything | `curl -sS --get "$URL/rest/search3.view" "${AUTH_ARGS[@]}" --data-urlencode "query=<term>"` |
| List playlists | `curl -sS --get "$URL/rest/getPlaylists.view" "${AUTH_ARGS[@]}"` |
| Playlist contents | `curl -sS --get "$URL/rest/getPlaylist.view" "${AUTH_ARGS[@]}" --data-urlencode "id=<playlistId>"` |
| Now playing | `curl -sS --get "$URL/rest/getNowPlaying.view" "${AUTH_ARGS[@]}"` |
| Starred items | `curl -sS --get "$URL/rest/getStarred2.view" "${AUTH_ARGS[@]}"` |

`getAlbumList2` `type` accepts `newest`, `recent`, `frequent`, `random`, `alphabeticalByName`, `starred`, etc. Keep user-supplied IDs and query terms in `--data-urlencode` args.

Full endpoint and response reference: [`references/api.md`](references/api.md).

## Checking the response

Subsonic wraps every reply: `{"subsonic-response":{"status":"ok"|"failed", ...}}`. On `failed`, read `.error.code` / `.error.message` (e.g. code 40 = wrong username/password, code 70 = data not found). Pipe through `jq '."subsonic-response"'` to read it.

## When NOT to use this skill

- The user wants to upload, edit tags, or change library/server settings — Navidrome manages those through its web UI and scanner, not the Subsonic API.
- The user is asking about a different homelab service — load that service's skill.
- Streaming actual audio bytes (`stream.view`) into the terminal is rarely useful; prefer metadata endpoints unless the user explicitly wants a download.

