Skip to main content
RapidDev - Software Development Agency
API AutomationsSpotifyOAuth 2.0

How to Automate Spotify Song Recommendations using the API

GET /v1/recommendations is permanently deprecated for Spotify apps created after November 27, 2024 (returns 403). The working alternative: use GET /v1/me/top/tracks and GET /v1/me/top/artists to get seed data, then GET /v1/search with genre and artist keywords to discover similar tracks, and POST /v1/playlists/{id}/tracks to save results. This approach requires 10-20 search queries per session — throttle to 3 requests/second to stay within the rolling 30-second rate limit.

Need help automating? Talk to an expert
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate7 min read30-60 minutesSpotifyMay 2026RapidDev Engineering Team
TL;DR

GET /v1/recommendations is permanently deprecated for Spotify apps created after November 27, 2024 (returns 403). The working alternative: use GET /v1/me/top/tracks and GET /v1/me/top/artists to get seed data, then GET /v1/search with genre and artist keywords to discover similar tracks, and POST /v1/playlists/{id}/tracks to save results. This approach requires 10-20 search queries per session — throttle to 3 requests/second to stay within the rolling 30-second rate limit.

API Quick Reference

Auth

OAuth 2.0

Rate limit

Rolling 30-second window (exact threshold undisclosed)

Format

JSON

SDK

Available

Understanding the Spotify Web API

The Spotify Web API is a REST API at https://api.spotify.com/v1. This page specifically addresses the most common broken-tutorial problem in the Spotify developer ecosystem: GET /v1/recommendations was permanently deprecated for apps created after November 27, 2024. If you've been following a tutorial that uses this endpoint, it will return HTTP 403 on any app you created after that date. Grandfathered Extended Quota apps from before that date still have access — which is why many tutorials still reference it.

What else was deprecated at the same time: GET /v1/audio-features, GET /v1/audio-analysis, GET /v1/artists/{id}/related-artists, GET /v1/browse/featured-playlists, GET /v1/browse/categories/{id}/playlists, and the 30-second preview_url field. These are all permanently unavailable for new apps.

The working alternative for building recommendation-like functionality uses a three-step approach: understand the user's taste via their top tracks and artists, extract genre signals and artist names, then use GET /v1/search with those signals as queries to discover similar catalog content. This is more manual than the old recommendations API but works for all apps. Official docs: https://developer.spotify.com/documentation/web-api

Base URLhttps://api.spotify.com/v1

Setting Up Spotify API Authentication

