Skip to main content

Building a Cross-Platform Music Library Sync Feature with a Single API

Published on May 28, 2026

Building a Cross-Platform Music Library Sync Feature with a Single API

Cross-platform music sync lets users move playlists, favorites, and library data between streaming services like Spotify, Apple Music, YouTube Music, Tidal, and Deezer through one integration. Instead of rebuilding OAuth flows and data parsers for each platform, a single API handles authentication, data normalization, and write operations across all of them.

The Technical Challenge: Auth, Data Formats, and Rate Limits Across Services

Building music library sync from scratch means solving three hard problems at once: authentication, data normalization, and rate limiting. Each one multiplies in complexity with every service you add.

Authentication is the first wall. Spotify uses OAuth 2.0 with PKCE. Apple Music requires a developer token plus a user music token. YouTube Music piggybacks on Google's OAuth with specific scopes. Each service has different token lifetimes, refresh mechanisms, and revocation behaviors. Supporting five services means maintaining five separate auth implementations and handling token refresh edge cases for each one.

Data formats vary wildly. A track object from Spotify includes a uri field like spotify:track:6rqhFgbbKwnb9MLmUQDhG6. Apple Music uses a catalog ID like 1440783612. YouTube Music returns a videoId. Playlist structures differ too: some services nest tracks inside albums, others flatten everything. Field names, pagination styles, and even the concept of "liked songs" vs. "library tracks" differ between platforms.

Rate limits add another layer. Spotify enforces per-app and per-user rate limits with retry-after headers. Apple Music uses a token bucket approach. YouTube Music inherits Google's quota system, which counts API calls against a daily budget. A sync operation that reads 500 tracks and writes them to another service can burn through rate limits fast if you are not batching and throttling correctly.

Here is what that looks like in practice:

ChallengeSpotifyApple MusicYouTube MusicTidalDeezer
Auth methodOAuth 2.0 + PKCEDeveloper + Music User TokenGoogle OAuth 2.0OAuth 2.0OAuth 2.0
Track identifierSpotify URICatalog IDVideo IDTrack IDTrack ID
Playlist paginationCursor-basedOffset-basedContinuation tokenOffset-basedOffset-based
Rate limit styleRetry-after headerToken bucketDaily quotaRetry-after headerRate limit header
Liked songs endpoint/me/tracks/me/librarygetLikedMusic/users/{id}/favorites/user/me/tracks

Building and maintaining all of this yourself takes months of engineering time. And every time a service updates its API (Spotify alone ships breaking changes yearly), you are back to debugging.

Step-by-Step: Reading a User's Library from Service A

The read side of a sync operation involves two steps: authenticating the user with their streaming service, then fetching their playlists and favorite tracks.

Authenticating the User

With MusicAPI's authentication flow, you authenticate users across any supported service with the same code path. Here is how to initialize authentication and handle the callback:

// Step 1: Start the auth flow
const authResponse = await fetch('https://api.musicapi.com/auth/init', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_MUSICAPI_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    service: 'spotify',  // or 'apple', 'youtube', 'tidal', 'deezer'
    callbackUrl: 'https://yourapp.com/auth/callback'
  })
});

const { authUrl } = await authResponse.json();
// Redirect the user to authUrl to complete OAuth

After the user authorizes your app, MusicAPI sends them back to your callback URL with a connection token. That same token format works regardless of which service the user connected.

Fetching Playlists and Favorites

Once authenticated, reading the user's library is a single set of endpoints:

// Fetch all user playlists
const playlists = await fetch('https://api.musicapi.com/user/playlists', {
  headers: { 'Authorization': 'Bearer CONNECTION_TOKEN' }
});

const { data: userPlaylists } = await playlists.json();
// Returns normalized playlist objects with id, name, trackCount, and imageUrl
// regardless of whether the source is Spotify, Apple Music, or YouTube Music

// Fetch tracks from a specific playlist
const tracks = await fetch(`https://api.musicapi.com/playlist/${playlistId}/tracks`, {
  headers: { 'Authorization': 'Bearer CONNECTION_TOKEN' }
});

