Beta founding offer: lock in 50% off any plan for life, before the beta ends. Claim founding pricing →
ResourcesIntegration GuidesBuild a “What’s This Track?” Button for a Music or DJ App

Build a “What’s This Track?” Button for a Music or DJ App

Wire audio capture to recognition: fingerprint a clip with fpcalc, call POST /v1/tracks/identify, and show the matched track with its Listen On / Buy On links.

What you're building

The interaction is familiar: the user hears a track (in a club, in a stream, in your own player), taps a button, and a moment later sees exactly what it is and where to get it. Under the hood that is two API calls glued to a fingerprint: POST /v1/tracks/identify turns a few seconds of audio into a matched catalogue track, and GET /v1/tracks/links turns that track into a row of “Listen on” / “Buy on” buttons.

This guide wires the whole thing together for a desktop or DJ app, where you can bundle the fingerprinter and keep the work off the user's network. Identify is a paid-tier feature (a free-tier key gets a 403) and costs 10 credits per fingerprint, matched or not.

Architecture

The flow is four hops, all driven from your app's backend or main process:

  • Capture a short audio clip: a file the user picks, or a few seconds recorded from the output.
  • Fingerprint it with fpcalc (the Chromaprint CLI) into a compact integer array.
  • Identify: post the fingerprint, read the ranked results.
  • Hydrate & render: fetch full metadata and platform links for the best hit and draw the buttons.

A successful identify returns a deliberately slim body: a ranked list, best first, each with a confidence from 0 to 1:

POST/v1/tracks/identify200 OK
{
  "matched": true,
  "results": [
    {
      "id": "8471023",
      "title": "Harder, Better, Faster, Stronger",
      "artists": [{ "id": 1925615, "name": "Daft Punk", "is_primary": true, "is_remixer": false }],
      "confidence": 0.86
    }
  ],
  "credits_charged": 10
}
Build
1

Get the audio

You need bytes on disk (or a temp file). Let the user select a file, or record from the current output and write it out. Length matters more than you would think: the section that matches our reference can sit anywhere in a recording, often the main hook minutes in rather than the intro, so a few seconds from the wrong part misses the match even when the track is in the catalog. Use the whole track where you can. A lossless master is still wasted work, though: 320 kbps already gives the best match.

2

Fingerprint it with fpcalc

fpcalc -raw prints a comma-separated integer fingerprint. Spawn it and parse the FINGERPRINT= line:

TypeScriptfingerprint.ts
import { execFile } from "node:child_process";
import { promisify } from "node:util";

const run = promisify(execFile);

// Runs in the Electron MAIN process. fpcalc is the Chromaprint CLI,
// bundled next to the app binary. -raw prints the integer fingerprint.
async function fingerprint(path: string): Promise<number[]> {
  const { stdout } = await run("fpcalc", ["-raw", path]);
  const m = stdout.match(/FINGERPRINT=(.+)/);
  if (!m) throw new Error("fpcalc produced no fingerprint");
  return m[1].split(",").map(Number);
}
Spawn fpcalc from the main process, not the renderer. Bundle the fpcalc binary with your app and shell out to it from your Node/Electron main process (or your backend). The renderer should never see the binary path or, later, the API key.
3

Call the identify endpoint

Post the fingerprint array as JSON. Take the first result, since the response is already ranked best-first, and keep its confidence so you can decide how loudly to assert the match:

TypeScriptidentify.ts
const BASE = "https://api.sonovault.now/v1";
const headers = { "x-api-key": API_KEY };

// Still in the main process - the key never reaches the renderer.
async function identify(fp: number[]) {
  const res = await fetch(`${BASE}/tracks/identify`, {
    method: "POST",
    headers: { ...headers, "content-type": "application/json" },
    body: JSON.stringify({ fingerprint: fp }),
  });
  if (res.status === 503) throw new Error("matcher busy - retry shortly");
  const { matched, results } = await res.json();
  return matched ? results[0] : null;   // best hit, or null
}
confidenceis a 0–1 score: roughly 1.0 for a clean match, ~0.75 for a good one, and 0.5 at the acceptance gate. Treat anything at the low end as “probably, but offer a re-capture”.
4

