DEV Community

Christiaan Maks
Christiaan Maks

Posted on

Matching 90M+ music tracks across six platforms: ISRCs, fuzzy matching, and what breaks

I run a music metadata API as a solo developer. Under it sits a catalog of 90M+ recordings aggregated from six platforms: Spotify, Apple Music, Tidal, Beatport, Discogs, and MusicBrainz. The core job is cross-referencing: take whatever you know about a track (an ISRC, a platform ID, or a messy "artist + title" string from a DJ export) and resolve it to one canonical recording with everything else attached.

When I started, I assumed this was mostly a plumbing problem. Every platform has an API, recordings have a standard identifier, join on it, done. Almost none of that survived contact with real data. This post is the parts I had to learn the hard way: why one song legitimately carries many ISRCs, how fuzzy matching on artist and title actually has to work, why recording-to-composition mapping is many-to-many in both directions, and the failure modes I now check for routinely.

The ISRC almost solves it

The ISRC (International Standard Recording Code) is a 12-character identifier for a specific recording. Daft Punk's "One More Time" is GBDUW0000053: country prefix GB, registrant code DUW, year 00, then a designation number. Every commercially released recording is supposed to have one, and most platforms expose it. So the naive architecture writes itself: one isrc column on the track table, join all six platforms on it, ship.

That was my first schema, and it was wrong in a way that took a while to surface.

Labels mint a fresh ISRC for every commercial variant of a recording. The radio edit gets one. The extended mix gets one. The 2001 release and the anniversary remaster get different ones. A reissue through a new distributor often gets one even when the audio is bit-identical. Regional releases sometimes get their own. None of this is an error; it is how the system is designed to work, because each of those is a distinct commercial product even when it is the same performance.

The consequence: one canonical recording legitimately carries many ISRCs, and different platforms will report different ones depending on which release they ingested. Two things followed for my design:

  1. ISRC is a junction table, not a column. One track row, many ISRC rows, each tagged with the source it came from. Deduplication collapses the radio edit, remaster, and reissue variants onto one canonical track while retaining every ISRC. Any of a track's ISRCs resolves to the same record.
  2. Cross-source ISRC disagreement is not a data-quality signal. Early on I built a consistency check that flagged tracks where two platforms reported different ISRCs, on the theory that disagreement meant a bad merge. It flagged correct data almost everywhere it fired. I deleted the check. The thing that actually indicates a bad merge is the inverse: the same ISRC attached to two different canonical tracks. That should not happen, and a periodic GROUP BY isrc HAVING COUNT(*) > 1 over the junction table is one of the cheapest data-integrity checks I run.

Looking a track up by any of its ISRCs returns the canonical record, with one representative ISRC on the payload:

curl "https://api.sonovault.now/v1/tracks/isrc/GBDUW0000053" \
  -H "x-api-key: $SONOVAULT_API_KEY"
Enter fullscreen mode Exit fullscreen mode
{
  "id": 123,
  "title": "One More Time",
  "releases": [
    {
      "id": 1,
      "title": "Discovery",
      "artist": { "id": 1, "name": "Daft Punk" },
      "label": { "id": 10, "name": "Virgin Records" },
      "release_date": "2001-03-12"
    }
  ],
  "artists": [
    { "id": 1, "name": "Daft Punk", "is_primary": true, "is_remixer": false }
  ],
  "isrc": "GBDUW0000053",
  "duration": 320,
  "genre": ["House"],
  "subgenre": ["French House"]
}
Enter fullscreen mode Exit fullscreen mode

Cross-platform resolution rides on top

Once recordings are deduplicated with all their ISRCs retained, cross-platform ID resolution is mostly a graph you maintain rather than compute. Each platform ingest attaches that platform's track ID to the canonical recording, keyed by ISRC when the platform exposes one. Where a source has no ISRC for a track, the fallback is artist + title matching (more on why that is hard below), verified against duration and release metadata before the link is written.

The payoff is that any single known identifier fans out to all the others. Give it an ISRC, a Spotify ID, or a Beatport ID, and you get back the full set:

curl "https://api.sonovault.now/v1/tracks/links?isrc=GBDUW0000053" \
  -H "x-api-key: $SONOVAULT_API_KEY"
