spotify-cli

command module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 26, 2026 License: MIT Imports: 7 Imported by: 0

README

spotify

CI Go Reference

Search Spotify and build playlists from the command line.

Turn a text file of Artist - Title lines into a playlist, add a whole album in one command, and pipe results into anything else — results go to stdout, everything else to stderr.

Built for the February 2026 Web API revision, which renamed the playlist endpoints, reshaped the responses and cut search results from 50 to 10. Most tutorials you will find predate it and no longer work — see What changed.

Spotify removed popularity from the track object in that same revision. It used to be how you told the real release from six karaoke covers of it, so this CLI ranks matches itself — and reports anything it is not sure about rather than guessing. Building a 21-track playlist from a 1997 compilation sleeve, it flagged 10 lines; 8 of those were genuinely wrong matches, including Aerosmith and a Diddy record.

$ spotify search track doxy --artist "Miles Davis"
URI                                   ARTIST       TITLE                 ALBUM         TIME
spotify:track:4cOdK2wGLETKBW3PvgPWqT  Miles Davis  Doxy - Remastered     Bags' Groove  4:52

$ spotify playlist create "Roadtrip" --from-file tracks.txt
Resolving 24/24…
Created "Roadtrip" (private) with 22 tracks.
https://open.spotify.com/playlist/3xY2...

Skipped 2 uncertain match(es):
  0.71  Nick Drake - Cello Song                  → Nick Drake - Cello Song - Live
  0.68  Sufjan Stevens - Death with Dignity      → Sufjan Stevens - Death With Dignity (Demo)

Review them with --dry-run, then pass --include-uncertain to accept,
or lower the bar with --min-score.

Install

With Go — simplest if you have a toolchain:

go install github.com/jantijn/spotify-cli@latest

That installs the binary as spotify-cli; rename or symlink it to spotify if you want the shorter name.

Prebuilt binary — grab the archive for your platform from Releases, unpack it, and put spotify on your PATH.

From source, which also sets up shell completions:

git clone https://github.com/jantijn/spotify-cli && cd spotify-cli
./install.sh

install.sh stamps the version from git describe, installs to ~/.local/bin (pass another directory as the first argument), and writes zsh and bash completions. Open a new shell afterwards.

Completions are available on their own too, whichever way you installed:

spotify completion zsh > "${fpath[1]}/_spotify"
spotify completion bash > /etc/bash_completion.d/spotify

One-time setup

PKCE needs an app registration; there is no shared client id to borrow.

  1. Open https://developer.spotify.com/dashboard and create an app. Copy the Client ID — there is no secret to copy, and a CLI could not keep one.
  2. Register http://127.0.0.1:8888/callback as a Redirect URI. Spotify rejects localhost here, so use the IP literal. To use a different port, register it and pass --redirect-uri.
  3. Sign in:
spotify auth login --client-id <YOUR_CLIENT_ID>

The client id and redirect URI are saved, so later logins need no flags. A browser opens, you approve, and the CLI catches the callback on the loopback address. Tokens land in ~/.config/spotify/token.json with mode 0600 and refresh silently from then on.

spotify auth status     # exits 4 if there is no working session
spotify auth logout     # deletes the local tokens

Usage

spotify search track "kind of blue"
spotify search track doxy --artist "Miles Davis" --year 1954
spotify search track doxy --limit 3
spotify search track doxy -o json | jq -r '.[0].uri'

The first column is the track URI, so results pipe straight into playlist add:

spotify search track doxy | cut -f1 | head -1 | xargs spotify playlist add <PLAYLIST>

--limit caps at 10. That is the API's ceiling since February 2026, not a choice this CLI made.

Albums
spotify search album "kind of blue" --artist "Miles Davis"
spotify album tracks 1weenld61qoidwYuZ1GESA
spotify playlist add <PLAYLIST> --from-album 1weenld61qoidwYuZ1GESA

