Published on May 28, 2026

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.
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:
| Challenge | Spotify | Apple Music | YouTube Music | Tidal | Deezer |
|---|---|---|---|---|---|
| Auth method | OAuth 2.0 + PKCE | Developer + Music User Token | Google OAuth 2.0 | OAuth 2.0 | OAuth 2.0 |
| Track identifier | Spotify URI | Catalog ID | Video ID | Track ID | Track ID |
| Playlist pagination | Cursor-based | Offset-based | Continuation token | Offset-based | Offset-based |
| Rate limit style | Retry-after header | Token bucket | Daily quota | Retry-after header | Rate limit header |
| Liked songs endpoint | /me/tracks | /me/library | getLikedMusic | /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.
The read side of a sync operation involves two steps: authenticating the user with their streaming service, then fetching their playlists and favorite tracks.
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.
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.
The write side takes the normalized track data and creates playlists on the destination service. This is where track matching across catalogs becomes critical.
// 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.
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:
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.
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 │ └──────────────┘ └─────────────┘
└─────────────┘
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.
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.
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.
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.
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).
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.
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.
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.
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.