Skip to main content

How to Migrate Playlists Between Streaming Services Using a REST API

Published on July 19, 2026

How to Migrate Playlists Between Streaming Services Using a REST API

Users switch streaming services more often than most developers expect. A listener might move from Spotify to Apple Music for better audio quality, try YouTube Music because it comes with their phone plan, or add Tidal for exclusive content. Their playlists, sometimes curated over years, are the hardest thing to leave behind.

Playlist migration features drive real user acquisition for music apps. If your app can move a user's playlists from their old service to their new one, you solve a problem that keeps millions of people locked into platforms they would rather leave.

This post walks through how to build playlist migration using a REST API: the workflow, the code, and the edge cases you need to handle for production-quality results.

Why Playlist Migration Matters for Music App Developers

Playlist migration is not just a nice-to-have feature. It is a proven growth driver for music applications.

Users accumulate hundreds (sometimes thousands) of tracks in playlists across their streaming history. The cost of rebuilding those playlists manually is high enough that many users stay on a service they no longer prefer. An app that eliminates that switching cost captures users at the exact moment they are most motivated to try something new.

Here is why playlist migration matters from a product perspective:

  • User acquisition: Offering playlist import from a competing service gives users a reason to try your platform with their existing music library intact.
  • Cross-platform features: Music apps that work across services (social listening, collaborative playlists, analytics) need to read playlists from one service and write them to another.
  • Data portability: Regulatory pressure (like the EU's Digital Markets Act) is pushing platforms toward interoperability. Building migration capabilities now puts you ahead of requirements.
  • Retention: Users who successfully migrate their playlists are significantly more likely to stay active on the new platform.

The technical challenge is real, though. Each streaming service has its own API for reading playlists, its own API for creating them, and its own set of limitations. Building this from scratch for even two services takes weeks. Supporting 12 services takes months.

The Playlist Migration Workflow: Read, Map, Create

Every playlist migration follows the same three-step pattern, regardless of the source and destination services.

Step 1: Read the source playlist

Fetch the playlist metadata (name, description, cover image) and its full track list from the source service. This requires authenticated access to the user's account on the source platform.

Step 2: Map tracks to the destination service

For each track in the source playlist, find the corresponding track on the destination service. This is the hardest step. Track IDs are platform-specific, so you need to match by ISRC, title/artist metadata, or a combination.

Step 3: Create the playlist on the destination service

Create a new playlist on the destination account and add all matched tracks. Handle failures gracefully: some tracks will not have matches, and the user needs to know which ones were skipped.

StepWithout MusicAPIWith MusicAPI
Read source playlistService-specific OAuth + endpointOne auth flow, one endpoint
Map tracksCustom matching logic per service pairNormalized metadata with ISRCs included
Create destination playlistService-specific playlist creation APIOne create endpoint per service
Handle auth tokensPer-service OAuth refresh, token storageMusicAPI handles token refresh
Services supportedEach new pair = new integration12+ services, same code

Handling Track Matching Across Platforms

Track matching is the step where most playlist migration tools fail or produce poor results. The same song has different IDs on every platform. Your matching strategy determines whether users get a 70% match rate or a 95% match rate.

The matching hierarchy

Use this priority order for the best results:

  1. ISRC match (highest confidence): The International Standard Recording Code is a 12-character identifier assigned to recordings. When both the source and destination track have the same ISRC, you have a confirmed match. Most major label tracks have ISRCs.

  2. Exact title + artist match: Normalize both strings (lowercase, strip special characters) and compare. This catches most tracks where ISRCs are missing.

  3. Fuzzy title + artist + duration match: For tracks where exact matching fails (due to character encoding differences, "feat." vs "ft.", or slightly different titles), use edit distance with a duration check (within 3 seconds) as a tiebreaker.

  4. Search-based matching: As a last resort, search the destination service for the track title and artist, then pick the best result from the search response.

async function matchTrack(sourceTrack, destinationService, userToken) {
  // Try ISRC match first (fastest, most reliable)
  if (sourceTrack.isrc) {
    const isrcResult = await searchByIsrc(sourceTrack.isrc, destinationService, userToken);
    if (isrcResult) return { match: isrcResult, method: 'isrc', confidence: 0.95 };
  }

  // Fall back to metadata search
  const query = `${sourceTrack.title} ${sourceTrack.artist}`;
  const searchResults = await searchTracks(query, destinationService, userToken);

  // Score each result
  const scored = searchResults.map(candidate => ({
    track: candidate,
    score: calculateMatchScore(sourceTrack, candidate)
  }));

  const best = scored.sort((a, b) => b.score - a.score)[0];

  if (best && best.score > 0.7) {
    return { match: best.track, method: 'metadata', confidence: best.score };
  }

  return null; // No match found
}

MusicAPI handles OAuth token refresh, playlist creation, and track matching across 12 services so you do not have to build and maintain per-service integration code. See how the unified auth flow works.

Code Example: Migrating a Spotify Playlist to Apple Music via MusicAPI

Here is a complete migration flow using MusicAPI. The same code structure works for any source/destination pair.

// Prerequisites:
// 1. User authenticated on both services via MusicAPI OAuth
//    See: https://musicapi.com/docs/user-authentication/getting-started
// 2. MusicAPI API token ready

const MUSICAPI_BASE = 'https://api.musicapi.com/v1';

async function migratePlaylist(userToken, playlistId, sourceService, destService) {
  // Step 1: Fetch the source playlist and its tracks
  const playlistInfo = await fetch(
    `${MUSICAPI_BASE}/playlists/${playlistId}`,
    {
      headers: {
        'Authorization': `Bearer ${userToken}`,
        'X-Music-Service': sourceService
      }
    }
  ).then(r => r.json());

  const sourceTracks = await fetch(
    `${MUSICAPI_BASE}/playlists/${playlistId}/tracks`,
    {
      headers: {
        'Authorization': `Bearer ${userToken}`,
        'X-Music-Service': sourceService
      }
    }
  ).then(r => r.json());

  console.log(`Source playlist: "${playlistInfo.name}" (${sourceTracks.tracks.length} tracks)`);

  // Step 2: Match each track on the destination service
  const matchResults = [];

  for (const track of sourceTracks.tracks) {
    const match = await matchTrack(track, destService, userToken);
    matchResults.push({
      source: track,
      destination: match ? match.match : null,
      method: match ? match.method : null,
      confidence: match ? match.confidence : 0
    });
  }

  const matched = matchResults.filter(r => r.destination);
  const unmatched = matchResults.filter(r => !r.destination);

  console.log(`Matched: ${matched.length}/${sourceTracks.tracks.length}`);
  console.log(`Unmatched: ${unmatched.length} tracks`);

  // Step 3: Create the playlist on the destination service
  const newPlaylist = await fetch(
    `${MUSICAPI_BASE}/playlists`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${userToken}`,
        'X-Music-Service': destService,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        name: playlistInfo.name,
        description: `Migrated from ${sourceService}`,
        trackIds: matched.map(r => r.destination.serviceId)
      })
    }
  ).then(r => r.json());

  return {
    playlistId: newPlaylist.id,
    totalTracks: sourceTracks.tracks.length,
    matchedTracks: matched.length,
    unmatchedTracks: unmatched.map(r => ({
      title: r.source.title,
      artist: r.source.artist
    }))
  };
}

// Usage: Migrate a Spotify playlist to Apple Music
const result = await migratePlaylist(
  userToken,
  'spotify-playlist-id',
  'spotify',
  'apple_music'
);

console.log(result);
// {
//   playlistId: 'new-apple-music-playlist-id',
//   totalTracks: 47,
//   matchedTracks: 44,
//   unmatchedTracks: [
//     { title: 'Rare Indie Track', artist: 'Small Artist' },
//     { title: 'Regional Release', artist: 'Local Band' },
//     { title: 'Platform Exclusive', artist: 'Signed Artist' }
//   ]
// }

The key advantage: this code works for any service pair. Change spotify to tidal and apple_music to youtube_music, and the migration runs the same way. MusicAPI's normalized endpoints handle the per-service differences.

Edge Cases: Missing Tracks, Duplicates, and Playlist Limits

Production-quality playlist migration needs to handle several edge cases that do not show up in happy-path testing.

Missing tracks

Some tracks will not have matches on the destination service. Common reasons:

  • Platform exclusives: Some tracks are only available on one service
  • Regional licensing: A track available in one country may not exist in another country's catalog
  • Independent releases: Smaller artists may not distribute to all platforms
  • Removed content: Tracks get delisted or removed from catalogs

Always return the list of unmatched tracks to the user. Give them the option to search manually for alternatives.

Duplicate tracks

The same track can appear multiple times in a playlist (intentionally). Your migration should preserve duplicate entries. Do not deduplicate playlist contents unless the user explicitly asks.

Also watch for false duplicates in matching: different versions of the same song (live, acoustic, remastered, radio edit) may match to the same destination track. Check duration differences to catch this.

Playlist limits

Each platform has different limits on playlist size:

PlatformMax Tracks per PlaylistMax Playlists
Spotify10,00010,000
Apple Music25,000No published limit
YouTube Music5,000No published limit
Tidal10,000No published limit
Deezer2,0002,000
Amazon Music500No published limit

If the source playlist exceeds the destination's limit, split it into multiple playlists automatically. Name them with a suffix (e.g., "My Playlist (Part 1)", "My Playlist (Part 2)").

Rate limiting

Migrating large playlists means many API calls in sequence. Each track match may require a search request. MusicAPI's centralized rate limiting handles per-service throttling for you. If you are building directly against platform APIs, you need to implement backoff and retry logic for each service independently.

Character encoding

Playlist names and track metadata can contain emoji, non-Latin characters, and special symbols. Ensure your migration preserves these correctly. Test with playlists that have Japanese, Korean, Arabic, and emoji-heavy names.

Building Playlist Migration into a Production App

Here is a checklist for shipping playlist migration as a production feature.

Architecture decisions

  • Async processing: Large playlist migrations (1000+ tracks) should run asynchronously. Show a progress indicator and notify the user when complete.
  • Partial success handling: Always allow partial migrations. A playlist with 95% matched tracks is better than a failed migration.
  • Retry logic: Network errors and rate limits will cause individual track matches to fail. Queue failed matches for retry before reporting them as unmatched.
  • Progress persistence: Store migration state so users can resume if they close the app mid-migration. Save matched tracks incrementally, not all at once at the end.

User experience patterns

Define the listener-facing screens alongside the transfer implementation with this specification for music library import UX.

  • Show a preview of matched and unmatched tracks before creating the playlist
  • Let users exclude tracks they do not want to migrate
  • Offer suggestions for unmatched tracks (e.g., "Did you mean this version?")
  • Save the migration mapping so users can re-sync later when new content becomes available

Monitoring

  • Track match rate per service pair (Spotify to Apple Music, Tidal to YouTube Music, etc.)
  • Monitor unmatched track patterns to improve your matching algorithm
  • Alert on sudden drops in match rate, which may indicate API changes

FAQ

How do I transfer playlists between streaming services programmatically?

Use a playlist migration workflow: read the source playlist and its tracks via API, match each track on the destination service using ISRCs or metadata, then create a new playlist with the matched tracks. MusicAPI provides normalized endpoints for all three steps across 12+ services.

What is the best way to match tracks across music platforms?

Use ISRC matching as your primary method (95%+ accuracy for major label content). Fall back to exact title + artist matching, then fuzzy matching with duration validation. Search-based matching is a last resort. Combining these methods typically yields a 90-95% match rate across major platforms.

Can I migrate playlists from any service to any other service?

With MusicAPI, yes. The unified API supports 12+ services including Spotify, Apple Music, YouTube Music, Tidal, Deezer, Amazon Music, and more. The same code handles any source/destination pair. You can also migrate playlists from Spotify to any other supported service.

What percentage of tracks typically match during migration?

Match rates depend on the service pair and playlist content. Major label content matches at 90-95% between major platforms. Playlists heavy on independent or regional music will have lower match rates (70-85%). Platform exclusives and delisted tracks account for most unmatched content.

How long does a playlist migration take?

For a 50-track playlist, migration typically completes in under 30 seconds. Larger playlists (500+ tracks) may take a few minutes due to per-track matching. Async processing with progress updates is recommended for playlists over 100 tracks. MusicAPI's rate limiting is handled server-side, so you do not need to implement throttling.

Do I need OAuth access to both the source and destination accounts?

Yes. Reading the source playlist requires authenticated access to the user's source account. Creating the playlist on the destination requires authenticated access there too. MusicAPI's unified OAuth flow lets users authenticate both accounts through a single integration.

What happens to tracks that cannot be matched?

Return them to the user in a clear list with title and artist information. Best practice is to show unmatched tracks before creating the playlist, letting users decide whether to proceed. Some apps offer manual search for unmatched tracks, letting the user pick an alternative version.


Ready to skip months of OAuth and SDK work? Start your free MusicAPI trial and connect 10+ streaming services with one unified API.