Home Pricing Docs Compare SDK Blog Changelog Status Get API Key →
Music API · Migration · Tutorial

The Spotify /recommendations Replacement

June 2026 · 9 min read · FreqBlog

When people talk about the Spotify Web API deprecations, they usually mean audio_features and audio_analysis — the per-track numbers. But the same November 2024 cull also took GET /v1/recommendations and GET /v1/artists/{id}/related-artists, and those two have arguably left the bigger hole. Eighteen months on, “Spotify recommendations endpoint deprecated” is still one of the most-asked questions on Stack Overflow and the developer forum, with no official replacement — for the seed-based recommender that powered a huge number of “discover” features and “fans also like” pages.

This post is a practical walkthrough of building both back, in about 40 lines of Python, against the FreqBlog Music API: GET /recommendations (seed tracks → similar tracks) and GET /related-artists (an artist → nearby artists). No Spotify account, no deprecated endpoint, no audio files — you pass catalog track ids and artist names.

What was removed, exactly. Spotify deprecated /v1/recommendations and /v1/artists/{id}/related-artists for apps created after 27 November 2024, and the broader audio-intelligence set (audio_features, audio_analysis, audio previews) with them. If you also need the raw per-track numbers, start with Spotify Audio Features Is Dead. Here’s What to Use Instead in 2026 for the host swap, then come back here for the recommender layer.

Why there’s no drop-in clone — and what to build instead

Spotify’s recommender was a black box backed by its full collaborative-filtering graph — billions of listening sessions. Nobody outside Spotify has that signal, so any honest replacement is built differently. The approach that actually ships is content-based similarity: describe each track by its audio features (tempo, key, energy, danceability, valence, acousticness…), and find neighbours in that feature space. It doesn’t know that two tracks share an audience; it knows they sound alike. For “here’s a track, give me more like it” that turns out to be most of what you want — and it has a property the collaborative graph never did: it works on brand-new and long-tail tracks with zero listening history.

FreqBlog runs that similarity index over its own pre-analysed catalogue and exposes it as two Spotify-shaped endpoints. The one twist worth knowing up front: pure audio-feature similarity is genre-blind — an EDM track and a hard-rock track can sit close in feature space if their tempo and energy line up. So the ranking is genre-aware: it uses FreqBlog’s own catalogue genre data (around 94% coverage) to lift same-genre matches and push down a coincidentally-close cross-genre track. That’s the bit that makes the results feel right rather than merely numerically near.

/recommendations — seed tracks in, similar tracks out

GET /recommendations is the direct stand-in for /v1/recommendations. Give it 1–5 catalog seed tracks; it blends them into a single point in audio-feature space (a centroid) and returns the nearest catalogue tracks, re-ranked by genre affinity. Like Spotify’s endpoint, you pass seed tracks — the difference is the id format: FreqBlog itunes_track_id values (e.g. apple_ad1829eeccb70f9a), which you get from /search or any /lookup response, not Spotify URIs.

import requests

API = "https://api.freqblog.com"
HEADERS = {"X-Api-Key": "sk_live_your_key_here"}

def recommend(seed_ids, limit=10, exclude_seed_artists=False):
    r = requests.get(f"{API}/recommendations",
                     params={"seed_tracks": ",".join(seed_ids),
                             "limit": limit,
                             "exclude_seed_artists": str(exclude_seed_artists).lower()},
                     headers=HEADERS, timeout=30)
    r.raise_for_status()
    return r.json()

# Seed: "Jump" by Van Halen (hard rock). Catalog id from /search or /lookup.
data = recommend(["apple_ad1829eeccb70f9a"], limit=8)

for s in data["seeds"]:
    print(f"seed {s['id']}  found={s['found']}")
for rec in data["tracks"]:
    t = rec["track"]
    print(f"  {rec['score']:.3f}  {t['track_name']} - {t['artist_name']}  [{t.get('genre')}]")

The response echoes your seeds with a found flag (so you can tell which ids resolved), a count, and a tracks list of {track, score} objects. For the Van Halen seed, the picks come back all hard rock — Alice Cooper, Guns N’ Roses, Aerosmith, David Lee Roth — scoring around 0.93–0.95. Each track is a full stub: itunes_track_id, track_name, artist_name, album_name, genre, release_date, isrc/mbid when known. Feed any of those ids straight into /lookup for the full audio features.

One thing that trips people up: the list is NOT strictly score-descending. score is the raw audio-feature cosine similarity (0–1); the genre lift affects ordering, not the printed number. So a same-genre track with a slightly lower cosine can — correctly — rank above a cross-genre track with a higher cosine. If you re-sort the list purely by score on the client you’ll throw away the genre-aware ranking. Trust the order it comes back in.

Blending multiple seeds

Multiple seeds work exactly like Spotify’s: pass up to five and you get recommendations for the blend, not for each one in turn. This is how you build a “based on your recent likes” row — take the last few tracks a user engaged with, blend them, and the centroid lands in the middle of their taste:

recent_likes = [
    "apple_ad1829eeccb70f9a",   # hard rock
    "dz_92720102",              # arena rock (AC/DC)
    "1443810719",               # classic rock
]
data = recommend(recent_likes, limit=20, exclude_seed_artists=True)
print(f"{data['count']} recommendations for the blend")

exclude_seed_artists=true drops anything by the seed artists themselves — the difference between “more of the same artist” and genuine discovery. Leave it off for a “more like this” box; turn it on for a “you might also like” row.

/related-artists — deriving an artist graph without one

GET /related-artists stands in for /v1/artists/{id}/related-artists — the endpoint behind every “fans also like” / “similar artists” panel. Spotify had an explicit artist-relationship graph to read from. FreqBlog has no such graph, so it derives one on the fly: it builds the seed artist’s audio-feature centroid from their catalogue tracks, finds the nearest tracks across the catalogue, and aggregates those by artist.

def related(artist, limit=10):
    r = requests.get(f"{API}/related-artists",
                     params={"artist": artist, "limit": limit},
                     headers=HEADERS, timeout=30)
    r.raise_for_status()
    return r.json()

data = related("Van Halen", limit=8)
for a in data["related"]:
    print(f"  {a['score']:.2f}  {a['artist_name']}  "
          f"({a['match_count']} matching tracks, sample {a['sample_track_id']})")

For Van Halen this leads with Danger Danger, AC/DC and Def Leppard — the right neighbourhood. Each result carries artist_name, a score, a match_count (how many of that artist’s tracks landed in the seed’s neighbourhood), and a sample_track_id — a representative catalog id you can hand straight to /lookup or to /next-track.

Two design choices keep the list sensible, and they’re worth understanding because they explain results you might otherwise find surprising:

Putting it together: a “discover” feed in ~40 lines

The two endpoints compose into the classic discovery surface — recommended tracks plus the artists behind them — with no extra plumbing:

def discover(seed_track_ids, seed_artist):
    recs = recommend(seed_track_ids, limit=10, exclude_seed_artists=True)
    arts = related(seed_artist, limit=6)

    print("Because you like that sound:")
    for rec in recs["tracks"]:
        t = rec["track"]
        print(f"  {t['track_name']} - {t['artist_name']}")

    print("\nFans also like:")
    for a in arts["related"]:
        print(f"  {a['artist_name']}")

discover(["apple_ad1829eeccb70f9a"], "Van Halen")

That’s the whole thing. The seed ids come from one /search or /lookup per track name; everything else is two HTTP calls. The part that used to require Spotify’s recommendation graph is now a content-based query you own.

What each call costs

Both endpoints run through the same monthly-quota model as /lookup, and you’re only charged on a served 200 — a 404 (no seed in the catalog) or 422 (no/invalid seed supplied) costs nothing.

EndpointQuotaWhy
GET /recommendations2 requestsBlends up to 5 seeds into a centroid and scores the catalogue, then genre-re-ranks the pool.
GET /related-artists2 requestsBuilds the seed artist’s centroid, scores the catalogue, and aggregates by artist with the per-artist cap + genre lift.

So a discover feed — one /recommendations (2) plus one /related-artists (2) — is 4 quota units a render. The free tier’s 1,000 requests/month is plenty to prototype with before you need a paid plan.

How it differs from Spotify’s endpoint — honestly

Rebuild your recommender — free tier, no card

Get an API key in seconds; the first 1,000 requests per month are free. Then point the snippets above at your seed tracks.

Get API Key →

Further reading