Published on August 8, 2026

Every streaming service stores and returns track metadata differently. Spotify calls it name. Apple Music calls it attributes.name. YouTube Music buries it in snippet.title. If you are building an app that pulls data from multiple services, you either write custom parsers for each one or you use a normalized API that does it for you.
This post breaks down exactly how metadata differs across services, what a normalized response looks like, and why it matters for your codebase.
Music metadata normalization is the process of converting each streaming service's unique data format into a single, consistent schema. Each of the 12+ major streaming platforms uses different field names, data types, and nesting structures for the same information. Without normalization, developers write and maintain separate parsing logic for every service they integrate.
The most basic piece of metadata, a track's title, has a different field name on almost every service:
| Service | Track title field | Nesting depth |
|---|---|---|
| Spotify | name | Top-level |
| Apple Music | attributes.name | 1 level deep |
| YouTube Music | snippet.title | 1 level deep |
| Tidal | title | Top-level |
| Deezer | title | Top-level |
| SoundCloud | title | Top-level |
| Amazon Music | title | Varies by endpoint |
| Napster | name | Top-level |
Four different field names for the same data point. Your code either handles all four, or you normalize upstream.
Artist data is where things get really inconsistent:
[{"name": "Queen", "id": "1dfeR..."}, {"name": "David Bowie", "id": "0oSG..."}]"Queen & David Bowie""Queen - Topic"{"name": "Queen", "id": 7553}{"name": "Queen", "id": 412}A collaboration between two artists looks fundamentally different on every platform. Spotify gives you structured data you can iterate. Apple Music gives you a string you have to split. YouTube Music gives you a channel name that may not even be the artist's real name.
Beyond titles and artists, nearly every metadata field has service-specific quirks:
| Field | Spotify | Apple Music | YouTube Music | Tidal | Deezer |
|---|---|---|---|---|---|
| Album art | album.images[] (array of sizes) | attributes.artwork (template URL with {w}x{h}) | snippet.thumbnails (object with size keys) | album.cover (base URL + size suffix) | album.cover (base URL + ?size=) |
| Duration | duration_ms (int, ms) | attributes.durationInMillis (int, ms) | Not in search results | duration (int, seconds) | duration (int, seconds) |
| ISRC | external_ids.isrc | attributes.isrc | Not available | isrc | isrc |
| Explicit flag | explicit (bool) | attributes.contentRating (string) | Not available | explicit (bool) | explicit_lyrics (bool) |
| Release date | album.release_date (string, variable precision) | attributes.releaseDate (ISO 8601) | Not in search results | streamStartDate (ISO 8601) | release_date (YYYY-MM-DD) |
Duration alone requires a conditional: is it milliseconds or seconds? Album art requires different URL construction logic per service. ISRC, the one universal track identifier, is not even available on YouTube Music.
A normalized music API response maps every service's metadata into a single schema with consistent field names, types, and formats. MusicAPI returns the same track object structure regardless of which streaming service the data came from. This means your frontend, database, and business logic code works with one data shape instead of twelve.
Every track returned by MusicAPI follows this structure:
{
"id": "track_abc123",
"title": "Bohemian Rhapsody",
"artist": "Queen",
"artists": [{"name": "Queen", "id": "artist_xyz"}],
"album": "A Night at the Opera",
"album_id": "album_def456",
"artwork_url": "https://cdn.musicapi.com/artwork/abc123/600x600.jpg",
"duration_ms": 354320,
"isrc": "GBUM71029604",
"explicit": false,
"release_date": "1975-10-31",
"service": "spotify",
"service_id": "7tFiyTwD0nx5a1eklYtX2J",
"available": true
}
Key guarantees:
title is always a string, always the clean track title (no remaster/remix tags appended unless they are part of the official title)artist is always a string with the primary artist nameartists is always an array, even for single-artist tracksduration_ms is always in milliseconds, always an integerartwork_url is always a resolved, CDN-backed URL (no template strings to construct)isrc is present when the source service provides it, null otherwiserelease_date is always ISO 8601 formatPlaylists and albums follow the same normalization principles. A playlist response always includes:
{
"id": "playlist_ghi789",
"name": "Chill Vibes",
"description": "Relaxing tracks for focus time",
"owner": "user_jkl012",
"track_count": 47,
"artwork_url": "https://cdn.musicapi.com/artwork/ghi789/300x300.jpg",
"service": "apple_music",
"service_id": "pl.a1b2c3d4",
"public": true
}
Here is the same track as returned by Spotify's API vs. MusicAPI:
Raw Spotify response (trimmed):
{
"name": "Bohemian Rhapsody - Remastered 2011",
"artists": [{"name": "Queen", "id": "1dfeR4PH6GvB29R", "uri": "spotify:artist:1dfeR4PH6GvB29R"}],
"album": {
"name": "A Night at the Opera (Remastered 2011)",
"images": [
{"url": "https://i.scdn.co/image/ab67616d0000b273...", "width": 640, "height": 640},
{"url": "https://i.scdn.co/image/ab67616d00001e02...", "width": 300, "height": 300}
],
"release_date": "1975-10-31",
"release_date_precision": "day"
},
"duration_ms": 354320,
"explicit": false,
"external_ids": {"isrc": "GBUM71029604"},
"id": "7tFiyTwD0nx5a1eklYtX2J",
"uri": "spotify:track:7tFiyTwD0nx5a1eklYtX2J"
}
Normalized MusicAPI response:
{
"title": "Bohemian Rhapsody",
"artist": "Queen",
"artists": [{"name": "Queen", "id": "artist_1dfeR4"}],
"album": "A Night at the Opera",
"artwork_url": "https://cdn.musicapi.com/artwork/7tFiy/600x600.jpg",
"duration_ms": 354320,
"isrc": "GBUM71029604",
"explicit": false,
"release_date": "1975-10-31",
"service": "spotify",
"service_id": "7tFiyTwD0nx5a1eklYtX2J"
}
Notice: the remaster tag is stripped from the title. The artist is extracted as a clean string. The artwork URL is a direct link, not a Spotify CDN URL that changes format between endpoints. The album name is also cleaned.
MusicAPI uses service-specific adapter modules that map each platform's raw API response into the unified schema. Each adapter knows the exact field paths, data types, and quirks of its target service. When a service changes its API (which happens regularly), only the adapter for that service needs updating. Your code stays the same.
Each adapter does three things:
name to title, Apple Music's attributes.name to title)Not every service provides every field. YouTube Music does not return ISRCs. SoundCloud does not return album information for most tracks. The normalized response uses null for missing fields instead of omitting them, so your code can always check if (track.isrc) without first checking if the field exists.
This is a deliberate API design choice. Nullable fields are safer than absent fields because they prevent undefined reference errors and make TypeScript/JSON Schema validation straightforward.
Streaming services update their APIs frequently. Spotify has changed their response format several times. Apple Music introduced MusicKit JS v3 with different field names. When these changes happen, MusicAPI updates the relevant adapter. The normalized response your app receives stays identical.
This is the core value of the normalization layer: it absorbs API instability so your code does not have to. Check the supported features page for the current field coverage per service.
Ready to skip months of OAuth and SDK work? Start your free MusicAPI trial and connect 10+ streaming services with one unified API.
Normalized metadata is not just a convenience. It changes how you architect your application. When every track, playlist, and album follows the same schema regardless of source, your database models, search indexes, and frontend components all get simpler.
With normalized responses, your data access layer works the same for every supported service:
// This function works for Spotify, Apple Music, Tidal, Deezer, and more
async function getPlaylistTracks(playlistId, service) {
const response = await musicapi.get(`/playlists/${playlistId}/tracks`, {
params: { service }
});
// response.data.items always has the same shape
return response.data.items;
}
No switch statements. No service-specific response handlers. One function handles all twelve services.
When every track follows the same schema, your database table matches the API response:
CREATE TABLE tracks (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
artist TEXT NOT NULL,
album TEXT,
duration_ms INTEGER,
isrc TEXT,
artwork_url TEXT,
service TEXT NOT NULL,
service_id TEXT NOT NULL
);
One table. One index. One full-text search configuration. Compare that to maintaining separate tables or columns for each service's idiosyncratic field structure.
ISRCs (International Standard Recording Codes) are the gold standard for identifying the same recording across services. When MusicAPI surfaces ISRCs from services that provide them, you can match tracks across platforms:
function findDuplicates(tracks) {
const byIsrc = new Map();
for (const track of tracks) {
if (track.isrc) {
if (!byIsrc.has(track.isrc)) byIsrc.set(track.isrc, []);
byIsrc.get(track.isrc).push(track);
}
}
return Array.from(byIsrc.values()).filter(group => group.length > 1);
}
For services that do not provide ISRCs, fall back to matching on normalized title + artist + duration (with tolerance). The normalized API makes this possible because titles and artists are already cleaned and formatted consistently.
Music metadata normalization converts each streaming service's unique data format into a single, consistent schema. Instead of handling different field names, types, and structures for Spotify, Apple Music, and YouTube Music, you work with one unified format.
MusicAPI normalizes metadata from 12+ streaming services, including Spotify, Apple Music, YouTube Music, Tidal, Deezer, SoundCloud, Amazon Music, Napster, and more.
The normalized response returns null for fields the source service does not provide. This applies to ISRCs on YouTube Music, album data on SoundCloud, and other service-specific gaps. Your code can safely check for null without worrying about missing keys.
No. Normalization reformats and restructures metadata without changing its meaning. Track titles get cleaned (remaster tags stripped), artist names get extracted from complex objects, and durations get converted to a consistent unit. The underlying data stays accurate.
The normalized response includes both an artist string (primary artist) and an artists array (all credited artists). This gives you a clean display name and structured data for linking to artist pages.
Yes. The service_id field in every normalized response lets you map back to the original resource on the source service. You can use this to construct deep links to the track on Spotify, Apple Music, or any other platform. For the raw auth tokens, see the original auth tokens endpoint.
Frequently enough that it matters. Major services update their APIs multiple times per year. MusicAPI's adapter layer absorbs these changes so your integration stays stable. Check the API docs for the current normalized schema.