Build a Spotify Music Time Machine with Python and OAuth 2.0
Source: Dev.to
If you’re like most people, you have no idea. Those songs are gone — not from Spotify’s servers, but from your memory. The playlists you obsessed over in 2022 sit forgotten while your current favorites dominate your queue.
Spotify gives you short‑term recaps and recommendations, but it’s surprisingly hard to revisit the music that defined earlier chapters of your life. You can remember an era, but not the exact tracks that were on repeat.
In this tutorial, you’ll build a tool that fixes that. You’ll authenticate with Spotify using OAuth 2.0, fetch your personal listening data, and create playlists programmatically — all in Python.
By the end, you’ll have:
- A working OAuth 2.0 integration with Spotify
- A script that creates a real playlist in your Spotify account (in about 30 lines of code)
- An understanding of Spotify’s time ranges and audio features
- The foundation for a “Forgotten Gems” feature that rediscovers songs you loved but stopped listening to
Prerequisites: Basic Python knowledge (variables, functions, lists). A Spotify account (free or premium). That’s it.
Step 1: Get Spotify Developer Credentials
Every application that uses Spotify’s API needs credentials: a Client ID and Client Secret. These identify your application to Spotify and enable OAuth authentication.
-
Go to the Spotify Developer Dashboard
Visit and log in with your Spotify account. -
Create an App
Click “Create app” and fill in the form:- App name:
Music Time Machine(or whatever you prefer) - App description:
Personal music analytics and playlist generator - Redirect URI:
http://localhost:8888/callback(exactly this — it’s where OAuth redirects after authorization) - APIs used: check “Web API”
- App name:
-
Copy Your Credentials
After creating the app, click “Settings.” You’ll see your Client ID displayed. Click “View client secret” to reveal your Client Secret. Copy both — you’ll need them in a moment.
Why localhost:8888/callback?
After you authorize the application, Spotify redirects your browser to this URI with an authorization code. The spotipy library (which we’ll use) opens a temporary local server on port 8888 to catch the redirect and extract the code. This is standard OAuth; the redirect URI doesn’t need to be a public server.
Step 2: Install Dependencies
You need two Python packages:
pip install spotipy python-dotenv
- spotipy – a Spotify API wrapper that handles OAuth token exchange, token refresh, and request formatting.
- python-dotenv – loads environment variables from a
.envfile.
Step 3: Store Credentials Securely
-
Create a
.envfile in your project directory. This file stores secrets and should never be committed to version control.SPOTIPY_CLIENT_ID=your_client_id_here SPOTIPY_CLIENT_SECRET=your_client_secret_here SPOTIPY_REDIRECT_URI=http://localhost:8888/callbackReplace the placeholder values with the credentials you copied in Step 1.
-
Add
.envto.gitignore(and other files you don’t want in version control):.env __pycache__/ *.pyc .cache *.dbThe
.cachefile appears when you run the OAuth flow — it stores your access token locally. Keep that out of version control too.
Step 4: Create Your First Playlist
This script authenticates with Spotify, fetches your top tracks, and creates an actual playlist in your account.
"""
Quick Start: Create your first Spotify playlist
Demonstrates OAuth 2.0 authentication and basic API usage
"""
import os
from dotenv import load_dotenv
import spotipy
from spotipy.oauth2 import SpotifyOAuth
# Load credentials from .env file
load_dotenv()
# Define the permissions we need
scope = "user-top-read playlist-modify-public playlist-modify-private"
# Create Spotify client with OAuth
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
# Get current user info
user = sp.current_user()
print(f"Authenticated as: {user['display_name']}")
# Fetch top tracks from last 4 weeks
print("\nFetching your top tracks...")
top_tracks = sp.current_user_top_tracks(limit=20, time_range='short_term')
# Extract track URIs (Spotify's unique identifiers) and names
track_uris = [track['uri'] for track in top_tracks['items']]
track_names = [track['name'] for track in top_tracks['items']]
# Create a new playlist
print("\nCreating playlist...")
playlist = sp.user_playlist_create(
user=user['id'],
name="My Top Tracks - Quick Start",
public=False,
description="Created by Music Time Machine - My current favorites"
)
# Add tracks to the playlist
sp.playlist_add_items(playlist['id'], track_uris)
print(f"\nSuccess! Created playlist with {len(track_uris)} tracks")
print(f"Playlist URL: {playlist['external_urls']['spotify']}")
print("\nYour top tracks right now:")
for i, name in enumerate(track_names, 1):
print(f" {i}. {name}")
-
Save the script as
quick_start.py. -
Run it:
python quick_start.py
What happens next: Your browser opens to Spotify’s authorization page. After you click “Agree,” the browser redirects to localhost:8888/callback. spotipy catches the authorization code, exchanges it for an access token, and the script continues.
Check your Spotify app — the playlist is there. You just integrated with a real‑world API! 🎉
Feel free to expand this foundation: fetch longer‑term top tracks (medium_term, long_term), analyze audio features, or build a “Forgotten Gems” playlist that surfaces tracks you loved in the past but haven’t played recently.
OAuth API, fetched personalized data, and created content in your account – all in about 30 lines of Python
What Just Happened: OAuth 2.0 in 60 seconds
When you ran that script, Spotipy handled a complete OAuth 2.0 Authorization Code flow:
| Step | What Spotipy did |
|---|---|
| Authorization request | Opened your browser to https://accounts.spotify.com/authorize with your Client ID, requested scopes, and redirect URI. |
| User consent | Showed the permissions the app needs; you clicked Agree. |
| Authorization code | Spotify redirected to http://localhost:8888/callback with a temporary code in the URL. |
| Token exchange | Spotipy sent the code (plus your Client ID & Secret) to Spotify’s token endpoint and received an access token and refresh token. |
| API calls | Every subsequent call (current_user_top_tracks, user_playlist_create, …) included the access token in the Authorization: Bearer … header. |
| Token cached | Tokens were saved in a .cache file. On later runs Spotipy reads the cache, avoiding another authorization step. When the access token expires (≈ 1 hour), Spotipy automatically uses the refresh token to obtain a new one. |
That’s the same OAuth dance that powers “Sign in with Google”, GitHub integrations, and virtually every third‑party app that connects to your accounts. The pattern is identical everywhere – only the endpoints differ.
Understanding Scopes: What Permissions Mean
scope = "user-top-read playlist-modify-public playlist-modify-private"
| Scope | Permission |
|---|---|
user-top-read | Read your top tracks and artists. Without it, sp.current_user_top_tracks() returns 403 Forbidden. |
playlist-modify-public | Create and modify public playlists. |
playlist-modify-private | Create and modify private playlists (the script creates private playlists with public=False). |
Principle of least privilege – request only the permissions you actually need. Spotify users see exactly what you’re asking for on the consent screen. Asking for a scope you never use (e.g.,
user-library-modify) is a red flag.
Going Further: Time Ranges and Forgotten Gems
Spotify’s current_user_top_tracks() method accepts a time_range parameter:
| Time range | Approx. period | What it captures |
|---|---|---|
short_term | ~4 weeks | Current obsessions |
medium_term | ~6 months | Sustained favorites |
long_term | Several years | All‑time patterns |
By comparing these ranges you can discover songs you’ve forgotten about:
# Fetch top tracks for each time range
short_term = sp.current_user_top_tracks(limit=50, time_range='short_term')
long_term = sp.current_user_top_tracks(limit=50, time_range='long_term')
# Extract track IDs into sets
short_ids = {track['id'] for track in short_term['items']}
long_ids = {track['id'] for track in long_term['items']}
# Forgotten gems: songs in your long‑term favorites that you haven't listened to recently
forgotten = long_ids - short_ids
print(f"You loved {len(forgotten)} tracks long‑term but haven't heard them recently")
Creating a “Forgotten Gems” playlist
# Get full track details for forgotten gems
forgotten_tracks = [
track for track in long_term['items']
if track['id'] in forgotten
]
# Create the playlist
user = sp.current_user()
playlist = sp.user_playlist_create(
user=user['id'],
name="Forgotten Gems",
public=False,
description="Songs I loved but forgot about – rediscovered by Music Time Machine"
)
track_uris = [track['uri'] for track in forgotten_tracks]
sp.playlist_add_items(playlist['id'], track_uris)
print(f"\nCreated 'Forgotten Gems' with {len(forgotten_tracks)} tracks")
for track in forgotten_tracks:
print(f" - {track['name']} – {track['artists'][0]['name']}")
Going Further: Audio Features and Mood Playlists
Spotify analyses every track and assigns numerical scores for audio characteristics. You can retrieve them via the API:
# Fetch audio features for your top tracks
tracks = sp.current_user_top_tracks(limit=50)['items']
track_ids = [track['id'] for track in tracks]
features_list = sp.audio_features(track_ids)
# Show a few examples
for track, features in zip(tracks[:3], features_list[:3]):
if features:
print(f"\n{track['name']} – {track['artists'][0]['name']}")
print(f" Energy: {features['energy']:.2f}")
print(f" Valence: {features['valence']:.2f} (happiness)")
print(f" Danceability: {features['danceability']:.2f}")
print(f" Tempo: {features['tempo']:.0f} BPM")
Key audio‑feature scores (0.0 – 1.0, except tempo)
| Feature | Meaning |
|---|---|
| Energy | Intensity & activity (e.g., AC/DC ≈ 0.9, ambient ≈ 0.1). |
| Valence | Musical happiness (e.g., “Happy” – 0.96, “Hurt” – 0.14). |
| Danceability | Beat strength & regularity (high for funk/disco, low for free jazz). |
| Tempo | Beats per minute (useful for workout/run playlists). |
| Acousticness | Likelihood of being acoustic vs. electronic. |
| Instrumentalness | Predicts presence of vocals (high → instrumental). |
Example: Build a workout‑ready playlist
def matches_workout_profile(features):
"""Return True if a track looks good for a high‑intensity workout."""
return (
features['energy'] > 0.7 and
features['danceability'] > 0.7 and
features['tempo'] > 120
)
# Filter top tracks for workout suitability
workout_tracks = [
track['uri'] for track, feat in zip(tracks, features_list)
if feat and matches_workout_profile(feat)
]
# Create the playlist
playlist = sp.user_playlist_create(
user=user['id'],
name="Workout Boost",
public=False,
description="High‑energy tracks for the gym – generated from my top‑50"
)
sp.playlist_add_items(playlist['id'], workout_tracks)
print(f"Created 'Workout Boost' with {len(workout_tracks)} tracks")
def matches_workout_profile(features):
"""Check if a track suits a workout playlist"""
return (
features['energy'] > 0.75 and
features['valence'] > 0.50 and
features['tempo'] > 140 and
features['danceability'] > 0.6
)
# Filter your top tracks for workout‑suitable songs
workout_tracks = [
tracks[i] for i in range(len(tracks))
if features_list[i] and matches_workout_profile(features_list[i])
]
print(f"Found {len(workout_tracks)} workout tracks from your top 50")
Where to Go From Here
In this tutorial you built a working Spotify OAuth integration, created playlists programmatically, and explored time ranges and audio features. That’s a solid foundation—but there’s a lot more you can build on top of it.
Ideas to explore
- Monthly snapshots – Save your top 50 tracks each month into a database. After a few months you’ll have a musical diary you can query: “What was I listening to last March?”
- Database‑powered Forgotten Gems – The set‑subtraction approach above works with live API data. With a SQLite database accumulating monthly snapshots you get a much richer history to mine.
- Evolution analytics – Track how your music taste changes over time. Calculate turnover rates, spot genre shifts, see if your summer playlists are more upbeat.
- Mood playlist generator with scoring – Instead of strict pass/fail filtering, score each track against a mood profile and take the top N matches.
Each of these builds on the same pattern: fetch from Spotify’s API, store or compare with historical data, generate something useful.
This tutorial is adapted from Chapter 16 of Mastering APIs With Python. If you want to go deeper—SQLite database design, error handling with retry logic, automated testing with mocks, and 29 more chapters covering everything from your first API call to deploying on AWS—check it out.