DEV Community

Alex Spinov
Alex Spinov

Posted on Edited on

Spotify API: Build a Music Discovery Tool in 50 Lines of Python

Spotify's API is one of the most generous free APIs available. You get access to 100 million tracks, audio features, recommendations, and playlist data — all for free.

Here's how to build a music discovery tool in 50 lines.

Setup (2 Minutes)

  1. Go to developer.spotify.com
  2. Create an app (free)
  3. Copy your Client ID and Client Secret
pip install httpx
Enter fullscreen mode Exit fullscreen mode

Authentication

Spotify uses Client Credentials flow for public data:

import httpx
import base64

CLIENT_ID = 'your_client_id'
CLIENT_SECRET = 'your_client_secret'

def get_token():
    auth = base64.b64encode(f'{CLIENT_ID}:{CLIENT_SECRET}'.encode()).decode()
    r = httpx.post('https://accounts.spotify.com/api/token',
        headers={'Authorization': f'Basic {auth}'},
        data={'grant_type': 'client_credentials'}
    )
    return r.json()['access_token']

token = get_token()
headers = {'Authorization': f'Bearer {token}'}
Enter fullscreen mode Exit fullscreen mode

Search for Artists

def search_artist(name):
    r = httpx.get('https://api.spotify.com/v1/search',
        headers=headers,
        params={'q': name, 'type': 'artist', 'limit': 1}
    )
    artist = r.json()['artists']['items'][0]
    return {
        'name': artist['name'],
        'id': artist['id'],
        'followers': artist['followers']['total'],
        'genres': artist['genres'],
        'popularity': artist['popularity']
    }

print(search_artist('Radiohead'))
# {'name': 'Radiohead', 'followers': 11M, 'genres': ['art rock', ...], 'popularity': 79}
Enter fullscreen mode Exit fullscreen mode

Get Top Tracks

def top_tracks(artist_id, market='US'):
    r = httpx.get(f'https://api.spotify.com/v1/artists/{artist_id}/top-tracks',
        headers=headers,
        params={'market': market}
    )
    return [{
        'name': t['name'],
        'album': t['album']['name'],
        'popularity': t['popularity'],
        'preview_url': t['preview_url']
    } for t in r.json()['tracks'][:5]]
Enter fullscreen mode Exit fullscreen mode

Get Audio Features (The Cool Part)

Spotify analyzes every track and provides:

  • danceability — how suitable for dancing (0-1)
  • energy — intensity and activity (0-1)
  • valence — musical positivity (0-1)
  • tempo — BPM
  • acousticness — acoustic vs electronic (0-1)
def audio_features(track_id):
    r = httpx.get(f'https://api.spotify.com/v1/audio-features/{track_id}',
        headers=headers
    )
    f = r.json()
    return {
        'danceability': f['danceability'],
        'energy': f['energy'],
        'valence': f['valence'],
        'tempo': f['tempo'],
        'acousticness': f['acousticness']
    }
Enter fullscreen mode Exit fullscreen mode

Discover Similar Artists

def discover(artist_name):
    """Find similar artists with different vibes."""
    artist = search_artist(artist_name)
    r = httpx.get(f'https://api.spotify.com/v1/artists/{artist["id"]}/related-artists',
        headers=headers
    )
    related = r.json()['artists'][:10]
    return [{
        'name': a['name'],
        'genres': a['genres'][:3],
        'popularity': a['popularity'],
        'followers': f"{a['followers']['total']:,}"
    } for a in related]

# Find artists similar to Radiohead
for artist in discover('Radiohead'):
    print(f"{artist['name']} ({artist['popularity']}/100) — {', '.join(artist['genres'])}")
Enter fullscreen mode Exit fullscreen mode

Get Recommendations

def get_recommendations(seed_artists=None, seed_genres=None, **kwargs):
    params = {'limit': 10}
    if seed_artists:
        params['seed_artists'] = ','.join(seed_artists)
    if seed_genres:
        params['seed_genres'] = ','.join(seed_genres)
    params.update(kwargs)  # target_energy=0.8, min_tempo=120, etc.

    r = httpx.get('https://api.spotify.com/v1/recommendations',
        headers=headers, params=params
    )
    return [{'name': t['name'], 'artist': t['artists'][0]['name']} 
            for t in r.json()['tracks']]

# High energy dance tracks
recs = get_recommendations(
    seed_genres=['electronic', 'dance'],
    target_energy=0.9,
    target_danceability=0.8,
    min_tempo=120
)
Enter fullscreen mode Exit fullscreen mode

Rate Limits

  • Free tier: ~180 requests/minute
  • No API key cost
  • Rate limit headers in response

What You Can Build

  • Mood-based playlist generator
  • Artist similarity explorer
  • Music taste analyzer
  • Genre evolution tracker
  • DJ set builder (match BPM/key)

More API Tutorials


What would you build with the Spotify API? Mood playlists? Music analytics? Something else? 👇

API tutorials at dev.to/0012303


More from me: 10 Dev Tools I Use Daily | 77 Scrapers on a Schedule | 150+ Free APIs


Need web scraping or data extraction? I've built 77+ production scrapers. Email spinov001@gmail.com — quote in 2 hours. Or try my ready-made Apify actors — no code needed.

Top comments (2)

Collapse
 
jakemoreno_dev profile image
Jake Moreno

really cool tutorial, the audio features endpoint is the hidden gem here imo. being able to pull stuff like acousticness and valence opens up some interesting possibilities for building mood-based playlists.

one thing I've been messing with lately is using the related artists endpoint to map out genre connections — like starting from a folk artist and seeing how many hops it takes to get to metal lol. the recommendation seeds are powerful for that kind of exploration too.

quick question — have you run into any issues with the audio features endpoint returning null for certain tracks? I noticed some newer releases sometimes don't have that data populated right away. curious if you found a workaround or if it's just a timing thing on Spotify's end.

Collapse
 
0012303 profile image
Alex Spinov

Your question has aged into a much bigger answer, and it is bad news for the post rather than for you.

I went and checked Spotify's own reference pages today. Four of the endpoints that tutorial leans on are now marked Deprecated in their docs: Get Track's Audio Features, Get Track's Audio Analysis, Get Recommendations, and Get Artist's Related Artists. Get Track itself is still fine. So the nulls you were hitting were not only new releases lagging behind — you were looking at an endpoint already on its way out, and the "hidden gem" framing in my post has not aged well. Same goes for the related-artists genre-hopping idea, which is a genuinely nicer use of that data and sits on the same list. Sorry. That one deserved to survive.

On the original nulls, for what it is worth: I never found a real workaround. What I settled on was treating a missing feature vector as a normal state rather than an error — skip the track, count the skip, print the count per run. If the miss rate moves, you have learned something about the endpoint instead of about your parsing. The version I regret is filling the gap with a neighbouring track's values to keep the playlist logic tidy, because that hides exactly the signal you would want to see.

Post flagged for correction. Thanks for asking a question specific enough to be checkable 165 days later.