Prefer this over --from-file for a whole record. It reads the real track list from GET /albums/{id}/tracks, in album order, so nothing is matched by name and nothing is capped at search's ten results. None of the scoring below applies — there is nothing to guess.

Two things worth knowing:

  • Look before you pipe. Spotify's own ranking is not stable across --limit: searching kind of blue returns the Legacy Edition first at --limit 1 and the plain album first at --limit 2 or more. So search album … | head -1 can pick the 21-track reissue with the studio false starts on it. Read the table, then pass the id you meant.
  • Track numbers restart on each disc, so a double album shows them qualified as 1-15, 2-1. Single-disc albums stay plain.
Playlists
spotify playlist list
spotify playlist list --all
spotify playlist show <PLAYLIST> --all
spotify playlist create "Roadtrip" --description "for the drive" --public
spotify playlist add <PLAYLIST> spotify:track:4cOdK2wGLETKBW3PvgPWqT
spotify playlist add <PLAYLIST> 4cOdK2wGLETKBW3PvgPWqT --position 0
spotify playlist remove <PLAYLIST> <TRACK> --force

Anywhere a playlist or track is expected you can pass an id, a spotify:playlist:… URI, or an open.spotify.com/... share link — the ?si= tracking parameter is stripped for you.

Building a playlist from a text file

This is the part the API made awkward, and the reason this CLI exists.

# tracks.txt — blank lines and #-comments are ignored
Miles Davis - Doxy
Bill Evans - Peace Piece
So What
spotify:track:4cOdK2wGLETKBW3PvgPWqT
track:"Blue in Green" artist:"Miles Davis" year:1959
spotify playlist create "Jazz" --from-file tracks.txt --dry-run   # look first
spotify playlist create "Jazz" --from-file tracks.txt
spotify playlist add <PLAYLIST> --from-file more.txt
pbpaste | spotify playlist create "From clipboard" --from-file -

Each line is one of:

Line Treated as
Artist - Title a search, with artist:/track: filters applied
Title a plain search
spotify:track:… or a link used directly, no search
anything with artist: etc your own Spotify query, passed through untouched

Hyphen, en dash and em dash all work as the separator.

A URI or link line may carry a trailing # comment, which is handy when a hand-built list needs to say what each id is. Search lines keep their #, because a title can contain one — #1 Crush is a song. A line that starts like a reference but does not parse is reported, never searched: a broken URI makes an excellent search query and a confidently wrong match.

Matching. February 2026 removed popularity from the track object, which used to be how you told the real release from six karaoke covers of it. Ranking now happens locally, on artist and title, over at most ten candidates. Titles are compared with diacritics folded and release notes ignored, so Doxy matches Doxy - Remastered 2015 and Stronger matches Stronger (feat. Daft Punk).

A line that does not clear the confidence bar is reported, not guessed at:

  • --dry-run shows every line with its score and match, and changes nothing.
  • --include-uncertain accepts the best guesses anyway.
  • --min-score 0.7 lowers the bar (default 0.85).

Resolution is one search per line, run sequentially — firing them all at once is the reliable way to get rate limited for the whole batch. A 200-line file takes a moment and the playlist comes out in file order.

Output and scripting

Human output by default; --format json (-o json) for machines. Results go to stdout, everything else — progress, warnings, confirmations — to stderr, so a pipe only ever carries data. Piped table output is tab-separated, so cut -f1 works.

Exit codes:

Code Meaning
0 success
1 general error
2 usage error
4 not signed in, or the token was rejected
5 no such playlist or track
6 forbidden — missing scope, or not an authorised user
7 rate limited, and the retries ran out
130 interrupted

Configuration

Precedence, highest first: flags → environment → config file → defaults.

Setting Flag Environment
Client id --client-id SPOTIFY_CLIENT_ID
Redirect URI --redirect-uri SPOTIFY_REDIRECT_URI
Access token --token SPOTIFY_ACCESS_TOKEN
Output format --format/-o