Hydrate and render the result

The identify result carries an id but not the full metadata, which keeps the recognition response small. Fetch the track and its cross-platform links by id, then map each link to a button:

TypeScriptresolve.ts
// hit.id came from the identify result. Fetch metadata + links in parallel.
async function resolve(id: string) {
  const [track, linkRes] = await Promise.all([
    fetch(`${BASE}/tracks/${id}`, { headers }).then(r => r.json()),
    fetch(`${BASE}/tracks/links?id=${id}`, { headers }).then(r => r.json()),
  ]);

  // track.title, track.artists, track.isrc - linkRes.links: { source, url }[]
  for (const { source, url } of linkRes.links) {
    const verb = source === "beatport" ? "Buy on" : "Listen on";
    addButton(`${verb} ${source}`, url);
  }
  return track;
}

GET /v1/tracks/:id gives you the title, artists, genre, duration and a representative isrc; GET /v1/tracks/links returns one entry per platform (Spotify, Apple Music, Tidal, Beatport, Discogs, MusicBrainz, YouTube), each with a ready-built url. Rendering those into buttons is the exact pattern from the Listen On / Buy On buttons guide.

5

Handle no-match and low confidence

Not every tap lands. On matched: false or an empty resultsarray, show a clear “couldn't identify that” state and invite a re-capture from a busier section of the track. Two error paths are worth handling explicitly:

  • 503 with a Retry-After header: the matcher is briefly unreachable. Wait the stated seconds and retry; the fingerprint is still good.
  • 429: the call would exceed the key's remaining monthly credits, so it's denied before any matching work. Surface a quota message rather than a generic error.

Going further

  • Debounce the button.Fingerprinting and a round-trip take a beat; disable the button while a lookup is in flight so an impatient double-tap doesn't burn 20 credits on the same clip.
  • Cache by fingerprint. If the same clip comes back (a user re-taps on the same track), you already know the answer, so key a small cache on a hash of the fingerprint and skip the call.
  • Proxy through your backend. A shipped desktop app is not a safe place for an API key. Send the fingerprint to your server, add x-api-key there, and forward to Sonovault. Your key never leaves your infrastructure.
  • No fpcalc? Upload instead.If a target can't run the binary, post the track to the same endpoint at 320 kbps and let the server fingerprint it (cost becomes 10 + ceil(MB)).

Frequently asked questions

Where do I get fpcalc?

fpcalc is the command-line tool that ships with Chromaprint, the open-source audio fingerprinter. Grab a prebuilt binary from the Chromaprint releases for each platform you target and bundle it with your app, or install it from your package manager for a server-side build. It is a single self-contained binary with no runtime dependencies.

Can I do this entirely in a browser?

Not the fingerprinting step: fpcalc is a native binary, so there is nothing to run in a plain browser tab. If you have no way to run fpcalc client-side, use the upload mode instead: post a short compressed clip to the same identify endpoint and let the server fingerprint it. The trade-off is more bytes on the wire and a cost of 10 + ceil(MB) credits rather than a flat 10.

How many credits does each tap cost?

A fingerprint lookup is 10 credits, whether it matches or not. If you upload raw audio instead of a fingerprint, it is 10 + ceil(MB) credits billed on the full uploaded size. Identify is a paid-tier feature, so a free-tier key gets a 403.

How do I keep my API key out of a shipped desktop app?

Never embed the key in the renderer or the client bundle, because anyone can extract it. Proxy the identify call through your own backend: the app sends the fingerprint to your server, your server adds the x-api-key header and forwards it to Sonovault. In Electron, at minimum keep the key and the fpcalc spawn in the main process, never the renderer.

Ready to build?

Free API key. No credit card. 1,000 requests to get started.

Get Free API Key
More in Integration Guides
Integration GuidesAn ACRCloud Alternative When You Need Recognition Plus Metadata7 min readIntegration GuidesCursor Pagination Patterns: Walking Large Result Sets Safely5 min read