const { data: playlistTracks } = await tracks.json();
// Each track includes: name, artist, album, duration, isrc, and service-specific id

// Fetch user's favorite/liked tracks
const favorites = await fetch('https://api.musicapi.com/user/favorites', {
  headers: { 'Authorization': 'Bearer CONNECTION_TOKEN' }
});

The response shape stays the same across all services. A track from Spotify and a track from Apple Music both come back with name, artist, album, isrc, and duration fields. This is what makes the sync possible: you read normalized data from Service A and write it directly to Service B.

For full endpoint details, see the playlists endpoint documentation or try it live on the Spotify playlists page.

Step-by-Step: Writing to Service B

The write side takes the normalized track data and creates playlists on the destination service. This is where track matching across catalogs becomes critical.

Creating Playlists and Adding Tracks

// Step 1: Create a new playlist on the destination service
const newPlaylist = await fetch('https://api.musicapi.com/playlist/create', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer DESTINATION_CONNECTION_TOKEN',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'My Synced Playlist',
    description: 'Synced from Spotify via MusicAPI'
  })
});

const { data: createdPlaylist } = await newPlaylist.json();

// Step 2: Add tracks to the new playlist
const addTracks = await fetch(`https://api.musicapi.com/playlist/${createdPlaylist.id}/tracks`, {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer DESTINATION_CONNECTION_TOKEN',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    tracks: playlistTracks.map(track => ({
      isrc: track.isrc,      // International Standard Recording Code
      name: track.name,       // Fallback matching by name + artist
      artist: track.artist
    }))
  })
});

const { data: result } = await addTracks.json();
console.log(`Matched: ${result.matched}, Not found: ${result.notFound}`);

Try the write endpoints yourself on the Spotify playlist creation page or Apple Music favorites page.

Handling Track Matching Across Catalogs

Not every track exists on every service. A song on Spotify might not be in Apple Music's catalog, or it might exist under a different title or artist spelling. The matching process works in layers:

  1. ISRC matching (most reliable): The International Standard Recording Code is a 12-character identifier assigned to each recording. When both services have ISRC data, matching is near-perfect.
  2. Metadata matching (fallback): When ISRC is missing, matching falls back to title + artist + album comparison with fuzzy string matching.
  3. Not found handling: Some tracks simply are not available on the destination service. Your sync flow should log these and report them to the user.

MusicAPI handles this matching internally. When you send tracks with ISRC codes, the API resolves them on the destination service automatically. The response tells you exactly which tracks matched and which did not.

Putting It Together: A Complete Sync Flow with MusicAPI

Here is the full architecture for a cross-platform sync feature built on a single unified API:

┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│  Your App   │────>│   MusicAPI   │────>│  Service A   │
│             │     │              │     │  (Source)    │
│  Sync Flow: │     │  - Auth      │     └─────────────┘
│  1. Auth A  │     │  - Normalize │
│  2. Read A  │     │  - Match     │     ┌─────────────┐
│  3. Auth B  │     │  - Rate limit│────>│  Service B   │
│  4. Write B │     │  - Write     │     │  (Dest)      │
│  5. Report  │     └──────────────┘     └─────────────┘
└─────────────┘

Complete Sync Function