This automation requires user-specific data (top tracks/artists) and the ability to create playlists. Use OAuth 2.0 Authorization Code flow with user-top-read and playlist-modify-private scopes. The access token expires in 3,600 seconds — implement token refresh using your stored refresh token before each run.

  1. 1Log in to https://developer.spotify.com/dashboard with your Spotify Premium account
  2. 2Click 'Create app', add a name, description, and set a redirect URI (e.g., http://localhost:8888/callback)
  3. 3Copy your Client ID and Client Secret from the app settings page
  4. 4Build the authorization URL with the required scopes: GET https://accounts.spotify.com/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REDIRECT_URI&scope=user-top-read%20playlist-modify-private
  5. 5After user authorization, extract the code from the redirect URL query parameter
  6. 6Exchange code for tokens: POST https://accounts.spotify.com/api/token with grant_type=authorization_code, code, redirect_uri, Authorization: Basic base64(client_id:client_secret)
  7. 7Store access_token and refresh_token securely in environment variables
  8. 8Refresh the token before each run: POST https://accounts.spotify.com/api/token with grant_type=refresh_token
auth.py
1import os
2import base64
3import requests
4
5CLIENT_ID = os.environ['SPOTIFY_CLIENT_ID']
6CLIENT_SECRET = os.environ['SPOTIFY_CLIENT_SECRET']
7REFRESH_TOKEN = os.environ['SPOTIFY_REFRESH_TOKEN']
8
9def get_access_token():
10 creds = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()
11 response = requests.post(
12 'https://accounts.spotify.com/api/token',
13 headers={'Authorization': f'Basic {creds}'},
14 data={'grant_type': 'refresh_token', 'refresh_token': REFRESH_TOKEN}
15 )
16 response.raise_for_status()
17 return response.json()['access_token']
18
19token = get_access_token()
20headers = {'Authorization': f'Bearer {token}'}

Security notes

  • Store Client ID, Client Secret, and refresh token in environment variables — never in source code
  • Refresh tokens are long-lived — encrypt at rest if storing in a database
  • Access tokens expire in 3,600 seconds — always refresh before each automation run
  • Only request user-top-read and playlist-modify-private — do not request public playlist scopes unless needed
  • Never log token values — log token refresh events only
  • In Development Mode, only 5 users can authorize your app — do not share credentials between test accounts

Key endpoints

GET/v1/me/top/tracks

Returns the user's top tracks. Use short_term for current taste signals to seed the recommendation search. The track names and artist IDs here drive the search queries.

ParameterTypeRequiredDescription
time_rangestringoptionalshort_term (~4 weeks), medium_term (~6 months), long_term (years). Use short_term for current taste.
limitnumberoptionalNumber of items, 1-50

Response

json
1{"items":[{"id":"4iV5W9uYEdYUVa79Axb7Rh","name":"Everlong","artists":[{"id":"7jy3rLJdDQY21OgRLCZ9sD","name":"Foo Fighters"}],"album":{"name":"The Colour and the Shape"},"popularity":74,"uri":"spotify:track:4iV5W9uYEdYUVa79Axb7Rh"}],"total":50}
GET/v1/me/top/artists

Returns the user's top artists with genre arrays. Genres are the primary seed signal for building search-based recommendations since audio features are deprecated.

ParameterTypeRequiredDescription
time_rangestringoptionalshort_term, medium_term, or long_term
limitnumberoptionalNumber of items, 1-50

Response

json
1{"items":[{"id":"7jy3rLJdDQY21OgRLCZ9sD","name":"Foo Fighters","genres":["alternative metal","alternative rock","grunge","post-grunge","rock"],"popularity":79}],"total":50}
GET/v1/search

Search the Spotify catalog for tracks matching genre, artist, or keyword queries. This is the core engine for discovery in the absence of the deprecated /recommendations endpoint.

ParameterTypeRequiredDescription
qstringrequiredQuery string. Supports field filters: genre:, artist:, album:, year:. E.g., 'genre:grunge year:2020-2026'
typestringrequiredMust include 'track' for track results
limitnumberoptionalResults per query, 1-50
marketstringoptionalISO country code for availability filtering

Response

json
1{"tracks":{"items":[{"id":"0VjIjW4GlUZAMYd2vXMi3b","name":"The Pretender","artists":[{"name":"Foo Fighters"}],"popularity":77,"uri":"spotify:track:0VjIjW4GlUZAMYd2vXMi3b","album":{"name":"Echoes, Silence, Patience & Grace","release_date":"2007-09-28"}}],"total":1200}}
POST/v1/playlists/{playlist_id}/tracks

Add discovered tracks to a playlist. Use this to save the recommendation results as a 'Discover' playlist.

ParameterTypeRequiredDescription
playlist_idstringrequiredSpotify playlist ID to add tracks to
urisarrayrequiredArray of Spotify track URIs, max 100 per request
positionnumberoptionalZero-based insert position, default appends to end

Request

json
1{"uris":["spotify:track:0VjIjW4GlUZAMYd2vXMi3b","spotify:track:4iV5W9uYEdYUVa79Axb7Rh"]}

Response

json
1{"snapshot_id":"MTY2LDljYzE0OTM4MjVkN2UxMjdmNzJiNGI4YjBiZmMxMzIxMzExYjM0ZQ=="}

Step-by-step automation

1

Understand What Was Deprecated and Why

Why: Using GET /v1/recommendations on a new app will always return 403 — you need to know exactly what was removed to build the right alternative.

The following endpoints are permanently unavailable for apps created after November 27, 2024: GET /v1/recommendations, GET /v1/audio-features/{id} (and batch), GET /v1/audio-analysis/{id}, GET /v1/artists/{id}/related-artists, GET /v1/browse/featured-playlists, GET /v1/browse/categories/{id}/playlists, and the preview_url field. These are not temporary restrictions — they are permanent for new apps. Do not attempt to work around the 403 by changing app settings; it cannot be fixed for new apps. The alternative approach uses search queries seeded by the user's listening history.

request.sh
1# This WILL return 403 for apps created after Nov 27, 2024:
2# curl 'https://api.spotify.com/v1/recommendations?seed_genres=rock&limit=20' \
3# -H 'Authorization: Bearer YOUR_TOKEN'
4
5# Verify your app creation date at developer.spotify.com/dashboard
6# If your app was created before Nov 27, 2024 AND has Extended Quota Mode, it still works.
7# For all new apps, use the search-based approach shown in steps 2-4 below.

Pro tip: If you see tutorials using /recommendations dated 2024 or later, check if the author notes they have a 'grandfathered Extended Quota app'. Those tutorials work for them but will 403 for you.

Expected result: Clear understanding of which endpoints are unavailable and the working alternative flow.

2

Extract Genre and Artist Seeds from Listening History

Why: The user's top genres and artists become the search query seeds for discovery — this is the direct replacement for the recommendations endpoint's seed_genres and seed_artists parameters.

Fetch the user's top artists using short_term time range. Extract the genre arrays from each artist and count frequency across the top 20 artists to find dominant genres. Also collect the top 5 artist names as search seeds. These will be used to construct targeted search queries in the next step.

request.sh
1curl -G 'https://api.spotify.com/v1/me/top/artists' \
2 -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
3 -d 'time_range=short_term' \
4 -d 'limit=20'

Pro tip: Use weighted genre scoring where top-ranked artists contribute more to the genre score than lower-ranked ones. This prevents a single artist with unusual genre tags from dominating the discovery queries.

Expected result: A list of top genres (e.g., ['alternative rock', 'post-grunge', 'grunge']), top artist names, and a set of already-heard track IDs to exclude from recommendations.

3

Search Catalog Using Genre and Artist Seeds

Why: Multiple targeted search queries across different genres produce a diverse pool of discovery candidates.

For each top genre, run a GET /v1/search?q=genre:{genre}&type=track&limit=20 query. Also run artist-based queries like genre:{artist_name_genre}. This produces a pool of 100-200 candidate tracks. Add 333ms delays between requests to stay at 3 requests/second. Filter out tracks already in the user's top tracks (the already_heard_ids set from the previous step) and deduplicate by track ID.

request.sh
1# Search by genre
2curl -G 'https://api.spotify.com/v1/search' \
3 -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
4 --data-urlencode 'q=genre:alternative rock year:2022-2026' \
5 -d 'type=track' \
6 -d 'limit=20' \
7 -d 'market=US'
8
9# Search by artist similarity
10curl -G 'https://api.spotify.com/v1/search' \
11 -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
12 --data-urlencode 'q=artist:Foo Fighters genre:post-grunge' \
13 -d 'type=track' \
14 -d 'limit=20' \
15 -d 'market=US'

Pro tip: Popularity scores (0-100) reflect recent global streaming, not your personal affinity. Mix high-popularity (>60) and mid-popularity (40-60) tracks to balance chart hits with more niche discoveries that match your genre taste.

Expected result: A deduplicated pool of candidate tracks not in the user's recent listening history, sorted by popularity.

4

Save Recommendations to a Discover Playlist

Why: Creating a 'Discover' playlist gives the user a persistent, playable destination for the recommended tracks.

Get the user ID from GET /v1/me, then create or update a 'Discover Weekly (Manual)' private playlist. Use PUT /v1/playlists/{id}/tracks to replace tracks on existing playlists, or POST /v1/users/{user_id}/playlists followed by POST /v1/playlists/{id}/tracks for new ones. Log the playlist URL so the user can find it.

request.sh
1# Create playlist
2curl -X POST 'https://api.spotify.com/v1/users/YOUR_USER_ID/playlists' \
3 -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
4 -H 'Content-Type: application/json' \
5 -d '{"name":"Discover (Manual)","description":"Search-based recommendations from your taste profile","public":false}'
6
7# Add tracks (replace YOUR_PLAYLIST_ID and track URIs)
8curl -X POST 'https://api.spotify.com/v1/playlists/YOUR_PLAYLIST_ID/tracks' \
9 -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
10 -H 'Content-Type: application/json' \
11 -d '{"uris":["spotify:track:0VjIjW4GlUZAMYd2vXMi3b","spotify:track:4iV5W9uYEdYUVa79Axb7Rh"]}'

Pro tip: Use PUT (replace) rather than POST (append) when updating an existing playlist — this keeps the playlist fresh and prevents it from growing to hundreds of tracks over time.

Expected result: A private 'Discover (Manual)' playlist created or updated with the recommended tracks. Console output includes the Spotify playlist URL.

Complete working code

This complete script runs the full search-based recommendation pipeline: refreshes OAuth token, fetches listening history for seeds, runs genre and artist searches, deduplicates and filters out heard tracks, and saves a 30-track 'Discover' playlist. This is the working alternative to the deprecated GET /v1/recommendations endpoint.

automate_song_recommendations.py
1import os
2import base64
3import time
4import requests
5from collections import Counter
6
7CLIENT_ID = os.environ['SPOTIFY_CLIENT_ID']
8CLIENT_SECRET = os.environ['SPOTIFY_CLIENT_SECRET']
9REFRESH_TOKEN = os.environ['SPOTIFY_REFRESH_TOKEN']
10PLAYLIST_NAME = os.environ.get('DISCOVER_PLAYLIST_NAME', 'Discover (Manual)')
11
12def get_token():
13 creds = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()
14 r = requests.post('https://accounts.spotify.com/api/token',
15 headers={'Authorization': f'Basic {creds}'},
16 data={'grant_type': 'refresh_token', 'refresh_token': REFRESH_TOKEN})
17 r.raise_for_status()
18 return r.json()['access_token']
19
20def api_get(token, path, params=None):
21 r = requests.get(f'https://api.spotify.com/v1{path}',
22 headers={'Authorization': f'Bearer {token}'}, params=params)
23 if r.status_code == 429:
24 time.sleep(int(r.headers.get('Retry-After', 5)))
25 return api_get(token, path, params)
26 r.raise_for_status()
27 return r.json()
28
29def api_post(token, path, payload):
30 r = requests.post(f'https://api.spotify.com/v1{path}',
31 headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'},
32 json=payload)
33 r.raise_for_status()
34 return r.json()
35
36def main():
37 token = get_token()
38
39 # Get seed data from listening history
40 top_artists = api_get(token, '/me/top/artists', {'time_range': 'short_term', 'limit': 20})['items']
41 top_tracks = api_get(token, '/me/top/tracks', {'time_range': 'medium_term', 'limit': 50})['items']
42 already_heard = {t['id'] for t in top_tracks}
43
44 # Extract genres (weighted by rank)
45 genre_scores = Counter()
46 for i, a in enumerate(top_artists):
47 for g in a['genres']:
48 genre_scores[g] += (20 - i)
49 top_genres = [g for g, _ in genre_scores.most_common(5)]
50 top_artist_names = [a['name'] for a in top_artists[:3]]
51 print(f'Seeds: genres={top_genres[:3]}, artists={top_artist_names}')
52
53 # Build search queries
54 queries = [f'genre:{g} year:2020-2026' for g in top_genres]
55 queries += [f'artist:"{n}"' for n in top_artist_names]
56
57 # Run searches with rate limiting
58 candidates = {}
59 for q in queries:
60 results = api_get(token, '/search', {'q': q, 'type': 'track', 'limit': 20, 'market': 'US'})
61 for track in results['tracks']['items']:
62 if track['id'] not in already_heard:
63 candidates[track['id']] = track
64 time.sleep(0.333)
65
66 # Select top 30 by popularity
67 recommended = sorted(candidates.values(), key=lambda t: t['popularity'], reverse=True)[:30]
68 print(f'Selected {len(recommended)} tracks')
69
70 # Create or update playlist
71 user_id = api_get(token, '/me')['id']
72 playlists = api_get(token, '/me/playlists', {'limit': 50})['items']
73 existing = next((p for p in playlists if p['name'] == PLAYLIST_NAME), None)
74 uris = [t['uri'] for t in recommended]
75
76 if existing:
77 requests.put(f'https://api.spotify.com/v1/playlists/{existing["id"]}/tracks',
78 headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'},
79 json={'uris': uris}).raise_for_status()
80 print(f'Updated: https://open.spotify.com/playlist/{existing["id"]}')
81 else:
82 p = api_post(token, f'/users/{user_id}/playlists', {
83 'name': PLAYLIST_NAME, 'public': False, 'description': 'Search-based discovery from your taste'})
84 api_post(token, f'/playlists/{p["id"]}/tracks', {'uris': uris})
85 print(f'Created: {p["external_urls"]["spotify"]}')
86
87if __name__ == '__main__':
88 main()

Error handling

403{"error":{"status":403,"message":"Player command failed: Premium required"}}
Cause

Most commonly: calling GET /v1/recommendations or GET /v1/audio-features on an app created after November 27, 2024. These are permanently deprecated for new apps. Also returned when trying to access playback controls without Premium.

Fix

Remove all calls to /recommendations, /audio-features, /audio-analysis, /artists/{id}/related-artists from your code. Use the search-based approach documented in this guide. There is no workaround or app setting that restores these endpoints for new apps.

Retry strategy

Not retryable — the endpoint is permanently unavailable for new apps.

401{"error":{"status":401,"message":"No token provided"}}
Cause

Access token expired (3,600 second lifetime) or was not included in the request.

Fix

Refresh the access token using the refresh_token grant before each automation run.

Retry strategy

Refresh immediately and retry once.

429{"error":{"status":429,"message":"API rate limit exceeded"}}
Cause

The rolling 30-second window threshold was exceeded. Running 10-20 search queries in rapid succession can trigger this.

Fix

Add 333ms delays between search requests (3 per second). Honor the Retry-After header when 429 is received.

Retry strategy

Read Retry-After header and wait exactly that many seconds. Then resume with delays between requests.

400{"error":{"status":400,"message":"Bad Request"}}
Cause

Malformed search query, invalid track URIs in playlist add request, or missing required parameters.

Fix

Validate track URIs before adding to playlists — they must be in format 'spotify:track:{id}'. URL-encode search query strings. Do not include null values in the uris array.

Retry strategy

Not retryable — fix the request.

Rate Limits for Spotify API

ScopeLimitWindow
Per appUndisclosed exact thresholdRolling 30 seconds
Search queries (practical guidance)3 requests per second (conservative)Add 333ms delay between search calls
Playlist track add100 URIs per requestPer API call
retry-handler.ts
1import time
2
3def search_with_retry(token, query, max_retries=3):
4 import requests
5 for attempt in range(max_retries):
6 r = requests.get('https://api.spotify.com/v1/search',
7 headers={'Authorization': f'Bearer {token}'},
8 params={'q': query, 'type': 'track', 'limit': 20, 'market': 'US'})
9 if r.status_code == 429:
10 wait = int(r.headers.get('Retry-After', 2 ** attempt))
11 print(f'Rate limited, waiting {wait}s...')
12 time.sleep(wait)
13 elif r.ok:
14 return r.json()['tracks']['items']
15 else:
16 r.raise_for_status()
17 return []
  • Throttle search queries to 3 per second (333ms between requests) to avoid hitting the rolling rate limit
  • Cache search results for a given genre set — running the same genre queries daily produces similar results and wastes API calls
  • Limit total search queries per run to 10-15 — more queries produce diminishing returns on discovery diversity
  • Honor the Retry-After header on every 429 response — do not implement fixed sleep durations
  • Add exponential backoff for consecutive 429 responses: 1s, 2s, 4s, 8s

Security checklist

  • Store Client ID, Client Secret, and refresh token in environment variables — never in code or version control
  • Only request user-top-read and playlist-modify-private scopes — do not request broader permissions
  • Refresh tokens are long-lived — encrypt at rest if persisted in a database
  • Never use Client Credentials flow for this automation — it cannot access user listening history
  • Log automation runs for audit trail but never log token values
  • In Development Mode, only the 5 authorized users can generate recommendations — do not expose the OAuth flow publicly
  • Validate track URIs before adding to playlists to prevent malformed request injection

Automation use cases

Weekly Discover Playlist

intermediate

Run every Monday to refresh a 'Discover (Manual)' playlist with tracks matching the user's current short-term taste.

Genre-Specific Discovery

intermediate

Instead of using the user's top genres, allow a user to input specific genres manually and generate a discovery playlist for that specific sound.

Collaborative Discovery

advanced

Merge genre seeds from two users (both authorized with the 5-user Development Mode limit) and create a shared discovery playlist with tracks that match both taste profiles.

Mood-Based Search

intermediate

Add mood keywords (e.g., 'focus', 'workout', 'sleep') to genre searches for mood-specific discovery playlists without audio features data.

No-code alternatives

Don't want to write code? These platforms can automate the same workflows visually.

Zapier

Free tier available; paid plans from $19.99/month

Zapier's Spotify integration can trigger on listening events and search for tracks, though multi-step recommendation logic requires a complex multi-action Zap.

Pros
  • + No code required
  • + Easy playlist creation from search results
  • + Connects to 7,000+ other apps
Cons
  • - Complex multi-step recommendation logic is difficult to express in Zap format
  • - Free tier limited to 100 tasks/month
  • - No native genre-seed discovery flow

Make

Free tier available; paid plans from $9/month

Make's HTTP modules can replicate this exact search-based discovery pipeline with genre seeds from the Spotify node's top artists endpoint.

Pros
  • + Visual scenario builder for multi-step flows
  • + HTTP Request module for full API access
  • + Free tier includes 1,000 operations/month
Cons
  • - More complex to set up than a Python script
  • - Genre aggregation logic requires custom functions
  • - Rate limiting management is less precise than code

n8n

Self-hosted free; cloud from €20/month

n8n's Spotify node combined with Code nodes and HTTP Request modules can replicate the full search-based recommendation pipeline.

Pros
  • + Self-hosted = free unlimited executions
  • + Code node allows full JavaScript logic for genre aggregation
  • + Open source with active community
Cons
  • - Server setup required for self-hosting
  • - More complex to maintain than a simple script

Best practices

  • Be explicit in your app about the fact that /recommendations is deprecated — users who research this topic will encounter broken tutorials and appreciate clarity
  • Prioritize genre-based searches over artist-based searches for discovery — artist searches tend to return the same well-known tracks the user already knows
  • Filter out tracks from your top tracks (medium_term) not just recently played — medium_term covers 6 months and prevents re-recommending familiar favorites
  • Mix popularity ranges in your selection algorithm: take 15 high-popularity tracks (score >65) and 15 mid-popularity tracks (40-65) for a balance of popular and niche discoveries
  • Add a year filter to genre searches (e.g., year:2020-2026) to prioritize contemporary releases over decades-old catalog tracks
  • Validate that all track URIs are in the correct format (spotify:track:{22-char-id}) before adding to playlists — malformed URIs cause silent failures

Ask AI to help

Copy one of these prompts to get a personalized, working implementation.

ChatGPT / Claude Prompt

I'm building a Spotify song recommendation system without using the deprecated /recommendations endpoint. My approach: I fetch the user's top artists via GET /v1/me/top/artists (short_term, limit 20), extract weighted genre scores, then run GET /v1/search?q=genre:{genre}&type=track queries for the top 5 genres. I'm getting mostly mainstream chart tracks even for niche genres. How can I improve the search query strategy to surface less-discovered tracks that still match the user's genre preferences? Can you suggest modifications to the search query format or filtering approach that would produce more diverse recommendations?

Lovable / V0 Prompt

Build a Spotify Discover tool that replaces the deprecated recommendations endpoint. The UI should have: 1) Spotify OAuth login with user-top-read and playlist-modify-private scopes, 2) After login, show the user's top 5 genres and top 3 artists from their short-term listening history as 'taste seeds', 3) A 'Generate Recommendations' button that searches Spotify using these seeds via GET /v1/search, filters out already-known tracks, and shows 20-30 results in a card grid with album art, track name, artist, and popularity score, 4) Checkboxes on each track for manual selection, 5) A 'Save to Playlist' button that creates a private 'Discover (Manual)' playlist with the selected tracks. Add a note explaining that this is a manual alternative to the deprecated Recommendations API.

