A production-quality Spotify CLI in a single Python file
Project description
spopy
A production-quality Spotify CLI in a single Python file. Runs anywhere with uv — no install step, no virtualenv, no package manager.
Designed for both local use and self-hosting on a remote gateway (Dokku, VPS, etc.).
Features
- Single file — one script, zero config files, inline dependencies via PEP 723
- Two auth modes — local browser login or headless gateway bootstrap (paste-back flow)
- Persistent tokens — authenticate once, use forever (auto-refresh)
- Full playback control — play, pause, seek, volume, shuffle, repeat, queue
- Search and browse — tracks, albums, artists, playlists
- Playlist management — create, add, remove, reorder, clear, replace
- Library operations — save, unsave, check, list saved tracks/albums
- Discovery — top tracks/artists, recently played, genre/mood search
- Three output modes — rich (human), plain (pipes), JSON (machines)
- Honest API handling — unsupported endpoints are reported clearly, never faked
- Safe for servers — never leaks tokens, no secrets in logs or output
Install
PyPI (recommended)
uv tool install spopy
Or with pipx:
pipx install spopy
Script installer
curl -fsSL https://raw.githubusercontent.com/amitkot/spopy/main/install.sh | bash
Standalone script
curl -fsSL https://raw.githubusercontent.com/amitkot/spopy/main/spopy.py -o ~/.local/bin/spopy && chmod +x ~/.local/bin/spopy
The standalone script uses PEP 723 inline deps and runs with uv run spopy.py.
Quick Start
No setup required — spopy includes a built-in Spotify app.
spopy auth login
spopy status
spopy play "bohemian rhapsody"
Remote / headless server
spopy auth url
# Open the printed URL in a browser on another machine
# After approving, copy the command from the callback page
spopy auth callback-url '<paste_the_url>'
Transfer token from local to remote
# On local machine
spopy auth login
spopy auth export-token-info --raw --yes > token.json
# Copy token.json to remote, then:
spopy auth import-token-info token.json
Configuration
All configuration is via environment variables. None are required — spopy works out of the box with PKCE auth.
Auth (all optional)
| Variable | Default | Description |
|---|---|---|
SPOTIFY_CLIENT_ID |
(built-in) | Override with your own Spotify app |
SPOTIFY_CLIENT_SECRET |
Set to switch to classic OAuth flow | |
SPOTIFY_REDIRECT_URI |
(auto) | Override redirect URI |
Setting
SPOTIFY_CLIENT_SECRETswitches spopy to the classic OAuth flow with your own Spotify app. Seespopy auth setup-guidefor details.
Runtime (all optional)
| Variable | Default | Description |
|---|---|---|
SPOTIFY_CACHE_PATH |
~/.config/spopy/token_cache |
Token cache file path |
SPOTIFY_USERNAME |
Spotify username (for multi-user) | |
SPOTIFY_SCOPES |
(sensible defaults) | Override OAuth scopes |
SPOTIFY_DEFAULT_DEVICE_ID |
Fallback device ID | |
SPOTIFY_DEFAULT_DEVICE_NAME |
Fallback device name | |
SPOTIFY_MARKET |
ISO country code for market | |
SPOTIFY_OUTPUT |
rich |
Default output: rich, plain, json |
SPOTIFY_TIMEOUT_SECONDS |
15 |
API request timeout |
SPOTIFY_RETRIES |
3 |
Max retry attempts |
SPOTIFY_BACKOFF_FACTOR |
0.5 |
Exponential backoff factor |
SPOTIFY_DEBUG |
0 |
Enable debug logging (1) |
SPOTIFY_OPEN_BROWSER |
1 |
Allow browser opening (0 to disable) |
SPOTIFY_NO_COLOR |
0 |
Disable color output (1) |
SPOTIFY_STATE_FILE |
Path to persist auth state |
Commands
Global flags
--json JSON output
--plain Plain text output (pipe-friendly)
--debug Debug logging
--market CC Spotify market (ISO country code)
--device-id ID Target device ID
--device-name N Target device name
--limit N Result limit
--offset N Result offset
--yes Skip confirmations
--exact Prefer exact name matches
--interactive Interactive selection from results
--version Show version
Auth
| Command | Description |
|---|---|
auth setup-guide |
First-time setup instructions |
auth status |
Show auth config and token status |
auth url |
Print authorization URL (for gateway flow) |
auth login |
Interactive login (local browser) |
auth callback-url <url> |
Exchange redirect URL for tokens |
auth code <code> |
Exchange raw auth code for tokens |
auth import-token-info <path> |
Import token JSON (- for stdin) |
auth export-token-info |
Export token JSON (--raw for real tokens) |
auth whoami |
Show current user |
auth logout |
Remove token cache |
Playback
| Command | Description |
|---|---|
play [query] |
Play or resume. Search query, URI, URL, or ID |
pause |
Pause playback |
resume |
Resume playback |
stop |
Stop (alias for pause — no true stop API) |
next |
Skip to next track |
previous |
Skip to previous |
seek <pos> |
Seek: 1:30, +10s, -15s, or milliseconds |
volume <0-100> |
Set volume |
repeat <off|track|context> |
Set repeat mode |
shuffle <on|off|toggle> |
Set shuffle |
Search
| Command | Description |
|---|---|
search <query> |
Search (--type track,album,artist,playlist) |
Devices
| Command | Description |
|---|---|
devices list |
List available devices |
devices transfer <device> |
Transfer playback (name or ID) |
Track
| Command | Description |
|---|---|
track show <query> |
Track details |
track play <query> |
Play a track |
track queue <query> |
Add to queue |
track save <query> |
Save to library |
track unsave <query> |
Remove from library |
track check <query> |
Check if saved |
track open <query> |
Print Spotify URL |
track audio <query> |
Audio features (restricted API) |
Album
| Command | Description |
|---|---|
album show <query> |
Album details |
album play <query> |
Play album |
album tracks <query> |
List album tracks |
album save <query> |
Save to library |
album unsave <query> |
Remove from library |
album check <query> |
Check if saved |
Artist
| Command | Description |
|---|---|
artist show <query> |
Artist details |
artist top <query> |
Top tracks (search-based) |
artist albums <query> |
List albums |
artist follow <query> |
Follow artist |
artist unfollow <query> |
Unfollow artist |
artist related <query> |
Related artists (restricted API) |
Playlist
| Command | Description |
|---|---|
playlist list |
Your playlists (--all for full list) |
playlist show <pl> |
Playlist details |
playlist create <name> |
Create (--description, --public/--private) |
playlist rename <pl> <name> |
Rename |
playlist describe <pl> <desc> |
Set description |
playlist set-public <pl> |
Make public |
playlist set-private <pl> |
Make private |
playlist follow <pl> |
Follow playlist |
playlist unfollow <pl> |
Unfollow playlist |
playlist items <pl> |
List items |
playlist add <pl> <tracks...> |
Add tracks |
playlist remove <pl> <tracks...> |
Remove tracks |
playlist clear <pl> |
Remove all items |
playlist reorder <pl> |
Reorder (--from, --to, --length) |
playlist replace <pl> <tracks...> |
Replace all items |
Library
| Command | Description |
|---|---|
library tracks |
Saved tracks |
library albums |
Saved albums |
library save <query> |
Save item |
library unsave <query> |
Remove item |
library check <query> |
Check if saved |
Queue
| Command | Description |
|---|---|
queue list |
Current queue |
queue add <query> |
Add to queue |
queue clear |
Not supported (no API) |
queue remove |
Not supported (no API) |
Discovery
| Command | Description |
|---|---|
status |
Current playback summary |
current |
Detailed now-playing info |
recent |
Recently played tracks |
top tracks |
Your top tracks (--time-range) |
top artists |
Your top artists (--time-range) |
discover |
Discovery suggestions from your history |
radio <query> |
Build a queue from a seed (search heuristic) |
genre list |
Well-known genres |
genre search <genre> |
Search by genre |
mood search <mood> |
Search by mood (heuristic) |
doctor |
Diagnose auth, config, devices, connectivity |
Output Modes
Rich (default)
$ spopy status
╭─ Bohemian Rhapsody — Queen (A Night at the Opera) ───╮
│ Playing 2:15 / 5:55 │
│ Device: MacBook Pro | Volume: 65% | Shuffle: off │
╰──────────────────────────────────────────────────────────╯
JSON
$ spopy --json status
{
"ok": true,
"command": "status",
"data": {
"name": "Bohemian Rhapsody",
"artists": "Queen",
"playing": true,
"progress": "2:15",
"duration": "5:55",
"device": "MacBook Pro"
}
}
Plain
$ spopy --plain status
Bohemian Rhapsody Queen A Night at the Opera playing 2:15/5:55 MacBook Pro
AI Agent Skill
A Claude Code skill is included for AI agents.
To install it, copy the skills/ directory into your project or Claude Code config:
# Project-level
cp -r skills/ /path/to/your/project/.claude/skills/
# Or user-level
cp -r skills/ ~/.claude/skills/
The skill teaches the agent how to invoke the CLI, parse JSON output, and handle errors.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 2 | Invalid user input |
| 3 | Auth/config error |
| 4 | Spotify API error (Premium required, no device, 403, 404) |
| 5 | Rate limit exhausted |
| 10 | Internal error |
Device Selection
For playback commands, devices are selected in this order:
--device-idflag--device-nameflag- Currently active Spotify device
SPOTIFY_DEFAULT_DEVICE_IDenv varSPOTIFY_DEFAULT_DEVICE_NAMEenv var- Error with helpful message
Known Limitations
These are Spotify API limitations, not CLI bugs:
- Queue clear/remove — no API endpoint exists
- Recommendations — removed from Spotify API (Nov 2024)
- Audio features — restricted to apps with extended API access
- Related artists — restricted to apps with extended API access
- Artist top tracks — removed from API (Feb 2026); CLI uses search fallback
- Search limit — max 10 results per type (Spotify API limit)
- Volume control — some devices (phones, smart speakers) don't support remote volume
- Premium required — all playback control requires Spotify Premium
License
MIT License. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file spopy-0.3.1.tar.gz.
File metadata
- Download URL: spopy-0.3.1.tar.gz
- Upload date:
- Size: 31.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da48ead240434209e5125f32b2aa65f6998b1d96420b5bdef16354486e7852c6
|
|
| MD5 |
25db09f32ecf02207bcb3fe8320a8fcb
|
|
| BLAKE2b-256 |
d00d55a857d9057f4cc75e3481eca153524c31f4be98882e99ee34ec0725a411
|
File details
Details for the file spopy-0.3.1-py3-none-any.whl.
File metadata
- Download URL: spopy-0.3.1-py3-none-any.whl
- Upload date:
- Size: 31.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a28c528b06aee42d55a0978ffecb2309ae1b71eeb907429b2f100324a914b324
|
|
| MD5 |
b55e5648038d31c8f33a287ae0bdbd3b
|
|
| BLAKE2b-256 |
b12bda3b93a298b5f151861dac156c70c91741ba372d36836f61fc4b277b4994
|