Skip to main content

Building AI-Powered Playlists: How to Combine Music APIs with LLMs

Published on August 8, 2026

Building AI-Powered Playlists: How to Combine Music APIs with LLMs

Building AI-Powered Playlists: How to Combine Music APIs with LLMs

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.

Why AI-Powered Playlists Are the Next Big Feature for Music Apps

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.

Beyond Collaborative Filtering: What LLMs Bring to Music Curation

Collaborative filtering works well for "more like this" recommendations. It fails at understanding context, mood, and complex constraints. Consider these requests:

  • "Songs that would play in a Wes Anderson movie"
  • "Upbeat tracks from the 2010s that are not overplayed at gyms"
  • "A dinner party mix that transitions from bossa nova to soul to modern R&B"

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 Cases: Mood-Based Playlists, Event Soundtracks, Workout Mixes

Use caseUser inputLLM 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

Feature Table: Traditional Recommendation vs. LLM-Powered Generation

CapabilityTraditional (collaborative filtering)LLM-powered
Input typeUser history, clicks, skipsNatural language description
Context understandingLimited (genre, tempo, popularity)Deep (mood, setting, cultural context)
Novel requestsCannot handleCore strength
Cold start problemSevere (needs user history)Minimal (works from description alone)
PersonalizationStrong with enough dataCan combine with user data for better results
ConsistencyHigh (same algo, same results)Variable (prompt engineering required)
Track existence verificationBuilt-in (queries catalog)Requires separate validation step

Architecture: Connecting a Music API to an LLM

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.

The Data Pipeline: User Library, Listening History, and Track Metadata

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.

Prompt Engineering for Playlist Generation

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`;
}

Code Example: Sending User Favorites to an LLM and Getting a Playlist Back

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;
}

Fetching and Creating Playlists Across Services

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.

Reading User Libraries from Multiple Platforms via MusicAPI

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.

Searching for Tracks Returned by the LLM

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];
}

Code Example: Creating an AI-Generated Playlist on Spotify via MusicAPI

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.

Handling Edge Cases and Improving Quality

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.

When the LLM Suggests Tracks That Do Not Exist

Roughly 10-20% of LLM-suggested tracks will not resolve against a streaming catalog. Handle this by:

  1. Over-generating: Ask for 25 tracks when you need 20
  2. Retry with feedback: If too many fail, send the failures back to the LLM with "these tracks were not found, suggest alternatives"
  3. Logging: Track which suggestions fail to improve your prompts over time
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);
}

Fuzzy Matching Track Names and Artists Across Services

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;
}

Rate Limits and Caching for Production Workloads

Each track resolution requires a search API call. For 25 tracks, that is 25 requests. At scale, this hits rate limits. Mitigate by:

  • Batching: Run searches in parallel with concurrency limits (5 at a time)
  • Caching: Cache search results by query string for 24 hours
  • Pre-resolution: Build a local mapping of common artist/title combinations

FAQ

Can I use any LLM for playlist generation?

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.

How accurate are LLM-generated track 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.

Does MusicAPI support creating playlists on all streaming services?

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.

How do I handle users connected to multiple streaming services?

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.

What about music licensing and copyright?

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.

How do I personalize playlists without user listening history?

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.

Can I build this as a real-time feature or is it too slow?

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.