async function syncLibrary(sourceToken, destToken, options = {}) {
  const results = {
    playlists: { synced: 0, failed: 0 },
    tracks: { matched: 0, notFound: 0 },
    errors: []
  };

  try {
    // 1. Read playlists from source
    const playlistsRes = await fetch('https://api.musicapi.com/user/playlists', {
      headers: { 'Authorization': `Bearer ${sourceToken}` }
    });
    const { data: playlists } = await playlistsRes.json();

    // 2. For each playlist, read tracks and write to destination
    for (const playlist of playlists) {
      try {
        // Read tracks from source playlist
        const tracksRes = await fetch(
          `https://api.musicapi.com/playlist/${playlist.id}/tracks`,
          { headers: { 'Authorization': `Bearer ${sourceToken}` } }
        );
        const { data: tracks } = await tracksRes.json();

        // Create playlist on destination
        const createRes = await fetch('https://api.musicapi.com/playlist/create', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${destToken}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            name: playlist.name,
            description: playlist.description || ''
          })
        });
        const { data: newPlaylist } = await createRes.json();

        // Add tracks to destination playlist
        const addRes = await fetch(
          `https://api.musicapi.com/playlist/${newPlaylist.id}/tracks`,
          {
            method: 'POST',
            headers: {
              'Authorization': `Bearer ${destToken}`,
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              tracks: tracks.map(t => ({
                isrc: t.isrc,
                name: t.name,
                artist: t.artist
              }))
            })
          }
        );
        const { data: addResult } = await addRes.json();

        results.playlists.synced++;
        results.tracks.matched += addResult.matched;
        results.tracks.notFound += addResult.notFound;
      } catch (err) {
        results.playlists.failed++;
        results.errors.push(`Failed to sync "${playlist.name}": ${err.message}`);
      }
    }
  } catch (err) {
    results.errors.push(`Failed to read source library: ${err.message}`);
  }

  return results;
}

This sync function works for any combination of supported services: Spotify to Apple Music, YouTube Music to Tidal, Deezer to Spotify, or any other pairing. One auth flow, one set of read/write endpoints, and one rate limiting layer that handles per-service throttling for you.

MusicAPI handles the hard parts. OAuth and token refresh across 12+ services, response normalization, ISRC-based track matching, and rate limit management all happen behind a single REST API. Instead of months of integration work per service, you ship a sync feature in days. See the full list of supported features here.

FAQ

How accurate is cross-platform track matching?

ISRC-based matching (the primary method) achieves near-perfect accuracy for tracks that exist on both services. Most major-label releases share the same ISRC across platforms. Metadata fallback matching handles edge cases like re-releases or regional variants. Expect 90-98% match rates for typical user libraries, depending on the services involved and the genres in the library.

Can I sync liked/favorite tracks, not just playlists?

Yes. MusicAPI exposes a favorites endpoint that reads liked songs from any connected service. You can sync these to a new playlist on the destination service, or add them to the destination's favorites list directly if the service supports write access to liked tracks.

How do I handle services where a track is not available?

Your sync function should collect unmatched tracks and present them to the user. The API response includes both matched and notFound counts, plus details about which tracks failed to match. Common reasons include regional licensing restrictions, tracks removed by the artist, or catalog differences between services.

Does syncing work in both directions?

Yes. Since MusicAPI provides both read and write endpoints for all supported services, you can sync from any service to any other service. You can also build two-way sync by running the sync flow in both directions, though you will need merge logic to handle conflicts (playlists that exist on both sides with different track lists).

What happens if a user's token expires during a large sync?

MusicAPI handles token refresh automatically. If a service token expires mid-sync, the API refreshes it behind the scenes and retries the request. Your application code does not need to handle refresh logic. For very large libraries (10,000+ tracks), consider batching the sync into smaller operations and providing progress updates to the user.

How many services does MusicAPI support for sync?

MusicAPI currently supports 12+ streaming services, including Spotify, Apple Music, YouTube Music, Tidal, Deezer, Amazon Music, and more. Each service gets the same normalized endpoints for playlists, tracks, favorites, and user profiles. Check the supported services page for the current list.

Is there a rate limit on sync operations?

MusicAPI manages per-service rate limits internally, so you do not need to implement your own throttling. The API queues and retries requests as needed. For bulk operations, the API accepts batch requests that are more efficient than individual calls. Check the pricing page for throughput limits on each plan.

Start Building Your Sync Feature Today

Ready to skip months of OAuth and SDK work? Start your free MusicAPI trial and connect 10+ streaming services with one unified API. Get started with user authentication and have your first sync flow running in minutes, not months.