Async wrapper for spotipy with a focus on integration with MCP agents.
Project description
spotifyify
An async-first Spotify client with a namespaced API and fully typed response models generated from the official Spotify OpenAPI specification.
Requirements
- Python 3.13+
- A Spotify app registered at developer.spotify.com
Installation
pip install spotifyify
With uv:
uv add spotifyify
Configuration
Credentials are loaded from environment variables (or a .env file via pydantic-settings):
| Variable | Required | Description |
|---|---|---|
SPOTIFY_CLIENT_ID |
Yes | Your app's client ID |
SPOTIFY_CLIENT_SECRET |
Yes | Your app's client secret |
SPOTIFY_REDIRECT_URI |
For user auth | OAuth redirect URI |
SPOTIFY_ACCESS_TOKEN |
Optional | Pre-existing access token |
SPOTIFY_REFRESH_TOKEN |
Optional | Refresh token for automatic renewal |
SPOTIFY_TOKEN_EXPIRES_AT |
Optional | Unix timestamp when the token expires |
.env example:
SPOTIFY_CLIENT_ID=your_client_id
SPOTIFY_CLIENT_SECRET=your_client_secret
SPOTIFY_REDIRECT_URI=http://localhost:8888/callback
SPOTIFY_REFRESH_TOKEN=your_refresh_token
If SPOTIFY_REFRESH_TOKEN (or SPOTIFY_ACCESS_TOKEN) is present the client operates in user mode. Without them it falls back to the Client Credentials flow, which only allows access to public data.
If a user-scoped endpoint is called and no user token is available, spotifyify starts an interactive Authorization Code login:
- Opens the Spotify consent page in your browser.
- Waits for the redirect on your configured
SPOTIFY_REDIRECT_URI(for examplehttp://localhost:8888/callback). - Exchanges the code for access and refresh tokens.
- Stores tokens in
.spotify_cacheby default (already git-ignored in this project).
Quick start
import asyncio
from spotifyify import Spotifyify, SpotifyScope
async def main():
async with Spotifyify(scopes=[SpotifyScope.USER_READ_PLAYBACK_STATE]) as sp:
state = await sp.player.state()
if state and state.item:
print(f"Now playing: {state.item.name}")
asyncio.run(main())
API design
The Spotifyify class is the entry point. It exposes all Spotify resources as lazy-loaded namespace properties. Every method is a coroutine and must be awaited.
Spotifyify
├── .tracks # Tracks, search, audio features, recommendations
├── .artists # Artists, top tracks, discography, related artists
├── .albums # Albums, new releases
├── .playlists # Playlists, CRUD, track management
├── .player # Playback control, queue, devices, history
├── .library # Saved tracks/albums/shows/episodes, top items
├── .shows # Podcast shows
├── .episodes # Podcast episodes
└── .users # Current user, public profiles, following
Tracks — sp.tracks
| Method | Description |
|---|---|
find(query, *, limit, offset, market) |
Search for tracks |
get(track_id, *, market) |
Get a single track |
get_many(track_ids, *, market) |
Get up to 50 tracks |
audio_features(track_ids) |
Audio features for tracks |
recommendations(*, seed_artists, seed_tracks, seed_genres, limit, market) |
Get recommendations |
Artists — sp.artists
| Method | Description |
|---|---|
find(query, *, limit, offset) |
Search for artists |
get(artist_id) |
Get a single artist |
get_many(artist_ids) |
Get up to 50 artists |
top_tracks(artist_id, *, market) |
Artist's top tracks |
albums(artist_id, *, include_groups, market, limit, offset) |
Artist's discography |
related(artist_id) |
Related artists |
Albums — sp.albums
| Method | Description |
|---|---|
find(query, *, limit, offset, market) |
Search for albums |
get(album_id, *, market) |
Get a single album |
get_many(album_ids, *, market) |
Get up to 20 albums |
tracks(album_id, *, limit, offset, market) |
Tracks in an album |
new_releases(*, country, limit, offset) |
New album releases |
Playlists — sp.playlists
| Method | Description |
|---|---|
find(query, *, limit, offset) |
Search for playlists |
get(playlist_id, *, market) |
Get a single playlist |
list(*, user_id, limit, offset) |
Current user's (or another user's) playlists |
create(name, *, public, collaborative, description, user_id) |
Create a playlist |
update(playlist_id, *, name, public, collaborative, description) |
Update playlist details |
add(playlist_id, uris, *, position) |
Add tracks to a playlist |
remove(playlist_id, uris) |
Remove tracks from a playlist |
reorder(playlist_id, *, range_start, insert_before, range_length, snapshot_id) |
Reorder tracks |
cover_image(playlist_id) |
Get playlist cover images |
Player — sp.player
| Method | Description |
|---|---|
state(*, market) |
Current playback state |
play(*, device_id, context_uri, uris, offset, position_ms) |
Start/resume playback |
pause(*, device_id) |
Pause playback |
skip(*, device_id) |
Skip to next track |
previous(*, device_id) |
Skip to previous track |
seek(position_ms, *, device_id) |
Seek to position |
repeat(state, *, device_id) |
Set repeat mode (track, context, off) |
shuffle(state, *, device_id) |
Toggle shuffle |
volume(volume_percent, *, device_id) |
Set volume (0–100) |
queue() |
Get the player queue |
add_to_queue(uri, *, device_id) |
Add a track/episode to the queue |
transfer(device_id, *, play) |
Transfer playback to another device |
devices() |
List available devices |
recently_played(*, limit, after, before) |
Recently played tracks |
Library — sp.library
| Method | Description |
|---|---|
saved_tracks(*, limit, offset, market) |
User's saved tracks |
saved_albums(*, limit, offset, market) |
User's saved albums |
saved_shows(*, limit, offset) |
User's saved shows |
saved_episodes(*, limit, offset) |
User's saved episodes |
save_tracks(track_ids) |
Save tracks |
remove_tracks(track_ids) |
Remove saved tracks |
save_albums(album_ids) |
Save albums |
remove_albums(album_ids) |
Remove saved albums |
save_shows(show_ids) |
Save shows |
remove_shows(show_ids) |
Remove saved shows |
save_episodes(episode_ids) |
Save episodes |
remove_episodes(episode_ids) |
Remove saved episodes |
check_tracks(track_ids) |
Check if tracks are saved |
check_albums(album_ids) |
Check if albums are saved |
check_shows(show_ids) |
Check if shows are saved |
check_episodes(episode_ids) |
Check if episodes are saved |
top_tracks(*, time_range, limit, offset) |
User's top tracks |
top_artists(*, time_range, limit, offset) |
User's top artists |
time_range accepts "short_term", "medium_term", or "long_term".
Shows — sp.shows
| Method | Description |
|---|---|
find(query, *, limit, offset, market) |
Search for shows |
get(show_id, *, market) |
Get a single show |
get_many(show_ids, *, market) |
Get multiple shows |
episodes(show_id, *, market, limit, offset) |
Episodes for a show |
Episodes — sp.episodes
| Method | Description |
|---|---|
find(query, *, limit, offset, market) |
Search for episodes |
get(episode_id, *, market) |
Get a single episode |
get_many(episode_ids, *, market) |
Get multiple episodes |
Users — sp.users
| Method | Description |
|---|---|
me() |
Current user's profile |
get(user_id) |
A public user's profile |
following(*, type, limit, after) |
Artists/users the current user follows |
follow(type, ids) |
Follow artists or users |
unfollow(type, ids) |
Unfollow artists or users |
check_following(type, ids) |
Check if following artists or users |
Scopes
Use SpotifyScope to declare the OAuth scopes your app requires:
from spotifyify import SpotifyScope
SpotifyScope.USER_READ_PLAYBACK_STATE
SpotifyScope.USER_MODIFY_PLAYBACK_STATE
SpotifyScope.USER_LIBRARY_READ
SpotifyScope.USER_LIBRARY_MODIFY
SpotifyScope.USER_TOP_READ
SpotifyScope.USER_READ_RECENTLY_PLAYED
SpotifyScope.PLAYLIST_MODIFY_PUBLIC
SpotifyScope.PLAYLIST_MODIFY_PRIVATE
SpotifyScope.PLAYLIST_READ_PRIVATE
Scopes can also be passed as plain strings.
Examples
See the examples/ directory for runnable scripts:
examples/search_and_play.py— search for tracks and control playbackexamples/manage_playlist.py— create and manage a playlistexamples/library_stats.py— explore your top tracks and saved library
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 spotifyify-0.2.0.tar.gz.
File metadata
- Download URL: spotifyify-0.2.0.tar.gz
- Upload date:
- Size: 37.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ba750bda696d66a38fde5f03c8b97b63ef705a7f2410b7de35b873bcf1b5244c
|
|
| MD5 |
fe37f063529cfb7e92856b551f24ea75
|
|
| BLAKE2b-256 |
cdf4fa70879438a38741f804b2761dfc2adf46630624071760254cd0116938cd
|
File details
Details for the file spotifyify-0.2.0-py3-none-any.whl.
File metadata
- Download URL: spotifyify-0.2.0-py3-none-any.whl
- Upload date:
- Size: 37.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
016706253a987a1ebdb20dd253510a55a3872330ddaf8717b3aec83987fbefe8
|
|
| MD5 |
ea1ca025baa8ce1024366f0c6e7d3991
|
|
| BLAKE2b-256 |
7546d62ff25e97fc580bce2b1ea4a6e01332464d24d4e6b69787373a0166a702
|