What were you listening to this week last year? What about three years ago?
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 favourites 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.
1. Go to the Spotify Developer Dashboard
Visit https://developer.spotify.com/dashboard and log in with your Spotify account.
2. 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 authorisation) - APIs used: Check "Web API"
3. 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 authorise the application, Spotify redirects your browser to this URI with an authorisation code. Spotipy (the library 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: spotipy (a Spotify API wrapper) and python-dotenv (for environment variable management).
pip install spotipy python-dotenv
Spotipy handles the OAuth token exchange, token refresh, and API request formatting behind the scenes. You could build all of this with requests and raw HTTP calls, but Spotipy saves you from writing hundreds of lines of boilerplate for a solved problem.
Step 3: Store Credentials Securely
Create a .env file 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/callback
Replace the placeholder values with your actual credentials from Step 1.
Now add .env to your .gitignore:
.env
__pycache__/
*.pyc
.cache
*.db
The .cache file 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 is the moment of truth. 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)
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 this as quick_start.py and run it:
python quick_start.py
What happens next: Your browser opens to Spotify's authorisation page. You'll see the permissions your app is requesting (read top tracks, create playlists). Click "Agree." Your browser redirects to localhost:8888/callback, Spotipy catches the authorisation 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 OAuth API, fetched personalised 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:
- Authorisation request: Spotipy opened your browser to
https://accounts.spotify.com/authorizewith your Client ID, requested scopes, and redirect URI. - User consent: You saw which permissions the app needs and clicked "Agree."
- Authorisation code: Spotify redirected to
localhost:8888/callbackwith a temporary authorisation code in the URL. - Token exchange: Spotipy sent the authorisation code (plus your Client ID and Secret) to Spotify's token endpoint and received an access token and refresh token.
- API calls: Every subsequent API call (
current_user_top_tracks,user_playlist_create, etc.) included the access token in theAuthorization: Bearerheader. - Token cached: Spotipy saved the tokens in a
.cachefile. Next time you run the script, it reads from cache instead of making you authorise again. When the access token expires (after 1 hour), Spotipy automatically uses the refresh token to get a new one.
That's the same OAuth dance that powers "Sign in with Google," GitHub integrations, and every third-party app that connects to your accounts. The pattern is identical everywhere — only the endpoints change.
Understanding Scopes: What Permissions Mean
Look at the scope variable in the script:
scope = "user-top-read playlist-modify-public playlist-modify-private"
Each scope grants permission for specific operations:
user-top-read— Read your top tracks and artists. Without this,sp.current_user_top_tracks()returns a 403 Forbidden error.playlist-modify-public— Create and modify public playlists.playlist-modify-private— Create and modify private playlists. The script creates private playlists (public=False).
This is the principle of least privilege: request only the permissions you need. Spotify users see what you're asking for during the consent screen. If you request user-library-modify (which can delete saved songs) but never use it, that's a red flag.
Going Further: Time Ranges and Forgotten Gems
Here's where it gets interesting. Spotify's current_user_top_tracks() method accepts a time_range parameter:
| Time Range | Period | What It Captures |
|---|---|---|
short_term |
~4 weeks | Current obsessions |
medium_term |
~6 months | Sustained favourites |
long_term |
Several years | All-time patterns |
By comparing these time 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")
That's Python set subtraction doing something genuinely useful. Your long-term favourites represent songs you listened to repeatedly over years. Your short-term list is what you're playing now. The difference? Songs you provably loved but fell off your rotation.
You could extend this into a full "Forgotten Gems" playlist creator:
# Get the 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:
artist = track['artists'][0]['name']
print(f" {track['name']} - {artist}")
Going Further: Audio Features and Mood Playlists
Spotify analyses every track in their catalogue and assigns numerical scores for audio characteristics. You can access these through 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)
# Look at what Spotify knows about a track
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")
The key features, all scored 0.0 to 1.0 (except tempo, which is BPM):
- Energy — Intensity and activity. AC/DC scores ~0.9, ambient music scores ~0.1.
- Valence — Musical happiness. "Happy" by Pharrell is 0.96, "Hurt" by Johnny Cash is 0.14.
- Danceability — Beat strength and regularity. Funk and disco score high, free jazz scores low.
- Tempo — Beats per minute. Useful for workout and running playlists.
- Acousticness — How acoustic vs. electronic the track sounds.
- Instrumentalness — Predicts vocal presence. High values suggest no vocals.
Combine these into "mood profiles" and you can generate playlists algorithmically:
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")
You could define profiles for focus music (low energy, high instrumentalness), chill playlists (low energy, moderate valence, acoustic), party playlists (high energy, high danceability), and anything else you can express as audio feature criteria.
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 could 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 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.
Want to build the full Music Time Machine?
This tutorial is adapted from Chapter 16 of Mastering APIs With Python, where the complete project is built end-to-end: OAuth authentication, SQLite database design, four production features (Forgotten Gems, Monthly Snapshots, Mood Playlists, Evolution Analytics), error handling with retry logic, and automated testing with mocks. The book takes you from your first API call to deploying production systems on AWS — 30 chapters for €35.
Get the Book