Frequently asked questions

Why does GET /v1/recommendations return 403?

GET /v1/recommendations was permanently deprecated for Spotify apps created after November 27, 2024. If your app was created after that date, this endpoint will always return HTTP 403 and there is no workaround. Apps that existed before that date with Extended Quota Mode are grandfathered. The working alternative is to use GET /v1/me/top/artists to extract genre seeds, then run GET /v1/search queries with those genres to discover tracks.

What other Spotify endpoints are deprecated for new apps?

As of November 27, 2024, these endpoints return 403 for new apps: GET /v1/recommendations, GET /v1/audio-features/{id} (and batch), GET /v1/audio-analysis/{id}, GET /v1/artists/{id}/related-artists, GET /v1/browse/featured-playlists, GET /v1/browse/categories/{id}/playlists. The preview_url field is also no longer populated. Additionally, as of February 2026, Development Mode apps have restrictions on artist popularity scores, follower counts, top-tracks per artist, and new-releases browsing.

Is the Spotify API free?

Yes — the Spotify Web API is free to use. Development Mode limits you to 5 authorized users per Client ID (as of February 11, 2026) and requires the app owner to have Spotify Premium. Extended Quota Mode for production apps requires a legally registered organization with 250,000+ monthly active users as of May 2025.

What happens when I hit the rate limit?

