Published on August 8, 2026

Your users want one search bar. They type "Bohemian Rhapsody," and they expect results from every streaming service they use. Building that experience by integrating each service's search API individually means wrestling with a dozen different auth flows, response schemas, and rate limits. A unified music API collapses that complexity into a single request.
This guide walks through building cross-platform music search from API request to rendered results, with code examples you can ship.
Cross-platform music search lets users find tracks, albums, and artists across multiple streaming services from a single query. Instead of building separate integrations for each platform, developers send one API call and get normalized results back. This cuts months of integration work and delivers the multi-service experience users expect from modern music apps.
Every music app that touches more than one streaming service faces the same UX problem. Users do not care which service hosts a track. They care about finding it. If your app forces users to pick a service before searching, you have already lost them.
A single, unified search bar that queries across services is table stakes for playlist migration tools, music discovery apps, and social music platforms. The search results should show which services have a given track, so users can play it wherever they have a subscription.
Each streaming service returns search results in its own format, with its own field names, and its own quirks. Here is what you are dealing with when you go direct:
| Capability | Spotify | Apple Music | YouTube Music | Tidal | Deezer |
|---|---|---|---|---|---|
| Auth method | OAuth 2.0 + PKCE | Developer token + MusicKit | OAuth 2.0 | OAuth 2.0 | OAuth 2.0 |
| Track title field | name | attributes.name | snippet.title | title | title |
| Artist field | artists[].name | attributes.artistName | snippet.channelTitle | artist.name | artist.name |
| Album art field | album.images[] | attributes.artwork | snippet.thumbnails | album.cover | album.cover |
| Rate limit | 180 req/min | 20 req/sec | Quota-based | 50 req/min | 50 req/5sec |
| Result format | JSON, paginated | JSON:API | JSON, paginated | JSON, paginated | JSON, paginated |
That is five different auth implementations, five different response parsers, and five different rate limiting strategies. For a single feature. Now multiply that by every other endpoint your app needs.
A unified music search API acts as a middleware layer between your application and multiple streaming services. You authenticate once, send one search request, and receive a normalized response that includes results from every connected service. MusicAPI handles the per-service auth, query translation, and response normalization behind the scenes.
The flow looks like this:
Here is what that looks like in practice. A single GET request replaces what would otherwise be five separate API calls, each with its own auth header, query format, and pagination model.
Before your app can search across services, each user needs to authenticate with the streaming services they use. MusicAPI simplifies this with a single authentication flow that handles OAuth for all supported services.
The flow works in three steps:
Once a user has connected their accounts, your search queries automatically include results from every authenticated service.
// Search across all connected services with one request
const response = await fetch(
'https://api.musicapi.com/search?query=Bohemian+Rhapsody&type=track&limit=10',
{
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'X-User-Token': userSessionToken
}
}
);
const results = await response.json();
// results.data contains normalized tracks from every connected service
// Each track has the same field structure regardless of source
console.log(results.data[0]);
// {
// "id": "track_abc123",
// "title": "Bohemian Rhapsody",
// "artist": "Queen",
// "album": "A Night at the Opera",
// "duration_ms": 354320,
// "artwork_url": "https://...",
// "service": "spotify",
// "service_id": "7tFiyTwD0nx5a1eklYtX2J",
// "available": true
// }
That response shape stays the same whether the track came from Spotify, Apple Music, or any of the other supported music services. No conditional parsing. No service-specific field mappings.
Search result normalization is the process of converting each streaming service's unique response format into a single, consistent data structure. This means your frontend code works with one schema regardless of which service returned the track. MusicAPI handles this normalization at the API layer, so you never write per-service parsing logic.
The same song looks different depending on which service you ask. "Bohemian Rhapsody" by Queen has slightly different metadata on every platform:
A normalized API maps all of these variations to consistent field names and formats. You get title, artist, album, duration_ms, and artwork_url every time.
When you search across five services, you will get the same song back multiple times. Deduplication is the process of grouping these results so your UI shows one entry per song with availability badges for each service.
MusicAPI's normalized response makes deduplication straightforward. You can match on a combination of track title, artist name, and duration:
function deduplicateResults(tracks) {
const groups = new Map();
for (const track of tracks) {
const key = normalizeKey(track.title, track.artist, track.duration_ms);
if (groups.has(key)) {
groups.get(key).services.push({
service: track.service,
service_id: track.service_id
});
} else {
groups.set(key, {
...track,
services: [{
service: track.service,
service_id: track.service_id
}]
});
}
}
return Array.from(groups.values());
}
function normalizeKey(title, artist, durationMs) {
// Strip remaster/remix tags and normalize casing
const cleanTitle = title
.replace(/\s*[\(\[].*?(remaster|remix|version|edit).*?[\)\]]\s*/gi, '')
.toLowerCase()
.trim();
const cleanArtist = artist.toLowerCase().trim();
// Allow 2-second tolerance for duration differences
const durationBucket = Math.round(durationMs / 2000);
return `${cleanTitle}|${cleanArtist}|${durationBucket}`;
}
Once you have deduplicated results, rank them by relevance and service availability:
function rankResults(deduplicatedTracks, userServices) {
return deduplicatedTracks
.map(track => ({
...track,
// Score based on how many of the user's services have this track
availabilityScore: track.services.filter(
s => userServices.includes(s.service)
).length,
// Boost exact title matches
relevanceScore: track.title.toLowerCase() === searchQuery.toLowerCase() ? 2 : 1
}))
.sort((a, b) => {
const scoreA = a.availabilityScore * a.relevanceScore;
const scoreB = b.availabilityScore * b.relevanceScore;
return scoreB - scoreA;
});
}
This ranking puts tracks available on the most services first, so users always see the most accessible results at the top.
A good search UI does three things: it responds fast, it shows which services have each track, and it connects results to actions like playback or playlist adds. The unified API response gives you everything you need to build all three without additional API calls.
Searching on every keystroke will burn through your rate limits and create a laggy experience. Debounce search input to 300ms and require at least 2 characters before firing a request:
let debounceTimer;
function handleSearchInput(query) {
clearTimeout(debounceTimer);
if (query.length < 2) {
clearResults();
return;
}
debounceTimer = setTimeout(() => {
performSearch(query);
}, 300);
}
For autocomplete, use a smaller limit parameter (3 to 5 results) during typing and fetch the full result set only when the user submits or selects a suggestion.
After deduplication, each result has a services array. Render this as a row of service badges so users can see at a glance where a track is available:
function TrackResult({ track }) {
return (
<div className="track-result">
<img src={track.artwork_url} alt={track.title} />
<div className="track-info">
<h3>{track.title}</h3>
<p>{track.artist}</p>
</div>
<div className="service-badges">
{track.services.map(s => (
<ServiceBadge key={s.service} service={s.service} />
))}
</div>
</div>
);
}
Search results become useful when they connect to actions. Use the service_id from the normalized response to link directly to playback or playlist creation on a specific service:
function addToPlaylist(track, targetService, playlistId) {
const serviceTrack = track.services.find(
s => s.service === targetService
);
if (!serviceTrack) {
// Track not available on target service
return showAlternativeServices(track);
}
return fetch(
`https://api.musicapi.com/playlists/${playlistId}/tracks`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'X-User-Token': userSessionToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
service: targetService,
track_id: serviceTrack.service_id
})
}
);
}
MusicAPI handles OAuth token refresh, service-specific request formatting, and error handling for each supported endpoint. Your code stays clean regardless of which service the user picks.
Ready to skip months of OAuth and SDK work? Start your free MusicAPI trial and connect 10+ streaming services with one unified API.
MusicAPI supports search across 12+ streaming services, including Spotify, Apple Music, YouTube Music, Tidal, Deezer, Amazon Music, and more. Each service that the user has authenticated with is included in search results automatically.
No. MusicAPI uses a single API key for your application. Users authenticate with each streaming service through MusicAPI's unified auth flow, and the API manages all per-service tokens, including automatic refresh.
MusicAPI manages rate limiting for each streaming service internally. Your application has its own rate limits with MusicAPI, but you do not need to track or throttle requests per streaming service. The API queues and distributes requests to stay within each platform's limits.
The search endpoint supports multiple content types. Pass type=track, type=album, or type=artist to filter results. You can also combine types in a single request to get mixed results.
Search results only include services the user has connected. If a user has only linked Spotify and Apple Music, the search response will contain results from those two services. You can check which services a user has connected via the user profile endpoint and prompt them to add more.
The deduplicated result set includes a services array for each track. Your UI can show availability badges and offer fallback actions. For example, if a track exists on Spotify but not Apple Music, you can suggest the user listen on Spotify or find a similar track on their preferred service.
MusicAPI returns search results in under 200ms for most queries. For autocomplete, set a lower limit (3 to 5) to reduce payload size and combine it with client-side debouncing at 300ms for a responsive experience.