Use this skill for Spotify playlist creation, song selection, listening-history lookups, and lightweight queue actions.
The tools below are means to musical understanding, not API documentation to follow. Understand the musical goal first, translate it to data (audio features, artists, temporal filters), chain tools creatively, and explain your choices so the user understands the thinking.
(v2), (softer), or (more dancey).--json mode when composing multi-step workflows. Parse JSON output; do not scrape text.taste show to check the user's taste profile. Excluded artists are enforced automatically, but favorite genres and notes may inform your seed/genre choices.Before your first CLI call, check that the environment is set up:
Check for .venv — if the .venv directory does not exist in the skill root, tell the user to run setup first:
bash
bash scripts/setup.sh
Do not attempt to create the venv yourself or install dependencies manually. The setup script handles everything including credential prompts.
Check auth — once .venv exists, verify authentication:
bash
.venv/bin/python scripts/spotify_cli.py --json status
If authenticated is false, tell the user to run the auth flow:
bash
.venv/bin/python scripts/spotify_auth.py
Do not attempt other commands until status returns authenticated: true.
ErrorCauseWhat to tell the userinvalid_client or "client ID invalid"The .env file has placeholder values or incorrect credentialsOpen .env in the skill root, replace your_client_id_here / your_client_secret_here with real credentials from https://developer.spotify.com/dashboard, then re-run spotify_auth.py``credentials not found or "placeholder values"Setup ran but user didn't edit .envSame as above — they need to create a Spotify app and paste the credentialsno such file or directory: .venv/bin/python``.venv doesn't existRun bash scripts/setup.sh from the skill root firstredirect_uri_mismatchSpotify app's redirect URI doesn't matchIn the Spotify developer dashboard, set the redirect URI to http://127.0.0.1:8888/callback
Before calling any CLI command, be aware of these active Spotify API regressions. They will cause silent failures or 403/400 errors if you ignore them.
--limit values above 10 on search return 400 Invalid limit. The CLI handles this internally, but do not expect more than 10 results from a single search call.GET /artists?ids=) returns 403. The code falls back to individual GET /artists/{id} calls automatically.tracks to items and track to item. The code normalizes both renames automatically.GET /playlists/{id}/items returns 403 for some third-party playlists (not owned by the authenticated user). User-owned playlists work normally.GET /recommendations, GET /audio-features, and GET /artists/{id}/related-artists are removed entirely. The recommend command works around this using ReccoBeats as the primary recommendation + audio features backend, with Spotify-only fallback.api.reccobeats.com). No auth required. Spotify handles playlists, search, playback, and listening history. ReccoBeats handles recommendations and audio features. If ReccoBeats is unavailable, the system falls back to Spotify-only recommendation (search + artist top tracks + genre scoring).null for genres. Genre-based filtering in blend-dna uses MusicBrainz as the primary genre source (free, no API key, cached to disk for 30 days), with Spotify genre-search membership as a fallback when MusicBrainz has no data for an artist.GET /artists/{id}/top-tracks returns 403. The artist-top-tracks command falls back to search automatically.See references/implementation-notes.md for full details on each regression and workaround.
For simpler cases:
Request typeApproach"Add [specific songs] to [playlist]"search → add-to-playlist"Playlist from my recent listening"create-from-recent (one command)"Playlist from my top tracks"create-from-top (one command)"Queue [song]"search if needed → queue"What have I been listening to?"recent / top-tracks / top-artists"Latest song by [artist]"artist-releases → add-to-playlist or queue"Playlist of only X and Y"artist-top-tracks + artist-releases → create-playlist + add-to-playlistArtist-centric / vibe-based / fusionUse the 6-step workflow below
Pick the tool based on the request shape:
Request typeToolWhy"Songs like X" / "more of this vibe"recommend --seed-urisBest for expanding from known tracks"Artists like X, Y, Z" / scene-basedrecommend --artistsDiscovers collaborator network + seeds from those artists"Blend of X and Y" / fusion of two aestheticsblend-dnaProfiles both sides and finds the overlap zone. Use this first for fusion requests — don't start with recommendQuick sonic exploration from a single trackdiscoverLightweight wrapper: returns similar tracks without scores. Good for Ring 1+ in concentric discoveryManual curation / filling gapssearch + audio-featuresWhen you need specific tracks and want to verify they fit
Key distinction: recommend returns scored results with reasons (useful for explaining choices). discover returns raw similar tracks without scores (faster, good for chaining). blend-dna is the only tool that profiles two distinct groups and finds the middle ground.
Map user language to CLI flags:
User saysFlagEffect"no X"--exclude-artists "X"Filters X out entirely"blend of A and B"--max-per-artist 2Prevents one artist from dominating"more emphasis on Y"--boost-artists "Y"+0.15 score boost"deep cuts" / "obscure"--popularity-target 25Favors less popular tracks"bangers" / "hits"--popularity-target 75Favors popular tracks"energetic" / "upbeat"Use seed tracks with high energy/danceabilityAudio features steer ReccoBeats recommendations"chill" / "mellow"Use seed tracks with low energy + high acousticnessAudio features steer ReccoBeats recommendations"nothing from [playlist]"--exclude-uris <uris...> ( recommend only)Removes specific tracks from candidates. Not available on blend-dna — exclude manually from final track selection."like X meets Y" / "blend of X and Y"blend-dna --group-a <X tracks> --group-b <Y tracks>Finds the overlap zone between two aesthetics"lean more toward X"blend-dna --weight-a 0.7 (or 0.3 for group B)Shifts the blend target toward one group"about an hour" / duration target--target-duration 60Trims results to fit target duration in minutes"indie rock / emo / shoegaze"--genres "indie rock" "emo"Sources candidates from genre search and boosts genre-matched tracks (available on both recommend and blend-dna)
All commands go through .venv/bin/python scripts/spotify_cli.py <command>. Pass --json before the command for machine-readable output.
CommandArgumentsNotesstatus(none)Check auth and connection status. Returns authenticated, credentials_found, token_exists, token_valid, and user.
The user's musical preferences are stored in taste_profile.json and persist across sessions. Excluded artists are automatically merged into --exclude-artists on every recommend, blend-dna, and score-by-features call — the agent never needs to pass them manually.
When you learn something about the user's taste (they dislike an artist, love a genre, have a preference), save it to the taste profile so future agents benefit too.
CommandArgumentsNotestaste show(none)Show the full taste profiletaste exclude``<artist>Never include this artist in playliststaste unexclude``<artist>Remove an artist from the exclusion listtaste fav-genre``<genre>Record a favorite genretaste unfav-genre``<genre>Remove a favorite genretaste fav-artist``<artist>Record a favorite artisttaste unfav-artist``<artist>Remove a favorite artisttaste note``<text>Add a free-text note about the user's tastetaste rm-note``<index>Remove a note by index
CommandArgumentsNotessearch``<query> --limit NMax 10 results per call (Spotify API cap)list-playlists``--limit NLists user's playlistslist-playlist``<playlist_id> --limit NLists tracks in a playlist. May return 403 for third-party playlists — if so, try analyze-playlist which has a fallbacktop-tracks``--time-range {short,medium,long}_term --limit NUser's top trackstop-artists``--time-range {short,medium,long}_term --limit NUser's top artistsrecent``--limit NRecently played tracksartist-releases``<artist_name> --limit N --include-groups TYPESRecent releases with lead track URIs. Default groups: single,album``artist-top-tracks``<artist_name>Top tracks for an artist (~10). Complements artist-releases for catalog access
CommandArgumentsReturnsanalyze-playlist``<playlist_id> --max-tracks N (default 200, 0=unlimited)track_count, total_duration_ms, avg_popularity, popularity_range, artist_distribution, top_artists, genre_clusters, top_genres, explicit_ratio, representative_seed_uris, audio_features (avg/min/max per feature), audio_features_sample_size, tracks. Large playlists are sampled automatically — output includes sampled, declared_total, and warning when this happens.audio-features``<track_id>...Per-track audio features: energy, danceability, valence, acousticness, instrumentalness, speechiness, liveness, tempo, loudness. No Spotify auth required. JSON output includes _not_found array when any requested track IDs have no features available.score-by-features``--target-playlist ID --target-energy F --target-valence F --target-danceability F --target-acousticness F --target-tempo F --target-loudness F --candidate-ids ID... --candidate-artists NAME... --exclude-artists NAME... --limit N (default 20) --max-distance FScores candidates by weighted audio-feature distance from a target profile. Target from playlist avg and/or explicit values. Candidates from track IDs and/or artist search. JSON output includes distance and per-feature feature_deltas (value, target, delta in natural scale).
CommandArgumentsNotesrecommend``--seed-uris URI... --seed-playlist ID --genres GENRE... --artists NAME... --exclude-artists NAME... --exclude-uris URI... --boost-artists NAME... --max-per-artist N (default 3) --popularity-target N --limit N (default 20) --target-duration MINUTES --add PLAYLIST_ID --create NAMEAt least one seed source required. Output includes score and reasons per track. --target-duration trims results to fit the specified duration.blend-dna``--group-a URI... --group-a-playlist ID --group-b URI... --group-b-playlist ID --label-a TEXT --label-b TEXT --weight-a W (0.0-1.0, default 0.5) --search-artists NAME... --search-queries QUERY... --candidate-uris URI... --genres GENRE... --exclude-artists NAME... --boost-artists NAME... --max-per-artist N --max-tracks-per-group N (default 100, 0=unlimited) --limit N --target-duration MINUTES --add PLAYLIST_ID --create NAMEBlends audio DNA of two track groups. --genres sources candidates from genre search and boosts genre-matched tracks (penalizes unaffiliated tracks). Output includes search_artist_misses when specified artists don't appear in final results. --target-duration trims results to fit.discover``<seed_uris...> --limit N --add PLAYLIST_IDWrapper around recommend without scores
CommandArgumentsNotescreate-playlist``<name> --public --description TEXTCreates empty playlistadd-to-playlist``<playlist_id> <track_uris...>URIs must be spotify:track:ID format. Validated before sendingremove-from-playlist``<playlist_id> <track_uris...>Confirm with user firstsearch-and-add``<playlist_id> "Artist - Title"...For adding specific known tracks. Format must be "Artist - Title". Tracks not found are silently skipped with a warningupdate-playlist``<playlist_id> --name TEXT --description TEXT --public/ --privateMetadata changes
CommandArgumentsNotesqueue``<track_uri>Requires active Spotify devicecreate-from-recent``<name> --limit N --public --description TEXTOne-step playlist from recent playscreate-from-top``<name> --time-range {short,medium,long}_term --limit N --public --description TEXTOne-step playlist from top tracks
For any non-trivial request (vibes, transformations, fusions, discovery), follow all six steps. For trivial cases (specific songs to add, queue a track), skip to the quick-reference table above.
Restate what the user wants, then identify:
--target-duration.Ask at most one clarifying question if genuinely ambiguous. Do not stall with multiple rounds of clarification.
Before generating recommendations, ground yourself in real data.
SituationFirst commandWhyTransform/extend existing playlist--json analyze-playlist <id>Get top_artists, top_genres, representative_seed_uris, audio_featuresUser names a specific song/artist--json search "<query>" --limit 5Get exact URI for seedingUser wants latest song by an artist--json artist-releases "<name>" --limit 5Get most recent release URIs to seed or add directlyCheck audio profile of tracks--json audio-features <track_id>...Get energy, danceability, valence, etc. (no Spotify auth needed)Playlist of only [artist]--json artist-top-tracks "<name>"Get artist's top ~10 tracks for direct playlist assemblySmall curated playlist (<~30 tracks)--json list-playlist <id>See every track, pick explicit seedsVibe-only, no existing playlist--json top-artists, --json top-tracks, or --json recentGround the vibe in the user's taste
Key rule: for non-trivial requests involving an existing playlist, analyze-playlist is almost always the right first step.
Run recommend or blend-dna in JSON-only mode — no --create, no --add yet.
--limit higher than target (e.g., 25-30 for a 20-track playlist) to give yourself room to curate--genres when the request implies a genre/scene — this dramatically improves result quality by boosting genre-affiliated candidates and penalizing unrelated ones--boost-artists or --exclude-artists, and run a second passDo not finalize on the first pass. Draft first, inspect second.
Understanding the JSON output:
score: 0.0-1.0 composite score. Higher = better match.reasons: array of tags explaining the score ( "genre match", "boosted artist", "no genre/artist affiliation (penalized)", etc.)search_artist_misses (blend-dna only): artists you searched for that didn't make the final results — consider adding them manually_hint: (optional) appears if there weren't enough scene-affiliated tracks. Follow its instructions.Review the JSON output from Step 3:
"genre match" or "no genre/artist affiliation (penalized)"? Penalized tracks may still be good — use your judgment.search_artist_misses lists artists you wanted, search for them manually and add their tracks.If the results are off, go back to Step 3 with adjusted parameters.
When recommendations fail or return off-vibe results:
SymptomLikely causeFixAll results are unrelated to the seedsReccoBeats returned global audio matches with no scene constraintAdd --genres and --artists to constrain the sceneResults are too generic/mainstreamSeeds are too popular or broadLower --popularity-target, use more specific seed tracks0 results returnedSeeds not found in ReccoBeats, or all candidates filtered outTry different seed tracks, remove --exclude-artists, broaden constraintsFusion request returns one-sided resultsrecommend can't balance two aestheticsSwitch to blend-dna with explicit groupsOff-genre tracks scoring highAudio features match but genre doesn'tAdd --genres to boost genre-matched candidates and penalize unaffiliated ones
Only now commit the tracks:
create-playlist "Name" then add-to-playlist <id> <uris...>, or use recommend ... --create "Name" for a fresh runadd-to-playlist <id> <uris...>Seed track inclusion: For "inspired by" or "like X and Y" requests, include 2-4 of the strongest seed tracks as anchors in the playlist — they ground the vibe and give the listener familiar touchpoints. For "find me something new" or "discovery" requests, omit the seeds entirely. If unclear, include seeds — the user can always remove them.
Tell the user:
Audio features are on a 0.0-1.0 scale (except tempo in BPM and loudness in dB). Use this language when analyzing requests and making decisions:
ConceptAudio featuresExampleMelancholicLow valence (0.0-0.3) + moderate-to-low energy (0.2-0.5)Lana Del Rey, Bon Iver, Adrianne LenkerUpbeat / energeticHigh energy (0.6+) + high valence (0.6+)Dance, funk, electronic upliftsIntrospectiveLow energy (0.0-0.4) + low danceability (0.2-0.4)Bedroom pop, lo-fi, indie balladsDanceableHigh danceability (0.6+) +/- energy/valenceHouse, pop, hip-hop groovesAcoustic / organicHigh acousticness (0.6+)Folk, singer-songwriter, unpluggedCinematicLow tempo + orchestral characteristics (often detected via lower danceability, lower speechiness)Post-rock, film scores, ambientRhythmically complexHigh tempo + low danceabilityProg rock, math rock, jazz fusionIntimateLow loudness + low energy + moderate-to-high acousticnessWhisper vocals, bedroom production
Before jumping to API calls, understand what you're solving:
artist-releases--exclude-artists filter or post-filter search resultsToolRevealsUse foranalyze-playlistAudio profile, top artists, genre clustersUnderstanding a vibe/aestheticaudio-featuresEnergy, valence, danceability, acousticness, etc.Verifying tracks match the target profiletop-artists / recentYour taste patterns across time rangesGrounding recommendations in actual listeningsearchTracks by genre, artist, or keywordFinding candidates that match criteriaartist-releasesRecent work with exact datesFiltering for "last year" or "recent"artist-top-tracksTop ~10 tracks for an artistBuilding artist-specific playlists without search guessworkdiscoverSimilar tracks from seeds (no scores)Quick sonic exploration — ideal for chaining in concentric discoveryrecommendScored candidates with reasons + scene affiliation tagsPrimary recommendation tool. Uses collaborator discovery when --artists providedblend-dnaPer-group profiles, overlap zone, scored candidates with per-feature distancesFusing two distinct aesthetics. Use this first for "X meets Y" requests, not recommend
You are not limited to the patterns above. They are examples, not recipes.
The north star is: understand the musical goal, then use the tools creatively to achieve it.
Examples of valid deviations:
analyze-playlist on multiple playlists to find a common thread, then build from therediscover on unexpected seed tracks to find sonic bridges between genrestop-artists with search to find obscure collaborators or side projectsaudio-features on a large search result set, then sort by multiple features simultaneouslyartist-releases to create a chronological playlist showing an artist's evolutionIf you can articulate the musical reasoning, the workflow is valid. Test it, iterate, and explain the choices to the user.
The quickest path is the automated setup script:
bash
bash scripts/setup.sh
If you prefer to set up manually, or if the .venv doesn't exist yet:
bash
# 1. Create the virtual environment
python3 -m venv .venv
# 2. Activate it and install dependencies
source .venv/bin/activate
pip install -r requirements.txt
Then authenticate (this opens a browser for Spotify login):
bash
.venv/bin/python scripts/spotify_auth.py
After auth completes, verify with status:
bash
.venv/bin/python scripts/spotify_cli.py --json status
references/setup.md — environment variables, authentication, and setup details.references/implementation-notes.md — Spotify API quirks, workarounds, and implementation details. Read this when debugging API-level issues.The following examples and patterns are reference material for complex requests. You don't need to read these for typical requests — they demonstrate how to chain tools for edge cases.
Request: "blend Radiohead's weird stuff with Bjork's electronic side"
Step 1 — Restate: fusion playlist, experimental Radiohead + electronic Bjork, ~20 tracks, likely low popularity target.
Step 2 — Search for specific seed tracks to get URIs:
bash
.venv/bin/python scripts/spotify_cli.py --json search "Radiohead Everything In Its Right Place" --limit 3
.venv/bin/python scripts/spotify_cli.py --json search "Bjork All Is Full of Love" --limit 3
Step 3 — Draft candidates (using URIs from Step 2):
bash
.venv/bin/python scripts/spotify_cli.py --json recommend \
--seed-uris spotify:track:XXXX spotify:track:YYYY \
--genres "art rock" "electronic" "experimental" "trip hop" \
--artists "Radiohead" "Bjork" \
--max-per-artist 3 \
--popularity-target 45 \
--limit 30
Step 4 — Inspect: Check Radiohead/Bjork balance, look for bridging artists (Portishead, Thom Yorke solo, Arca), verify reasons show genre overlap. If too Radiohead-heavy: --boost-artists "Bjork" and rerun. Trim to 20.
Step 5 — Create the playlist with the curated 20 tracks.
Step 6 — Summarize: "Seeded from Kid A and Homogenic-era tracks. The blend leans glitchy-electronic with tracks from Arca, Thom Yorke, and Portishead bridging the two."
Pattern A: Artist-centric discovery
analyze-playlist → understand target audio profile + top artistssearch → find similar artists (from listening history + research)audio-features → batch-verify candidates match the profileartist-releases if needed)create-playlist + add-to-playlist → commit to new playlistPattern B: Genre blending ("Radiohead weird stuff + Bjork electronic")
search → find seed tracks from each genre/artistaudio-features → check energy/danceability range of each seedrecommend → use both seeds with --genres, let scoring find bridges--genres or --boost-artists if skewedPattern C: Taste evolution ("Build on my recent listening")
recent → see what you've been playingtop-artists (short_term) → find 3-4 dominant artistssearch / artist-releases → find their recent collaborators or similar artistsaudio-features → verify new artists match your recent tastePattern D: Temporal filtering ("Recent releases in the last year")
search for artist or genreartist-releases → check release_date, keep recent onlyaudio-features → verify they match the vibecreate-playlist with curated listPattern E: DNA blending ("sinister bangers like JOAN OF ARC + the witches are angry")
Use blend-dna when the request describes a fusion of two distinct aesthetics:
blend-dna --group-a URIs... --group-b URIs... --search-artists "..." --genres "..." --limit 25feature_distances--weight-asearch_artist_misses — manually add any important artists that were filtered outPattern F: Concentric discovery (scaling depth with playlist length)
When building longer playlists, expand outward in rings:
recommend with --artists and --genres. Take the affiliated results.audio-features on them, then discover with those as seeds.audio-features shows candidates have drifted too far from Ring 0 DNA (difference > ~0.25 across key features).Scale rings to playlist size: 10-15 tracks = Ring 0 only; 20-30 = Rings 0+1; 40+ = Rings 0+1+2.
Pattern G: Audio-feature scoring (manual curation + vibe verification)
Use score-by-features for fine-grained control:
analyze-playlist or explicit feature valuesscore-by-features --target-playlist <ID> --candidate-artists "Artist1" "Artist2" --limit 10distance (lower = closer match) and feature_deltasRequest: "Check out my playlist (lana's bangers), make a similar playlist that's mostly Lana-centered, include similar artists, keep it recent, exclude Taylor Swift, based on my listening history."
Agent reasoning:
text
1. analyze-playlist("lana's bangers")
→ Top artists: Lana, Ethel Cain, MARINA, Paris Paloma
→ Audio: energy=0.43, valence=0.20 (melancholic, introspective)
2. Hypothesis: "Lana-centered" means matching this audio profile + artist aesthetic
3. Search for similar artists (Phoebe Bridgers, Clairo, boygenius, etc.)
→ get_top_artists() + recent to see if user already listens to related artists
4. Batch audio-features on candidates
→ Filter: energy < 0.60 AND valence < 0.50 (match Lana's profile)
5. artist-releases on matches → Keep only recent releases
6. create-playlist("lana's bangers v2") + add verified tracks
→ Exclude Taylor Swift (already in code: --exclude-artists)
Request: "Make an absolutely sinister banger playlist centered on 'JOAN OF ARC' by Night Lovell."
Why blend-dna over recommend: The two aesthetics (dark trap + witch house) are too distant for a single recommendation call.
text
1. Build Group A (Night Lovell core): Search → pick 7 tracks
2. Build Group B (witchy core): Search "Chelsea Wolfe", "Lingua Ignota" → pick 6 tracks
3. blend-dna --group-a <URIs> --group-b <URIs>
--search-artists "Night Lovell" "Ghostemane" "Crystal Castles" "HEALTH"
--genres "dark trap" "witch house" "industrial"
--max-per-artist 2 --limit 20
4. Inspect: blend zone = mid-high energy + very low valence + moderate danceability
5. Check search_artist_misses — manually add any that were filtered out
6. Create playlist from top-scoring candidates