The spotify toolset (plugins/spotify) and its loopback-PKCE login (hermes_cli/auth_spotify.py)
now live in NousResearch/hermes-spotify, installed from the plugin catalog. Same pattern as the
Home Assistant move (fb9fc8a3dd): a LEFT_CORE row installs the plugin for every home that was
using it (a Spotify login in auth.json, or the spotify toolset in platform_toolsets) on
`hermes update` / first start; the toolset name, tool names, schemas, env vars and stored login
(providers.spotify) are unchanged, so nobody re-authenticates.
The one user-visible change is the login command: plugins cannot extend `hermes auth`, so the
plugin registers `hermes spotify login|status|logout`. LeftCoreFeature gains a `cli` field and
moved_command_hint() turns `hermes auth [status|logout] spotify`, and `hermes spotify` while the
plugin is not loaded, into a line naming the new command and the install/enable step for this
home. The dashboard's "needs login" hint names the new command too.
Core drops the Spotify auth constants/dispatch, the CONFIGURABLE_TOOLSETS row and post-setup
step, the core `spotify` entries in the shared-metrics toolset enums, its tests, and the docs
(en + zh-Hans) now point at `hermes plugins install spotify` / `hermes spotify login`.
14 KiB
Spotify
Hermes can control Spotify — playback, queue, search, playlists, saved tracks/albums, and listening history — through the official spotify plugin from the plugin catalog. It uses Spotify's Web API with PKCE OAuth. The plugin is maintained by Nous Research in NousResearch/hermes-spotify and is not part of Hermes core. Tokens are stored in ~/.hermes/auth.json and refreshed automatically on 401; you only log in once per machine (refresh tokens expire after ~6 months; re-run hermes spotify login when they do).
Unlike Hermes' built-in OAuth integrations, Spotify requires every user to register their own lightweight developer app. Spotify does not let third parties ship a public OAuth app that anyone can use. It takes about two minutes and hermes spotify login walks you through it.
Install
hermes plugins install spotify
Plugins are installed per profile. To use Spotify in another profile, install it there too (hermes -p <profile> plugins install spotify).
:::info Upgrading from a release that bundled Spotify
Nothing to do. Every profile that was already using Spotify (a Spotify login in its auth.json, or the spotify toolset listed in platform_toolsets) gets the plugin installed automatically from the catalog by hermes update, or the first time the profile starts if that step could not run (this honours security.allow_lazy_installs). Your login, the spotify toolset and the tool names carry over unchanged. The one visible change: hermes auth spotify is now hermes spotify login (and hermes auth status spotify / hermes auth logout spotify are hermes spotify status / hermes spotify logout); the old spelling prints the new one. If you later remove the plugin (hermes plugins remove spotify), it stays removed.
:::
Prerequisites
- A Spotify account. Free works for search, playlist, library, and activity tools. Premium is required for playback control (play, pause, skip, seek, volume, queue add, transfer).
- Hermes Agent installed and running, with the
spotifyplugin installed. - For playback tools: an active Spotify Connect device — the Spotify app must be open on at least one device (phone, desktop, web player, speaker) so the Web API has something to control. If nothing is active you'll get a
403 Forbiddenwith a "no active device" message; open Spotify on any device and retry.
Setup
1. Enable the toolset
The plugin's spotify toolset is opt-in, so users who don't want it don't ship extra tool schemas on every API call. Turn it on in hermes tools (toggle 🔌 Spotify, then save) or with:
hermes tools enable spotify
2. Log in
hermes spotify login
The 7 Spotify tools only reach the agent once you're logged in.
If no HERMES_SPOTIFY_CLIENT_ID is set, Hermes walks you through the app registration inline:
- Opens
https://developer.spotify.com/dashboardin your browser - Prints the exact values to paste into Spotify's "Create app" form
- Prompts you for the Client ID you get back
- Saves it to
~/.hermes/.envso future runs skip this step - Continues straight into the OAuth consent flow
After you approve, tokens are written under providers.spotify in ~/.hermes/auth.json. The active inference provider is NOT changed — Spotify auth is independent of your LLM provider.
Creating the Spotify app (what the wizard asks for)
When the dashboard opens, click Create app and fill in:
| Field | Value |
|---|---|
| App name | anything (e.g. hermes-agent) |
| App description | anything (e.g. personal Hermes integration) |
| Website | leave blank |
| Redirect URI | http://127.0.0.1:43827/spotify/callback |
| Which API/SDKs? | check Web API |
Agree to the terms and click Save. On the next page click Settings → copy the Client ID and paste it into the Hermes prompt. That's the only value Hermes needs — PKCE doesn't use a client secret.
Running over SSH / in a headless environment
If SSH_CLIENT or SSH_TTY is set, Hermes skips the automatic browser open during both the wizard and the OAuth step. Copy the dashboard URL and the authorization URL Hermes prints, open them in a browser on your local machine, and proceed normally — the local HTTP listener still runs on the remote host on port 43827. Your laptop's browser can't reach the remote loopback without an SSH local-forward:
ssh -N -L 43827:127.0.0.1:43827 user@remote-host
For jump-box / bastion setups and other gotchas (mosh, tmux, port conflicts), see OAuth over SSH / Remote Hosts.
Verify
hermes spotify status
Shows whether tokens are present and when the access token expires. Refresh is automatic: when any Spotify API call returns 401, the client exchanges the refresh token and retries once. Refresh tokens persist across Hermes restarts, so you only re-auth if you revoke the app in your Spotify account settings or run hermes spotify logout.
Using it
Once logged in, the agent has access to 7 Spotify tools. You talk to the agent naturally — it picks the right tool and action. For the best behavior, the agent loads a companion skill that teaches canonical usage patterns (single-search-then-play, when not to preflight get_state, etc.).
> play some miles davis
> what am I listening to
> add this track to my Late Night Jazz playlist
> skip to the next song
> make a new playlist called "Focus 2026" and add the last three songs I played
> which of my saved albums are by Radiohead
> search for acoustic covers of Blackbird
> transfer playback to my kitchen speaker
Tool reference
All playback-mutating actions accept an optional device_id to target a specific device. If omitted, Spotify uses the currently active device.
spotify_playback
Control and inspect playback, plus fetch recently played history.
| Action | Purpose | Premium? |
|---|---|---|
get_state |
Full playback state (track, device, progress, shuffle/repeat) | No |
get_currently_playing |
Just the current track (returns empty on 204 — see below) | No |
play |
Start/resume playback. Optional: context_uri, uris, offset, position_ms |
Yes |
pause |
Pause playback | Yes |
next / previous |
Skip track | Yes |
seek |
Jump to position_ms |
Yes |
set_repeat |
state = track / context / off |
Yes |
set_shuffle |
state = true / false |
Yes |
set_volume |
volume_percent = 0-100 |
Yes |
recently_played |
Last played tracks. Optional limit, before, after (Unix ms) |
No |
spotify_devices
| Action | Purpose |
|---|---|
list |
Every Spotify Connect device visible to your account |
transfer |
Move playback to device_id. Optional play: true starts playback on transfer |
Home Assistant-managed speakers
If Home Assistant manages speakers that already support Spotify Connect (for example Sonos, Echo, Nest, or other Connect-capable speakers), they appear in spotify_devices list automatically whenever Spotify can see them. Hermes does not need a Home Assistant ↔ Spotify bridge for this path — Spotify handles the device routing natively.
Ask Hermes to transfer playback by the speaker's display name (for example, “transfer Spotify to the kitchen speaker”), or call spotify_devices list and pass the exact device_id to spotify_devices transfer when scripting. If the speaker is missing, open the Spotify app or the speaker's Spotify integration once so Spotify registers it as an active Connect target.
spotify_queue
| Action | Purpose | Premium? |
|---|---|---|
get |
Currently queued tracks | No |
add |
Append uri to the queue |
Yes |
spotify_search
Search the catalog. query is required. Optional: types (array of track / album / artist / playlist / show / episode), limit, offset, market.
spotify_playlists
| Action | Purpose | Required args |
|---|---|---|
list |
User's playlists | — |
get |
One playlist + tracks | playlist_id |
create |
New playlist | name (+ optional description, public, collaborative) |
add_items |
Add tracks | playlist_id, uris (optional position) |
remove_items |
Remove tracks | playlist_id, uris (+ optional snapshot_id) |
update_details |
Rename / edit | playlist_id + any of name, description, public, collaborative |
spotify_albums
| Action | Purpose | Required args |
|---|---|---|
get |
Album metadata | album_id |
tracks |
Album track list | album_id |
spotify_library
Unified access to saved tracks and saved albums. Pick the collection with the kind arg.
| Action | Purpose |
|---|---|
list |
Paginated library listing |
save |
Add ids / uris to library |
remove |
Remove ids / uris from library |
Required: kind = tracks or albums, plus action.
Feature matrix: Free vs Premium
Read-only tools work on Free accounts. Anything that mutates playback or the queue requires Premium.
| Works on Free | Premium required |
|---|---|
spotify_search (all) |
spotify_playback — play, pause, next, previous, seek, set_repeat, set_shuffle, set_volume |
spotify_playback — get_state, get_currently_playing, recently_played |
spotify_queue — add |
spotify_devices — list |
spotify_devices — transfer |
spotify_queue — get |
|
spotify_playlists (all) |
|
spotify_albums (all) |
|
spotify_library (all) |
Scheduling: Spotify + cron
Because Spotify tools are regular Hermes tools, a cron job running in a Hermes session can trigger playback on any schedule. No new code needed.
Morning wake-up playlist
hermes cron add \
--name "morning-commute" \
"0 7 * * 1-5" \
"Transfer playback to my kitchen speaker and start my 'Morning Commute' playlist. Volume to 40. Shuffle on."
What happens at 7am every weekday:
- Cron spins up a headless Hermes session.
- Agent reads the prompt, calls
spotify_devices listto find "kitchen speaker" by name, thenspotify_devices transfer→spotify_playback set_volume→spotify_playback set_shuffle→spotify_search+spotify_playback play. - Music starts on the target speaker. Total cost: one session, a few tool calls, no human input.
Wind-down at night
hermes cron add \
--name "wind-down" \
"30 22 * * *" \
"Pause Spotify. Then set volume to 20 so it's quiet when I start it again tomorrow."
Gotchas
- An active device must exist when the cron fires. If no Spotify client is running (phone/desktop/Connect speaker), playback actions return
403 no active device. For morning playlists, the trick is to target a device that's always on (Sonos, Echo, a smart speaker) rather than your phone. - Premium required for anything that mutates playback — play, pause, skip, volume, transfer. Read-only cron jobs (scheduled "email me my recently played tracks") work fine on Free.
- The cron agent inherits your active toolsets. Spotify must be enabled in
hermes toolsfor the cron session to see the Spotify tools. - Cron jobs run with
skip_memory=Trueso they don't write to your memory store.
Full cron reference: Cron Jobs.
Sign out
hermes spotify logout
Removes tokens from ~/.hermes/auth.json. To also clear the app config, delete HERMES_SPOTIFY_CLIENT_ID (and HERMES_SPOTIFY_REDIRECT_URI if you set it) from ~/.hermes/.env, or run the wizard again.
To revoke the app on Spotify's side, visit Apps connected to your account and click REMOVE ACCESS.
Troubleshooting
403 Forbidden — Player command failed: No active device found — You need Spotify running on at least one device. Open the Spotify app on your phone, desktop, or web player, start any track for a second to register it, and retry. spotify_devices list shows what's currently visible.
403 Forbidden — Premium required — You're on a Free account trying to use a playback-mutating action. See the feature matrix above.
204 No Content on get_currently_playing — nothing is currently playing on any device. This is Spotify's normal response, not an error; Hermes surfaces it as an explanatory empty result (is_playing: false).
INVALID_CLIENT: Invalid redirect URI — the redirect URI in your Spotify app settings doesn't match what Hermes is using. The default is http://127.0.0.1:43827/spotify/callback. Either add that to your app's allowed redirect URIs, or set HERMES_SPOTIFY_REDIRECT_URI in ~/.hermes/.env to whatever you registered.
429 Too Many Requests — Spotify's rate limit. Hermes returns a friendly error; wait a minute and retry. If this persists, you're probably running a tight loop in a script — Spotify's quota resets roughly every 30 seconds.
401 Unauthorized keeps coming back — Your refresh token was revoked (usually because you removed the app from your account, or the app was deleted). Run hermes spotify login again.
Wizard doesn't open the browser — If you're over SSH or in a container without a display, Hermes detects it and skips the auto-open. Copy the dashboard URL it prints and open it manually.
Advanced: custom scopes
By default Hermes requests the scopes needed for every shipped tool. Override if you want to restrict access:
hermes spotify login --scope "user-read-playback-state user-modify-playback-state playlist-read-private"
Scope reference: Spotify Web API scopes. If you request fewer scopes than a tool needs, that tool's calls will fail with 403.
Advanced: custom client ID / redirect URI
hermes spotify login --client-id <id> --redirect-uri http://localhost:3000/callback
Or set them permanently in ~/.hermes/.env:
HERMES_SPOTIFY_CLIENT_ID=<your_id>
HERMES_SPOTIFY_REDIRECT_URI=http://localhost:3000/callback
The redirect URI must be allow-listed in your Spotify app's settings. The default works for almost everyone — only change it if port 43827 is taken.
Where things live
| File | Contents |
|---|---|
~/.hermes/auth.json → providers.spotify |
access token, refresh token, expiry, scope, redirect URI |
~/.hermes/.env |
HERMES_SPOTIFY_CLIENT_ID, optional HERMES_SPOTIFY_REDIRECT_URI |
| Spotify app | owned by you at developer.spotify.com/dashboard; contains the Client ID and the redirect URI allow-list |