Files, under $XDG_CONFIG_HOME/spotify (or ~/.config/spotify):

  • config.toml — client id and redirect URI, mode 0600
  • token.json — access and refresh tokens, mode 0600

SPOTIFY_ACCESS_TOKEN bypasses the login entirely, for scripts that mint their own token. SPOTIFY_API_BASE_URL and SPOTIFY_ACCOUNTS_BASE_URL retarget the client, which is how the tests run without touching Spotify.

What changed in February 2026

Verified against the changelog and the migration guide. What this CLI had to account for:

Renamed/playlists/{id}/tracks/playlists/{id}/items, for GET, POST, PUT and DELETE. The DELETE body's array went from tracks to items; sending the old name returns 200 and removes nothing, which is why there is a test pinning it.

Reshaped — the playlist object's tracks field became items at every level, so tracks.tracks.track is now items.items.item. This CLI decodes both so a response in either shape still yields a track.

RemovedPOST /users/{id}/playlists (use POST /me/playlists, which can only create for the signed-in user); GET /users/{id} and GET /users/{id}/playlists, so another user's playlists are unreachable; the batch lookups GET /tracks, /albums, /artists and friends; GET /markets; GET /browse/new-releases; GET /artists/{id}/top-tracks. Recommendations and audio-features went earlier.

SurvivedGET /albums/{id} and GET /albums/{id}/tracks. Only the batch album lookup went, so a single album is still reachable, which is what makes --from-album possible. Note that an album's collection is still called tracks — the rename to items was a playlist change only.

Shrunk — search limit max 50 → 10, default 20 → 5. Track objects lost popularity, available_markets, external_ids and linked_from; user objects lost country, email and product, so nothing here can tell whether an account is Premium.

Consolidated — the per-type library and follow endpoints became PUT/DELETE /me/library taking uris. Not used by this CLI.

Quota

Development Mode is non-commercial: a Premium account, one client id per developer, and at most five authorised users, listed in the dashboard under User Management. An account that is not on that list gets 403 with a valid token — this CLI exits 6 and says so, rather than telling you to sign in again.

Anything rolled out beyond those five users needs Extended Quota Mode, which is an application with a review, so budget the lead time if a product depends on it.

Development

go test ./...
go vet ./...

The tests run against an httptest fake, including the PKCE flow, and redirect XDG_CONFIG_HOME to a temp directory — so they never touch your real credentials and never call Spotify. That is also why CI can run the full suite with no secrets configured.

Layout:

Path Holds
internal/cli command tree, exit codes, bulk resolution
internal/spotify API client for the February 2026 endpoints
internal/auth PKCE flow, loopback callback, token cache and refresh
internal/match text → track matching without popularity
internal/config flag/env/file precedence, redirect URI validation
internal/output table and JSON rendering, TTY detection

Releasing

Tag and push; GitHub Actions runs GoReleaser and publishes binaries for macOS, Linux and Windows on amd64 and arm64.

git tag v0.2.0 && git push origin v0.2.0

Licence

MIT — see LICENSE.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
internal
auth
Package auth handles interactive sign-in via the OAuth 2.0 authorization code flow with PKCE, and caches the resulting tokens on disk so a login persists across commands.
Package auth handles interactive sign-in via the OAuth 2.0 authorization code flow with PKCE, and caches the resulting tokens on disk so a login persists across commands.
cli
Package cli wires up the command tree.
Package cli wires up the command tree.
config
Package config resolves settings from flags, environment and config file.
Package config resolves settings from flags, environment and config file.
match
Package match turns a line of text like "Miles Davis - Doxy" into a Spotify search query, and picks the best track from what the search returns.
Package match turns a line of text like "Miles Davis - Doxy" into a Spotify search query, and picks the best track from what the search returns.
output
Package output renders results for humans or for machines.
Package output renders results for humans or for machines.
spotify
Package spotify is a thin client for the Spotify Web API.
Package spotify is a thin client for the Spotify Web API.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL