Published on August 8, 2026

Traditional music recommendations rely on collaborative filtering: "users who liked X also liked Y." LLMs bring a different capability. They understand natural language requests like "make me a 90-minute playlist for a rainy Sunday afternoon with a mix of jazz and lo-fi hip hop, nothing too upbeat." That is a description no collaborative filter can parse, but an LLM handles it naturally.
This guide walks through the full architecture: fetching user music data, passing it to an LLM, resolving the generated track list against real streaming catalogs, and creating the playlist on the user's preferred service.
AI-powered playlists use large language models to generate track lists from natural language descriptions. Instead of selecting from pre-built categories like "focus" or "workout," users describe what they want in plain English. The LLM generates a track list, and a music API resolves those tracks against real streaming service catalogs and creates the playlist. This combination enables playlist generation that feels personal and contextual.
Collaborative filtering works well for "more like this" recommendations. It fails at understanding context, mood, and complex constraints. Consider these requests:
An LLM processes these as natural language and generates specific track suggestions. The model draws on its training data, which includes extensive music knowledge: genres, eras, artist relationships, mood associations, and cultural context.
| Use case | User input | LLM output |
|---|---|---|
| Mood playlist | "Melancholic but hopeful, like a sunrise after a storm" | 15 tracks matching that emotional arc |
| Event soundtrack | "Background music for a tech startup demo day" | Upbeat, modern, unobtrusive selections |
| Workout mix | "Heavy lifting session, 45 minutes, 140+ BPM, no pop" | BPM-matched tracks in the right duration |
| Discovery | "Artists similar to Khruangbin but more electronic" | Curated list of lesser-known artists |
| Nostalgia | "Songs from early 2000s road trips" | Era-specific tracks with strong cultural associations |
| Capability | Traditional (collaborative filtering) | LLM-powered |
|---|---|---|
| Input type | User history, clicks, skips | Natural language description |
| Context understanding | Limited (genre, tempo, popularity) | Deep (mood, setting, cultural context) |
| Novel requests | Cannot handle | Core strength |
| Cold start problem | Severe (needs user history) | Minimal (works from description alone) |
| Personalization | Strong with enough data | Can combine with user data for better results |
| Consistency | High (same algo, same results) | Variable (prompt engineering required) |
| Track existence verification | Built-in (queries catalog) | Requires separate validation step |
Building an AI playlist generator requires three components: a music data source for user context, an LLM for track generation, and a music API for catalog resolution and playlist creation. MusicAPI serves as both the data source and the creation layer, reading from and writing to 12+ streaming services through a single integration.
Better LLM output starts with better input. Feed the model the user's existing taste data:
// Fetch user's favorite tracks for LLM context
async function getUserMusicProfile(userToken, service) {
const favorites = await fetch(
'https://api.musicapi.com/favorites?service=' + service + '&limit=50',
{ headers: { 'Authorization': 'Bearer API_KEY', 'X-User-Token': userToken } }
);
const playlists = await fetch(
'https://api.musicapi.com/playlists?service=' + service + '&limit=20',
{ headers: { 'Authorization': 'Bearer API_KEY', 'X-User-Token': userToken } }
);
const favData = await favorites.json();
const playlistData = await playlists.json();
return {
topArtists: extractUniqueArtists(favData.items),
topGenres: extractGenres(favData.items),
recentTracks: favData.items.slice(0, 20).map(t => `${t.artist} - ${t.title}`),
playlistNames: playlistData.items.map(p => p.name),
};
}
The favorite tracks endpoint returns normalized data, so this code works for any connected service.
The prompt should include the user's taste profile, the request, and output format constraints:
function buildPlaylistPrompt(userProfile, request) {
return `You are a music curator. Generate a playlist based on the user's request.
User's music taste:
- Top artists: ${userProfile.topArtists.join(', ')}
- Genres they enjoy: ${userProfile.topGenres.join(', ')}
- Recent listening: ${userProfile.recentTracks.join('; ')}
Request: "${request}"
Return a JSON array of exactly 20 tracks. Each track should be a real, existing song.
Format: [{"title": "Track Name", "artist": "Artist Name"}]
Rules:
- Only suggest real songs by real artists
- Match the mood and context of the request
- Mix well-known tracks with deeper cuts
- Consider the user's taste but prioritize the request
- No tracks already in the user's recent listening`;
}
async function generateAIPlaylist(userToken, service, userRequest) {
// Step 1: Get user's music profile
const profile = await getUserMusicProfile(userToken, service);
// Step 2: Build the prompt and call the LLM
const prompt = buildPlaylistPrompt(profile, userRequest);
const llmResponse = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': LLM_API_KEY,
'anthropic-version': '2023-06-01'
},
body: JSON.stringify({
model: 'claude-sonnet-5',
max_tokens: 1024,
messages: [{ role: 'user', content: prompt }]
})
});
const llmData = await llmResponse.json();
const suggestedTracks = JSON.parse(llmData.content[0].text);
// Step 3: Resolve each track against the streaming catalog
const resolvedTracks = await resolveTracksViaMusicAPI(suggestedTracks, userToken, service);
// Step 4: Create the playlist
const playlist = await createPlaylist(resolvedTracks, userToken, service, userRequest);
return playlist;
}
The power of combining an LLM with a unified music API is that the same AI playlist feature works across every streaming service the user has connected. The code for reading a Spotify user's library is identical to reading an Apple Music user's library. Creating a playlist on Tidal uses the same API call as creating one on Deezer.
For users connected to multiple services, you can aggregate their taste data:
async function getMultiServiceProfile(userToken, services) {
const profiles = await Promise.all(
services.map(service => getUserMusicProfile(userToken, service))
);
return {
topArtists: deduplicateArtists(profiles.flatMap(p => p.topArtists)),
topGenres: deduplicateGenres(profiles.flatMap(p => p.topGenres)),
recentTracks: profiles.flatMap(p => p.recentTracks).slice(0, 30),
};
}
Check supported features for which data types each service exposes.
LLMs suggest tracks by name and artist, but you need service-specific track IDs to create a playlist. Search the catalog for each suggestion:
async function resolveTracksViaMusicAPI(suggestedTracks, userToken, service) {
const resolved = [];
for (const track of suggestedTracks) {
const searchQuery = `${track.title} ${track.artist}`;
const response = await fetch(
`https://api.musicapi.com/search?query=${encodeURIComponent(searchQuery)}&type=track&service=${service}&limit=3`,
{ headers: { 'Authorization': 'Bearer API_KEY', 'X-User-Token': userToken } }
);
const results = await response.json();
const match = findBestMatch(results.data, track);
if (match) {
resolved.push(match);
}
}
return resolved;
}
function findBestMatch(results, suggested) {
if (!results || results.length === 0) return null;
return results.find(r =>
r.title.toLowerCase().includes(suggested.title.toLowerCase()) &&
r.artist.toLowerCase().includes(suggested.artist.toLowerCase())
) || results[0];
}
Once you have resolved track IDs, create the playlist:
async function createPlaylist(tracks, userToken, service, description) {
// Create the playlist
const createResponse = await fetch(
'https://api.musicapi.com/playlists',
{
method: 'POST',
headers: {
'Authorization': 'Bearer API_KEY',
'X-User-Token': userToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
service: service,
name: `AI Mix: ${description.slice(0, 50)}`,
description: `Generated from: "${description}"`,
tracks: tracks.map(t => t.service_id)
})
}
);
return createResponse.json();
}
MusicAPI handles the OAuth token refresh and service-specific playlist creation format for you. The same code creates playlists on Spotify, Apple Music, YouTube Music, or any other supported service.
Ready to skip months of OAuth and SDK work? Start your free MusicAPI trial and connect 10+ streaming services with one unified API.
LLMs hallucinate. They will suggest tracks that do not exist, misspell artist names, and occasionally invent entirely fictional songs. A production AI playlist feature needs validation, fuzzy matching, and graceful fallbacks.
Roughly 10-20% of LLM-suggested tracks will not resolve against a streaming catalog. Handle this by:
async function generateWithRetry(userToken, service, request, targetCount = 20) {
let resolved = [];
let attempts = 0;
const maxAttempts = 3;
while (resolved.length < targetCount && attempts < maxAttempts) {
const needed = targetCount - resolved.length + 5;
const suggestions = await callLLM(request, needed, resolved);
const newResolved = await resolveTracksViaMusicAPI(suggestions, userToken, service);
resolved.push(...newResolved);
attempts++;
}
return resolved.slice(0, targetCount);
}
Exact string matching fails often. "The Beatles" vs "Beatles," "Guns N' Roses" vs "Guns N Roses," "Jay-Z" vs "JAY-Z." Use fuzzy matching:
function fuzzyScore(a, b) {
const normalize = s => s.toLowerCase()
.replace(/['']/g, '')
.replace(/[^a-z0-9\s]/g, '')
.replace(/\s+/g, ' ')
.trim();
const na = normalize(a);
const nb = normalize(b);
if (na === nb) return 1.0;
if (na.includes(nb) || nb.includes(na)) return 0.8;
const words_a = new Set(na.split(' '));
const words_b = new Set(nb.split(' '));
const intersection = [...words_a].filter(w => words_b.has(w));
return intersection.length / Math.max(words_a.size, words_b.size);
}
function findBestMatch(results, suggested) {
if (!results || results.length === 0) return null;
const scored = results.map(r => ({
track: r,
score: fuzzyScore(r.title, suggested.title) * 0.6 +
fuzzyScore(r.artist, suggested.artist) * 0.4
}));
const best = scored.sort((a, b) => b.score - a.score)[0];
return best.score > 0.5 ? best.track : null;
}
Each track resolution requires a search API call. For 25 tracks, that is 25 requests. At scale, this hits rate limits. Mitigate by:
Yes. The architecture works with any LLM that accepts text prompts and returns structured output. Claude, GPT, Gemini, or open-source models all work. The music API layer is LLM-agnostic; it handles catalog resolution and playlist creation regardless of which model generates the suggestions.
Expect 80-90% of suggestions to resolve against real streaming catalogs. The remaining 10-20% are either hallucinated tracks, obscure releases not available on the target service, or misspelled names that fuzzy matching catches. Over-generating by 25-30% and using retry logic produces reliable 20-track playlists.
MusicAPI supports playlist creation on Spotify, Apple Music, YouTube Music, Deezer, Tidal, and other services. Check the supported features page for the current list. The API call is the same regardless of target service.
Aggregate taste data from all connected services to give the LLM a richer profile. When creating the playlist, let the user choose their preferred service. The same resolved track list can create playlists on different services since MusicAPI's search works cross-platform.
AI playlist generation creates playlists of existing, licensed tracks on legitimate streaming platforms. You are not generating music; you are curating it. The tracks play through the user's existing streaming subscription. No additional licensing is required beyond standard API usage.
LLMs work from natural language alone. A first-time user can describe what they want ("jazz for a coffee shop on a rainy day") and get a quality playlist without any listening history. Add personalization progressively as the user engages with your app and builds a favorites library.
LLM inference takes 2-5 seconds. Resolving 20-25 tracks against a catalog takes another 3-8 seconds with parallel requests. Total end-to-end time is typically under 15 seconds, fast enough for a "generating your playlist" loading state. Cache popular prompt patterns to cut repeat requests to under 2 seconds.