You receive HTTP 429 with a Retry-After header in seconds. The search-based recommendation approach uses 10-20 search queries per run, which can hit the rate limit if run too quickly. Add 333ms delays between search requests (3 per second) and honor the Retry-After header when 429 is received.

Can I use audio features (tempo, energy, danceability) to filter recommendations?

No — GET /v1/audio-features is deprecated for apps created after November 27, 2024 and returns 403. You cannot access tempo, energy, danceability, valence, or any other audio analysis properties. The primary filtering signals available to new apps are: genre (from artist data), popularity score (proxy for mainstream-ness), and release year (from album data).

Can RapidDev help build a custom Spotify recommendation engine?

Yes — RapidDev has built 600+ integrations and can design a production recommendation pipeline using the search-based approach that works for all Spotify apps. We can also integrate third-party audio analysis APIs (like Tunebat or AcousticBrainz) as replacements for the deprecated audio features. Reach out at rapidevelopers.com for a free consultation.

Are there third-party alternatives for audio features data since Spotify deprecated it?

Yes — several options exist: Tunebat.com offers a paid API with BPM, key, and energy data for Spotify tracks. AcousticBrainz (open-source, though the main service is shutting down) provided similar data. Some developers use the Essentia library locally to analyze audio files. None are as convenient as Spotify's native audio features API was, but they fill the gap for apps that need acoustic property data.

RapidDev

Need this automated?

Our team has built 600+ apps with API automations. We can build this for you.

Book a free consultation

Skip the coding — we'll build it for you

Our experts have built 600+ API automations. From prototype to production in days, not weeks.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.