Build a Shazam-style music identification app
Capture a few seconds of audio from the microphone and identify the song with AudD's standard recognition endpoint, including streaming links.
This recipe builds the core of a Shazam-style app: the user taps a button, the app records a few seconds of audio from the microphone, and you tell them what song is playing — with links to open it in Apple Music or Spotify. It’s for anyone adding “what’s this song?” to a web or mobile product.
What you’ll build
Two pieces that fit together:
- A browser front end that records a short clip from the microphone with
MediaRecorderand POSTs the audio blob to your backend. - A small Node backend that takes the uploaded bytes and calls AudD’s
standard recognition endpoint with the official
@audd/sdk.
The front end never sees your API token — only the backend holds it. The backend returns a compact JSON answer (artist, title, streaming links) that the front end renders.
Recognition runs on the standard endpoint (POST https://api.audd.io/).
That’s the right choice for this task: it’s built for a short audio clip,
responds in under 2 seconds, and matches against AudD’s database of 160
million songs. A clip of 5–15 seconds is plenty.
Prerequisites
- An API token from dashboard.audd.io. The
string
testworks for the first run — it’s a public token capped at 10 requests/day on the standard endpoint. - Node.js 20+ for the backend, with the official SDK:
npm install @audd/sdk express - A browser with microphone access.
MediaRecorderandgetUserMediarequire a secure context —https://, orhttp://localhostduring development.
Walkthrough
Step 1: Confirm the backend recognizes a clip
Before wiring up a microphone, prove the recognition path works against a
known file. The SDK’s recognize takes a URL, a filesystem path, raw bytes,
or a Blob, and returns the top match — or null on a successful call that
matched nothing.
import { AudD } from "@audd/sdk";
// get a real token at dashboard.audd.io; "test" is capped at 10 req/day
const audd = new AudD("test");
const song = await audd.recognize("https://audd.tech/example.mp3");
if (song) {
console.log(`${song.artist} — ${song.title}`);
} else {
console.log("no match");
}
Run this and you’ll see one artist/title line printed for the example file. Once that works, you know your token and SDK install are good; everything after this is plumbing.
Step 2: Ask for streaming links
A Shazam-style app wants more than a title — it wants a tappable “open in
Apple Music” link. Pass returnMetadata to populate provider blocks. Valid
values are apple_music, spotify, deezer, and musicbrainz.
Each one you request adds a little latency, so ask only for the providers you
render.
const song = await audd.recognize("https://audd.tech/example.mp3", {
returnMetadata: ["apple_music", "spotify"],
});
if (song) {
console.log(song.artist, "—", song.title);
console.log("Apple Music:", song.streamingUrl("apple_music"));
console.log("Spotify:", song.streamingUrl("spotify"));
console.log("Universal link:", song.songLink);
}
song.songLink is always present on a match: it’s a universal lis.tn URL
that redirects to the song on whatever service the user has. Use it as the
fallback when a specific provider link is missing — see
What you get back.
Step 3: Write the backend endpoint
Now expose the recognition as an HTTP endpoint your front end can POST to.
The SDK accepts a Buffer directly, so you can forward the uploaded bytes
without writing them to disk.
import express from "express";
import { AudD } from "@audd/sdk";
const app = express();
const audd = new AudD(process.env.AUDD_API_TOKEN ?? "test");
// Accept a raw audio body up to the standard endpoint's 10 MB cap.
app.use(express.raw({ type: "audio/*", limit: "10mb" }));
app.post("/identify", async (req, res) => {
try {
const song = await audd.recognize(req.body, {
returnMetadata: ["apple_music", "spotify"],
});
if (!song) {
// A successful call that matched nothing — NOT an error.
return res.json({ matched: false });
}
res.json({
matched: true,
artist: song.artist,
title: song.title,
album: song.album,
songLink: song.songLink,
appleMusic: song.streamingUrl("apple_music"),
spotify: song.streamingUrl("spotify"),
artwork: song.thumbnailUrl,
});
} catch (err) {
console.error("recognition failed", err);
res.status(502).json({ error: "recognition_failed" });
}
});
app.listen(8080, () => console.log("listening on :8080"));
The key line is { matched: false }: AudD returns result: null when the
clip didn’t match anything, and the SDK surfaces that as null from
recognize. That is a normal, successful outcome — a quiet room, a song not
in the database, a clip that’s mostly speech. Treat it as “try again,” never
as a server error.
Step 4: Record from the microphone in the browser
The front end records a short clip with MediaRecorder, then POSTs the blob
to /identify. Keep the clip short — a handful of seconds is enough for the
standard endpoint, and it keeps you well under the 10 MB cap.
<button id="listen">Identify song</button>
<pre id="out"></pre>
<script type="module">
const out = document.getElementById("out");
document.getElementById("listen").addEventListener("click", async () => {
out.textContent = "Listening…";
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const recorder = new MediaRecorder(stream);
const chunks = [];
recorder.addEventListener("dataavailable", (e) => chunks.push(e.data));
recorder.addEventListener("stop", async () => {
stream.getTracks().forEach((t) => t.stop()); // release the mic
const blob = new Blob(chunks, { type: recorder.mimeType });
const res = await fetch("/identify", {
method: "POST",
headers: { "Content-Type": "audio/webm" },
body: blob,
});
const data = await res.json();
if (!data.matched) {
out.textContent = "No match — try again with the music a bit louder.";
return;
}
out.textContent =
`${data.artist} — ${data.title}\n` +
`Apple Music: ${data.appleMusic ?? data.songLink}\n` +
`Spotify: ${data.spotify ?? data.songLink}`;
});
recorder.start();
setTimeout(() => recorder.stop(), 7000); // record ~7 seconds
});
</script>
When you click the button, the browser asks for microphone permission,
records about seven seconds, uploads the blob, and prints the artist, title,
and streaming links. MediaRecorder typically produces WebM/Opus on
Chrome and Firefox; AudD decodes it server-side, so you don’t need to
transcode.
Keep clips short and the mic close to the source. The standard endpoint is tuned for a short audio clip. Seven to ten seconds of reasonably clean audio matches reliably; a 30-second recording mostly adds upload size and latency without improving the result.
What you get back
On a match, the SDK gives you the top result. With returnMetadata: ["apple_music", "spotify"], the underlying response looks like this:
{
"status": "success",
"result": {
"artist": "Imagine Dragons",
"title": "Warriors",
"album": "Smoke + Mirrors (Deluxe)",
"release_date": "2015-02-17",
"label": "KIDinaKORNER/Interscope Records",
"timecode": "00:31",
"song_link": "https://lis.tn/Warriors",
"apple_music": { "url": "https://music.apple.com/…", "…": "…" },
"spotify": { "external_urls": { "spotify": "https://open.spotify.com/…" }, "…": "…" }
}
}
| Field | Meaning |
|---|---|
artist, title, album | The recognized track. |
release_date, label | When and by whom the recording was released. |
timecode | Position within the matched song where the user’s clip occurred — not an offset into the user’s clip. A clip recorded 31 seconds into “Warriors” reports 00:31. |
song_link | Universal lis.tn URL. Always present on a match. Redirects the user to the song on a service they have. Your reliable fallback when a specific provider link is absent. |
apple_music, spotify, … | Per-provider blocks, present only for the providers you named in returnMetadata. Shapes mirror each provider’s own track object. |
In the SDK, prefer the typed helpers over reaching into provider blocks by
hand: song.streamingUrl("apple_music") returns the direct Apple Music URL
when you requested that block, and falls back to a lis.tn redirect derived
from song_link otherwise. song.streamingUrls() returns every resolvable
provider link, and song.previewUrl() returns a 30-second preview when one
is available.
For fields outside the typed surface (anything AudD returns that the SDK
doesn’t model as a property), read song.extras["the_key"], or
song.rawResponse for the full payload.
Handling errors
For this recipe, separate three outcomes that are easy to conflate:
- No match —
recognizeresolves tonull. Not an error. Render “try again,” optionally with a hint to hold the phone closer to the speaker. - Errors the server returns — bad token, exhausted quota, or audio it couldn’t decode. The SDK raises typed exceptions:
import {
AudDAuthenticationError,
AudDQuotaError,
AudDInvalidAudioError,
AudDAPIError,
AudDConnectionError,
} from "@audd/sdk";
try {
const song = await audd.recognize(req.body, {
returnMetadata: ["apple_music", "spotify"],
});
// …
} catch (err) {
if (err instanceof AudDAuthenticationError) {
// bad/missing token — a config problem, not a per-request one
return res.status(500).json({ error: "server_misconfigured" });
} else if (err instanceof AudDQuotaError) {
// out of requests — top up at dashboard.audd.io
return res.status(429).json({ error: "quota_exhausted" });
} else if (err instanceof AudDInvalidAudioError) {
// the uploaded blob wasn't decodable audio
return res.status(422).json({ error: "unreadable_audio" });
} else if (err instanceof AudDAPIError) {
console.error(`AudD #${err.errorCode}: ${err.serverMessage} (request_id=${err.requestId})`);
return res.status(502).json({ error: "recognition_failed" });
} else if (err instanceof AudDConnectionError) {
// transient network issue — the SDK already retried
return res.status(504).json({ error: "upstream_timeout" });
}
throw err;
}
- Quota during testing — the
testtoken is capped at 10 requests/day on the standard endpoint. If recognition suddenly starts returning a quota error in development, you’ve hit that cap; switch to a real token from the dashboard.
The SDK retries transient connection failures before the upload completes, but never repeats a recognition call after your bytes reached the server — re-sending would risk double-billing the request.
Going further
- Mobile. The same standard endpoint backs the native SDKs. On iOS,
record with
AVAudioRecorderand call the Swift SDK (github.com/AudDMusic/audd-swift); on Android, capture withMediaRecorderand call the Kotlin SDK (io.audd:audd-kotlinon Maven Central). The recognition call and theresult: nullno-match contract are identical — only the recording API differs per platform. - Match against your own tracks. If you want the app to recognize a
private catalog (a label’s unreleased promos, an artist’s own back
catalog), upload those songs to a custom catalog first; later
recognizecalls on the same account match against them too. Custom-catalog access is gated — email [email protected] to enable it. - Longer or mixed audio. The standard endpoint returns one match. To identify every song across a longer recording (a DJ set, a podcast), use the enterprise endpoint instead — see Standard, enterprise, or streams: how to choose.
Related
Reading this as an AI agent? The raw Markdown is at recipes/shazam-clone.md, and the full index is /resources/llms.txt.