Enter fullscreen mode Exit fullscreen mode
{
  "track_id": 123,
  "title": "One More Time",
  "isrc": "GBDUW0000053",
  "links": [
    { "source": "spotify", "external_id": "0DiWol3AO6WpXZgp0goxAV", "url": "https://open.spotify.com/track/0DiWol3AO6WpXZgp0goxAV" },
    { "source": "beatport", "external_id": "12345678", "url": "https://www.beatport.com/track/-/12345678" },
    { "source": "applemusic", "external_id": "1440650", "url": "https://music.apple.com/song/1440650" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Fuzzy matching: the strings people actually have

A large share of lookups do not start from an identifier at all. They start from a text field in a rekordbox or Serato export, a radio automation log, or a CSV someone assembled by hand. The same recording shows up as:

Daft Punk - One More Time (Original Mix)
01. Daft Punk feat. Romanthony - One More Time [Clean]
DAFT PUNK "One More Time" (Radio Edit)
Enter fullscreen mode Exit fullscreen mode

The matching pipeline that handles this is unglamorous: normalize casing and diacritics, pull feat. credits out of both fields, strip decoration tokens ((Original Mix), [Clean], track-number and "Part N" prefixes), then try an exact match on the normalized form before falling back to scored fuzzy search in Elasticsearch, with duration as a tiebreaker when the input has one. The lessons are all in the edge cases:

  • Decorations carry meaning, sometimes. Stripping (Radio Edit) is correct when the question is "which song is this" and wrong when the question is "which variant is this". You have to decide which question your matcher answers. Mine answers the first, because dedup already collapsed the variants onto one canonical track, so both strings should resolve to the same place.
  • Token stripping needs word boundaries and paranoia. [Clean] in brackets is a DJ-pool tag; the word "Clean" inside an artist name is not. Every rule in the strip list eventually meets an artist or title that uses it legitimately.
  • Titles are massively reused. There are enormous numbers of entirely unrelated songs called "Home" or "Stay". Title similarity alone is close to worthless as a match signal; the artist has to agree too. This sounds obvious written down. It cost me a cleanup pass to fully believe it (see the ISWC section).
  • Artist names fragment and conflate. "&" versus "and", a stray "The", diacritics dropped by one exporter and kept by another: these fragment one artist into several. The opposite failure is worse: two different artists with the same name. Discogs disambiguates those with numeric suffixes like (2), and merging them by name conflates real people. Never merge artists on name similarity alone; you need shared identifiers or shared release evidence.

From code, search takes artist and title as separate parameters and returns a results array plus a pagination cursor:

const res = await fetch(
  "https://api.sonovault.now/v1/tracks/search?" +
    new URLSearchParams({ artist: "Daft Punk", title: "One More Time" }),
  { headers: { "x-api-key": process.env.SONOVAULT_API_KEY! } }
);

const data = await res.json();
const track = data.results[0];
console.log(track.isrc);                     // "GBDUW0000053"
console.log(track.releases[0].release_date); // "2001-03-12"
// data.next_cursor pages through further matches, null on the last page
Enter fullscreen mode Exit fullscreen mode

ISRC to ISWC: recordings versus compositions

An ISRC identifies a recording. An ISWC (format T plus ten digits) identifies the composition behind it, the written work. Mapping between them sounds like a lookup table and is actually the messiest part of the whole system, because the relationship is many-to-many in both directions:

  • One recording can carry several ISWCs. A medley embodies every work it contains. A track built on a substantial sample can be linked to the sampled work as well as its own.
  • One ISWC maps to many recordings. Every cover, live take, radio edit, and remaster of "Harder Better Faster Stronger" is a distinct recording (often with its own ISRC, per the earlier section) of the same work, T0701427997.

The reverse lookup makes the shape visible:

curl "https://api.sonovault.now/v1/tracks/iswc/T0701427997" \
  -H "x-api-key: $SONOVAULT_API_KEY"
Enter fullscreen mode Exit fullscreen mode
{
  "iswc": "T0701427997",
  "title": "HARDER BETTER FASTER STRONGER",
  "total": 2,
  "recordings": [
    { "sonovault_id": 123, "isrc": "GBDUW0000059", "title": "Harder Better Faster Stronger", "artist": "Daft Punk" },
    { "sonovault_id": 456, "isrc": "GBDUW0000182", "title": "Harder Better Faster Stronger - Radio Edit", "artist": "Daft Punk" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Two hard-won rules from building this mapping:

  1. Never link on title alone. An early version of my matcher accepted a work-to-recording link when the titles matched, even if the artists did not. Because titles are so heavily reused, this quietly attached compositions to unrelated songs that happened to share a name. The fix was strict: the artist has to agree or the link is rejected, and I ran a cleanup pass over everything the old rule had written.
  2. Distrust upstream links too. Source datasets for composition data contain their own mis-attributions; a recurring pattern is an entire album's recordings linked to a single work named after the album. If you ingest those links blindly you inherit the error, so I sanity-check that the work title actually resembles the recording title before accepting one.

What breaks, as a checklist

If you build something in this space, the recurring failure modes I now monitor for: the same ISRC on two canonical tracks (merge error, always a bug), title-only matches (unrelated songs sharing a name), artist conflation (same name, different people) and fragmentation ("&" versus "and"), release-level metadata sprayed onto every track of an album, and upstream identifier links that are simply wrong. Everything else in the pipeline is ordinary engineering. The data is the hard part.

Where this runs

All of the above powers SonoVault, the API the examples in this post hit. There is a free tier, auth is one x-api-key header, and there is no OAuth dance or approval queue. If you want to poke at the data without writing code, the ISRC lookup tool is the same endpoint behind a form, and the full endpoint reference is in the docs. If you are weighing an aggregated API against running MusicBrainz yourself (a reasonable choice for plenty of projects), I wrote an honest comparison here.

One caveat I put on everything: this is factual metadata aggregated from third parties. It can be wrong, incomplete, or mismatched, so verify it before you use it for anything contractual or financial. That is true of every metadata source in this industry; the difference is whether the vendor tells you.

Top comments (0)