SpotiClaw
by @ledzgio
Spotify Web API client for Nyx agents. Use when interacting with Spotify: search, playback, playlists, library, tracks, artists, albums, shows, podcasts. Req...
clawhub install spoticlawπ About This Skill
name: spoticlaw description: "Spotify Web API client for Nyx agents. Use when interacting with Spotify: search, playback, playlists, library, tracks, artists, albums, shows, podcasts. Requires SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, SPOTIFY_REDIRECT_URI, and a local .spotify_cache token file." homepage: https://github.com/ledzgio/spoticlaw metadata: {"clawdbot":{"emoji":"π΅","requires":{"env":["SPOTIFY_CLIENT_ID","SPOTIFY_CLIENT_SECRET","SPOTIFY_REDIRECT_URI"],"files":[".spotify_cache"]},"primaryEnv":"SPOTIFY_CLIENT_ID"}}
Spoticlaw - Spotify Web API Client
A lightweight Spotify Web API client using direct HTTP requests. No Spotipy dependency.
Quick Start
# Install dependencies
pip install requests python-dotenvAdd to path
import sys
sys.path.insert(0, "skills/spoticlaw/scripts")from spoticlaw import player, search, playlists, library
Search for music
results = search().query("coldplay", types=["track"], limit=10)Play a track
player().play(uris=["spotify:track:..."])Manage playlists
playlists().create("My Playlist")
playlists().add_items("playlist_id", ["spotify:track:..."])Save to library
library().save(["spotify:track:..."])
Or run from the scripts directory:
cd skills/spoticlaw/scripts
python -c "from spoticlaw import player; player().play(...)"
Required Configuration
Required env vars in agent runtime:
SPOTIFY_CLIENT_IDSPOTIFY_CLIENT_SECRETSPOTIFY_REDIRECT_URI (recommended: http://127.0.0.1:8888/callback)Required file:
.spotify_cache (OAuth token cache)Authentication
Security Note: Tokens never pass through the AI model. Authentication is done locally, and the token file is copied manually to the agent.
Setup
1. Create a Spotify app at https://developer.spotify.com/dashboard
2. Get CLIENT_ID and CLIENT_SECRET
3. Add http://127.0.0.1:8888/callback as Redirect URI
4. Create .env file in your LOCAL machine:
SPOTIFY_CLIENT_ID=your_client_id
SPOTIFY_CLIENT_SECRET=your_client_secret
SPOTIFY_REDIRECT_URI=http://127.0.0.1:8888/callback
5. Run authentication on your LOCAL machine:
cd skills/spoticlaw/scripts
pip install -r requirements.txt
python auth.py
6. Open the displayed URL in your browser, authorize
7. Copy the token file to your agent:
# Linux/Mac - copy to agent's skill folder
cp .spotify_cache /path/to/agent/skills/spoticlaw/.spotify_cacheOr if agent is remote, copy via scp, USB, etc.
scp .spotify_cache user@agent:/path/to/skills/spoticlaw/.spotify_cache
That's it! No token ever touches the AI. The agent just reads the file.
Token Auto-Refresh
The library automatically handles token refresh only if the agent has the same app credentials in .env:
refresh_token + client credentials to request a new access tokenSPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET in the agent environment.spotify_cache exists but .env is missing/mismatched, refresh fails (invalid_client)python auth.py locally again and copy updated .spotify_cacheFor more on Spotify's OAuth flow, see: https://developer.spotify.com/documentation/web-api/tutorials/code-flow
Required Scopes
The auth.py script requests these scopes:
user-read-playback-state - Read playback stateuser-modify-playback-state - Control playbackplaylist-read-private - Read private playlistsplaylist-modify-public - Modify public playlistsplaylist-modify-private - Modify private playlistsuser-library-read - Read user libraryuser-library-modify - Modify user libraryuser-read-recently-played - Recently played tracksuser-top-read - Top tracks/artistsuser-follow-read - Followed artistsPrimitives
Important: Add the module path and install deps first:
pip install requests python-dotenv # Install dependenciesFor agent execution, add to path:
import sys
sys.path.insert(0, "skills/spoticlaw/scripts")
User
from spoticlaw import useruser().me() # Get current user profile
Returns: {id, display_name, email, country, ...}
Search
from spoticlaw import searchSearch for tracks
search().query("song name", types=["track"], limit=10)Search for artists
search().query("artist name", types=["artist"], limit=10)Search multiple types
search().query("coldplay", types=["track", "artist", "album"], limit=10)
Parameters:
q: Search query stringtypes: List of types: track, artist, album, playlist, show, episode, audiobooklimit: Max 10 results (Spotify limit)offset: Pagination offsetTracks
from spoticlaw import trackstracks().get("track_id") # Get single track
tracks().get_multiple(["id1", "id2"]) # Get multiple tracks
Returns track metadata: {name, artists, album, duration_ms, uri, ...}
Artists
from spoticlaw import artistsartists().get("artist_id") # Get artist details
artists().get_albums("artist_id", limit=10) # Get artist albums
Album filters (include_groups):
album, single, compilation, appears_onAlbums
from spoticlaw import albumsalbums().get("album_id") # Get album details
albums().get_tracks("album_id") # Get album tracks
Shows (Podcasts)
from spoticlaw import showsshows().get("show_id") # Get show details
shows().get_episodes("show_id", limit=10) # Get show episodes
Episodes
from spoticlaw import episodesepisodes().get("episode_id") # Get episode details
Playlists
from spoticlaw import playlists, user_playlistsGet user's playlists
user_playlists().get(limit=50)Get playlist details
playlists().get("playlist_id")Get playlist tracks
Note: Each item has 'item' key (not 'track'), e.g. item['item']['name']
Also can contain episodes (podcasts), not just tracks
playlists().get_items("playlist_id", limit=50)Create playlist
playlists().create(name="My Playlist", description="...", public=False)Update playlist
playlists().update("playlist_id", name="New Name")Add tracks
playlists().add_items("playlist_id", ["spotify:track:...", "spotify:track:..."])Remove tracks
playlists().remove_items("playlist_id", ["spotify:track:..."])Delete playlist (unfollow)
playlists().delete("playlist_id")
Library
from spoticlaw import librarylibrary().save(["spotify:track:..."]) # Save to library
library().remove(["spotify:track:..."]) # Remove from library
library().check(["spotify:track:..."]) # Check if saved, returns [True, False]
Player
from spoticlaw import playerGet playback state
player().get_playback_state() # Returns {} if nothing playing
player().get_currently_playing()Get devices
player().get_devices() # Returns list of devices with IDsTransfer playback to device
player().transfer("device_id", play=True)Playback control
player().play(uris=["spotify:track:..."]) # Start playing
player().play(context_uri="spotify:album:...") # Play album/playlist
player().pause()
player().next()
player().previous()
player().seek(60000) # Seek to 1:00 (milliseconds)
player().set_volume(80) # Set volume 0-100Queue
player().add_to_queue("spotify:track:...")
player().get_queue()Modes
player().set_shuffle(True)
player().set_repeat("off") # off, track, contextRecently played
player().get_recently_played(limit=50)
Personalisation
from spoticlaw import personalisationpersonalisation().get_top("tracks", time_range="medium_term", limit=20)
personalisation().get_top("artists", time_range="long_term", limit=20)
time_range: short_term (4 weeks), medium_term (6 months), long_term (years)
Follow
from spoticlaw import followfollow().get_followed(limit=50) # Get followed artists
Composite Workflows
The primitives can be mixed and matched to create powerful automations. Here are practical examples:
Workflow 1: Play a Specific Song
from spoticlaw import search, player1. Search for the song
results = search().query("stairway to heaven", types=["track"], limit=5)
song = results["tracks"]["items"][0]
song_uri = song["uri"]
print(f"Playing: {song['name']} by {song['artists'][0]['name']}")2. Play it
player().play(uris=[song_uri])
Workflow 2: Create Playlist from Search Results
from spoticlaw import search, playlists1. Search for songs
results = search().query("led zeppelin", types=["track"], limit=10)
track_uris = [t["uri"] for t in results["tracks"]["items"][:5]]2. Create playlist
pl = playlists().create("Led Zeppelin Mix", public=False)
playlist_id = pl["id"]3. Add tracks
playlists().add_items(playlist_id, track_uris)
print(f"Created playlist: {pl['name']}")
Workflow 3: Save Album to Library
from spoticlaw import artists, albums, library1. Find artist
artist = search().query("the weeknd", types=["artist"], limit=1)["artists"]["items"][0]2. Get albums
albums_list = artists().get_albums(artist["id"], include_groups="album", limit=5)3. Save first album
album = albums_list["items"][0]
library().save([album["uri"]])
print(f"Saved album: {album['name']}")
Workflow 4: Play Podcast Episode
from spoticlaw import search, shows, player1. Find podcast
podcast = search().query("joe rogan", types=["show"], limit=1)["shows"]["items"][0]
show_id = podcast["id"]2. Get latest episode
episodes = shows().get_episodes(show_id, limit=1)
episode = episodes["items"][0]
episode_uri = episode["uri"]3. Get device and play
devices = player().get_devices()
if devices.get("devices"):
device_id = devices["devices"][0]["id"]
player().transfer(device_id, play=True)
player().play(uris=[episode_uri])
print(f"Playing: {episode['name']}")
Workflow 5: Transfer Playback and Play
from spoticlaw import player, search1. Get available devices
devices = player().get_devices()
print("Available devices:", [d["name"] for d in devices.get("devices", [])])2. Transfer to phone
if devices.get("devices"):
device_id = devices["devices"][0]["id"]
player().transfer(device_id, play=True)
# 3. Search and play
results = search().query("dream on", types=["track"], limit=1)
track_uri = results["tracks"]["items"][0]["uri"]
player().play(uris=[track_uri])
Workflow 6: Get User's Top Artists and Follow One
from spoticlaw import personalisation, search, library1. Get top artists
top = personalisation().get_top("artists", limit=10)
print("Your top artists:")
for i, a in enumerate(top["items"], 1):
print(f" {i}. {a['name']}")2. Search for a new artist
new_artist = search().query("tame impala", types=["artist"], limit=1)["artists"]["items"][0]
print(f"\nFound: {new_artist['name']}")Note: Follow functionality requires additional scope (not in current scope list)
Use Spotify app to follow manually
Workflow 7: Build Queue from Album
from spoticlaw import albums, player1. Get album tracks
album_id = "4aawyAB9vmqN3uQ7FjRGTy" # Example album
tracks = albums().get_tracks(album_id)2. Add all tracks to queue
for track in tracks["items"][:5]: # First 5 tracks
player().add_to_queue(track["uri"])print("Added 5 tracks to queue")
Workflow 8: Check Library for Multiple Tracks
from spoticlaw import library, search1. Search for tracks
results = search().query("classic rock", types=["track"], limit=20)
track_uris = [t["uri"] for t in results["tracks"]["items"]]2. Check which are in library
saved = library().check(track_uris)3. Show results
for i, (track, is_saved) in enumerate(zip(results["tracks"]["items"], saved)):
status = "β saved" if is_saved else "β not saved"
print(f"{i+1}. {track['name']} - {status}")
Workflow 9: Get Recently Played and Save One
from spoticlaw import player, library1. Get recently played
recent = player().get_recently_played(limit=10)print("Recently played:")
for i, item in enumerate(recent["items"], 1):
track = item["track"]
print(f" {i}. {track['name']} - {track['artists'][0]['name']}")
2. Save the first one
if recent["items"]:
track_uri = recent["items"][0]["track"]["uri"]
library().save([track_uri])
print(f"\nSaved: {recent['items'][0]['track']['name']}")
Workflow 10: Play from Playlist
from spoticlaw import playlists, player1. Get user's playlists
my_playlists = user_playlists().get(limit=10)print("Your playlists:")
for p in my_playlists["items"]:
print(f" - {p['name']} ({p['tracks']['total']} tracks)")
2. Pick first playlist and play
if my_playlists["items"]:
playlist_id = my_playlists["items"][0]["id"]
# Get device first
devices = player().get_devices()
if devices.get("devices"):
player().transfer(devices["devices"][0]["id"], play=True)
# Play playlist
player().play(context_uri=f"spotify:playlist:{playlist_id}")
print(f"Playing: {my_playlists['items'][0]['name']}")
Error Handling
from spoticlaw import player, SpotifyExceptiontry:
player().play(uris=["spotify:track:..."])
except SpotifyException as e:
if "NO_ACTIVE_DEVICE" in str(e):
print("No device found. Open Spotify and try again.")
elif "Invalid token" in str(e):
print("Token expired. Re-authenticate: python auth.py")
else:
print(f"Error: {e}")
Common Issues
| Error | Solution |
|-------|----------|
| No token. Re-authenticate. | Run python auth.py |
| The access token expired | Should auto-refresh. If not, run python auth.py |
| Insufficient client scope | Re-auth with more scopes |
| NO_ACTIVE_DEVICE | Open Spotify app, then retry |
| Invalid limit | Use max 10 for search, 50 for playlists |
| Resource not found | Invalid ID or item unavailable |
API Limits
offset parameterFiles
scripts/spoticlaw.py - Main API clientscripts/auth.py - Authentication helperscripts/requirements.txt - DependenciesMore Info
For setup, troubleshooting, and contributions: https://github.com/your-org/spoticlaw
π‘ Examples
# Install dependencies
pip install requests python-dotenvAdd to path
import sys
sys.path.insert(0, "skills/spoticlaw/scripts")from spoticlaw import player, search, playlists, library
Search for music
results = search().query("coldplay", types=["track"], limit=10)Play a track
player().play(uris=["spotify:track:..."])Manage playlists
playlists().create("My Playlist")
playlists().add_items("playlist_id", ["spotify:track:..."])Save to library
library().save(["spotify:track:..."])
Or run from the scripts directory:
cd skills/spoticlaw/scripts
python -c "from spoticlaw import player; player().play(...)"
βοΈ Configuration
1. Create a Spotify app at https://developer.spotify.com/dashboard
2. Get CLIENT_ID and CLIENT_SECRET
3. Add http://127.0.0.1:8888/callback as Redirect URI
4. Create .env file in your LOCAL machine:
SPOTIFY_CLIENT_ID=your_client_id
SPOTIFY_CLIENT_SECRET=your_client_secret
SPOTIFY_REDIRECT_URI=http://127.0.0.1:8888/callback
5. Run authentication on your LOCAL machine:
cd skills/spoticlaw/scripts
pip install -r requirements.txt
python auth.py
6. Open the displayed URL in your browser, authorize
7. Copy the token file to your agent:
# Linux/Mac - copy to agent's skill folder
cp .spotify_cache /path/to/agent/skills/spoticlaw/.spotify_cacheOr if agent is remote, copy via scp, USB, etc.
scp .spotify_cache user@agent:/path/to/skills/spoticlaw/.spotify_cache
That's it! No token ever touches the AI. The agent just reads the file.
Token Auto-Refresh
The library automatically handles token refresh only if the agent has the same app credentials in .env:
refresh_token + client credentials to request a new access tokenSPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET in the agent environment.spotify_cache exists but .env is missing/mismatched, refresh fails (invalid_client)python auth.py locally again and copy updated .spotify_cacheFor more on Spotify's OAuth flow, see: https://developer.spotify.com/documentation/web-api/tutorials/code-flow
Required Scopes
The auth.py script requests these scopes:
user-read-playback-state - Read playback stateuser-modify-playback-state - Control playbackplaylist-read-private - Read private playlistsplaylist-modify-public - Modify public playlistsplaylist-modify-private - Modify private playlistsuser-library-read - Read user libraryuser-library-modify - Modify user libraryuser-read-recently-played - Recently played tracksuser-top-read - Top tracks/artistsuser-follow-read - Followed artists