Install
openclaw skills install spoticlawSpotify Web API client for Nyx agents. Use when interacting with Spotify: search, playback, playlists, library, tracks, artists, albums, shows, podcasts. Req...
openclaw skills install spoticlawA lightweight Spotify Web API client using direct HTTP requests. No Spotipy dependency.
# Install dependencies
pip install requests python-dotenv
# Add 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 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)Security Note: Tokens never pass through the AI model. Authentication is done locally, and the token file is copied manually to the agent.
CLIENT_ID and CLIENT_SECREThttp://127.0.0.1:8888/callback as Redirect URI.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
cd skills/spoticlaw/scripts
pip install -r requirements.txt
python auth.py
Open the displayed URL in your browser, authorize
Copy the token file to your agent:
# Linux/Mac - copy to agent's skill folder
cp .spotify_cache /path/to/agent/skills/spoticlaw/.spotify_cache
# Or 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.
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
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 artistsImportant: Add the module path and install deps first:
pip install requests python-dotenv # Install dependencies
# For agent execution, add to path:
import sys
sys.path.insert(0, "skills/spoticlaw/scripts")
from spoticlaw import user
user().me() # Get current user profile
Returns: {id, display_name, email, country, ...}
from spoticlaw import search
# Search 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 offsetfrom spoticlaw import tracks
tracks().get("track_id") # Get single track
tracks().get_multiple(["id1", "id2"]) # Get multiple tracks
Returns track metadata: {name, artists, album, duration_ms, uri, ...}
from spoticlaw import artists
artists().get("artist_id") # Get artist details
artists().get_albums("artist_id", limit=10) # Get artist albums
Album filters (include_groups):
album, single, compilation, appears_onfrom spoticlaw import albums
albums().get("album_id") # Get album details
albums().get_tracks("album_id") # Get album tracks
from spoticlaw import shows
shows().get("show_id") # Get show details
shows().get_episodes("show_id", limit=10) # Get show episodes
from spoticlaw import episodes
episodes().get("episode_id") # Get episode details
from spoticlaw import playlists, user_playlists
# Get 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")
from spoticlaw import library
library().save(["spotify:track:..."]) # Save to library
library().remove(["spotify:track:..."]) # Remove from library
library().check(["spotify:track:..."]) # Check if saved, returns [True, False]
from spoticlaw import player
# Get playback state
player().get_playback_state() # Returns {} if nothing playing
player().get_currently_playing()
# Get devices
player().get_devices() # Returns list of devices with IDs
# Transfer 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-100
# Queue
player().add_to_queue("spotify:track:...")
player().get_queue()
# Modes
player().set_shuffle(True)
player().set_repeat("off") # off, track, context
# Recently played
player().get_recently_played(limit=50)
from spoticlaw import personalisation
personalisation().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)
from spoticlaw import follow
follow().get_followed(limit=50) # Get followed artists
The primitives can be mixed and matched to create powerful automations. Here are practical examples:
from spoticlaw import search, player
# 1. 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])
from spoticlaw import search, playlists
# 1. 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']}")
from spoticlaw import artists, albums, library
# 1. 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']}")
from spoticlaw import search, shows, player
# 1. 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']}")
from spoticlaw import player, search
# 1. 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])
from spoticlaw import personalisation, search, library
# 1. 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
from spoticlaw import albums, player
# 1. 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")
from spoticlaw import library, search
# 1. 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}")
from spoticlaw import player, library
# 1. 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']}")
from spoticlaw import playlists, player
# 1. 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']}")
from spoticlaw import player, SpotifyException
try:
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}")
| 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 |
offset parameterscripts/spoticlaw.py - Main API clientscripts/auth.py - Authentication helperscripts/requirements.txt - DependenciesFor setup, troubleshooting, and contributions: https://github.com/your-org/spoticlaw