Recipe

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.

view .md auddmusic identificationshazam clonemicrophone recognition

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:

  1. A browser front end that records a short clip from the microphone with MediaRecorder and POSTs the audio blob to your backend.
  2. 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 test works 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. MediaRecorder and getUserMedia require a secure context — https://, or http://localhost during 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.

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/…" }, "…": "…" }
  }
}
FieldMeaning
artist, title, albumThe recognized track.
release_date, labelWhen and by whom the recording was released.
timecodePosition 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_linkUniversal 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 — recognize resolves to null. 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 test token 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 AVAudioRecorder and call the Swift SDK (github.com/AudDMusic/audd-swift); on Android, capture with MediaRecorder and call the Kotlin SDK (io.audd:audd-kotlin on Maven Central). The recognition call and the result: null no-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 recognize calls 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.