Integration

Add music recognition to a React Native app

Record a few seconds of audio in a React Native app, upload it to your own backend, and identify the song with AudD's standard recognition endpoint.

view .md auddreact nativemusic recognitionmobile

This page shows how to add “what’s this song?” to a React Native app. The app records a few seconds of audio on the device, uploads the clip to a backend you control, and the backend calls AudD to identify the track. It’s for mobile developers who want song identification without putting their API token inside a shipped app.

What you’ll build

Three moving parts:

  1. The React Native app records a short clip with an audio-recording library and POSTs the file to your backend over fetch + FormData.
  2. Your backend receives the upload, calls AudD’s standard recognition endpoint with the official @audd/sdk, and returns a compact JSON answer.
  3. The app renders the result: artist, title, and a song_link the user can tap to open the track.

Recognition runs on the standard endpoint (POST https://api.audd.io/). That’s the right choice here: it’s built for a short audio clip, responds in under 2 seconds, and matches against AudD’s database of 160 million songs. A 5–15 second clip is plenty. The standard endpoint has a 10 MB file-size cap, which a few seconds of compressed audio stays well under.

Never put your AudD token in the app. A shipped mobile app is not a trusted environment. Anyone can pull the IPA/APK off a device, unzip it, and read every string in the JavaScript bundle and native binary — including any API token you embedded. A token in the app is a token anyone can extract and spend against your account. The token lives only on your backend, and the app talks to your backend. This page is built around that rule.

Prerequisites

  • An API token from dashboard.audd.io. Keep it on the backend. The string test works for a first run on the backend — it’s a public token capped at 10 requests/day on the standard endpoint.
  • A React Native app (bare React Native or Expo with a dev/prebuild that includes a native audio module — recording needs native code).
  • An audio-recording library. Common, well-maintained options:
    • react-native-audio-recorder-player
    • expo-av (Expo) / its successor expo-audio
    • react-native-nitro-sound Each records to a file on the device and gives you back a file path (and on most, the recording format). Pick one and follow its README for setup; the example below shows the shape, not one library’s exact method names.
  • Node.js 20+ for the backend, with npm install @audd/sdk express multer.

Walkthrough

Step 1: Record a clip on the device

Ask for the microphone permission, start recording when the user taps a button, and stop after a few seconds. Every recording library follows the same three-call shape — start, stop, read the resulting file path:

// Shape of a typical RN audio-recording library.
// Method names differ per library — check its README.
import { PermissionsAndroid, Platform } from "react-native";
import { recorder } from "./recorder"; // your chosen library, wrapped

async function ensureMicPermission() {
  if (Platform.OS === "android") {
    const granted = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
    );
    return granted === PermissionsAndroid.RESULTS.GRANTED;
  }
  return true; // iOS: declare NSMicrophoneUsageDescription in Info.plist
}

// Returns a local file URI like file:///.../clip.m4a
async function recordClip(seconds = 8): Promise<string> {
  const uri = await recorder.startRecorder(); // begins writing to a file
  await new Promise((r) => setTimeout(r, seconds * 1000));
  await recorder.stopRecorder();
  return uri;
}

On iOS, recording typically produces an .m4a (AAC) file; on Android, an .m4a/.mp4 or .aac depending on the library. AudD accepts MP3, WAV, FLAC, M4A, OGG, AAC, WMA, and AIFF, so the default output of any of these libraries is a format AudD can read — you don’t need to transcode.

Step 2: Upload the clip to your backend

POST the recorded file to your backend as multipart form data. React Native’s fetch accepts a { uri, name, type } object as a FormData part and streams the file straight from disk — you don’t read it into a JS string.

async function identify(fileUri: string) {
  const form = new FormData();
  form.append("file", {
    uri: fileUri,
    name: "clip.m4a",
    type: "audio/m4a",
  } as any);

  const res = await fetch("https://your-backend.example.com/identify", {
    method: "POST",
    body: form,
    // Don't set Content-Type yourself — RN sets the multipart boundary.
  });

  if (!res.ok) throw new Error(`backend ${res.status}`);
  return res.json() as Promise<IdentifyResponse>;
}

type IdentifyResponse =
  | { match: false }
  | {
      match: true;
      artist: string;
      title: string;
      album: string | null;
      songLink: string | null;
    };

There is no api_token and no call to api.audd.io here — the app only knows about your backend.

Step 3: Recognize on the backend

The backend receives the upload and forwards the bytes to AudD with the official Node SDK. The SDK reads the token from the constructor (or the AUDD_API_TOKEN environment variable) and returns a result object on a match or null on a successful call that matched nothing.

import express from "express";
import multer from "multer";
import { AudD } from "@audd/sdk";

const upload = multer({ limits: { fileSize: 10 * 1024 * 1024 } }); // 10 MB cap
const audd = new AudD(process.env.AUDD_API_TOKEN!); // token lives here, only here
const app = express();

app.post("/identify", upload.single("file"), async (req, res) => {
  if (!req.file) return res.status(400).json({ error: "no_file" });

  const song = await audd.recognize(req.file.buffer, {
    returnMetadata: ["apple_music", "spotify"], // optional streaming links
  });

  if (!song) return res.json({ match: false });

  res.json({
    match: true,
    artist: song.artist,
    title: song.title,
    album: song.album,
    songLink: song.songLink, // universal lis.tn URL
  });
});

app.listen(3000);

audd.recognize accepts the uploaded bytes directly (req.file.buffer), so you never write the clip to disk. Set the multer limit to 10 MB to match the standard endpoint’s file-size cap and reject oversized uploads before they reach AudD.

Step 4: Show the result in the app

Render the answer and make the songLink tappable so the user can open the track. song_link is a universal URL on lis.tn that resolves to the user’s preferred music service.

import { Linking, Text, View } from "react-native";

function Result({ data }: { data: IdentifyResponse }) {
  if (!data.match) return <Text>No match — try a louder or longer clip.</Text>;
  return (
    <View>
      <Text style={{ fontWeight: "bold" }}>{data.title}</Text>
      <Text>{data.artist}</Text>
      {data.songLink && (
        <Text
          style={{ color: "#2563eb" }}
          onPress={() => Linking.openURL(data.songLink!)}
        >
          Open this track
        </Text>
      )}
    </View>
  );
}

What you get back

On a match, the backend returns the fields the app needs. The AudD result carries more — album, release_date, label, timecode, and per-provider streaming blocks when you pass returnMetadata — and you forward whichever your UI uses:

{
  "match": true,
  "artist": "Imagine Dragons",
  "title": "Warriors",
  "album": "Smoke + Mirrors (Deluxe)",
  "songLink": "https://lis.tn/Warriors"
}

A successful call that matched nothing returns { "match": false } — the SDK gives you null, which is not an error. Distinguish “we recognized nothing” (ask the user to try again) from “the request failed” (a real error, handled below).

timecode on a match is the position within the matched song where the user’s clip occurred — it is not an offset into the user’s recording.

Handling errors

The backend is where errors surface; the app only sees your HTTP status codes. The cases that matter:

  • No match — recognize returns null. Not an error. Return { match: false } and prompt the user to retry.
  • Authentication errors — a bad or missing token. This is a server misconfiguration, not a user problem. Fail loudly at startup, never per request, and never leak the token text in a response the app could log.
  • Quota errors — you’ve hit your request limit. Surface to ops; don’t retry in a tight loop. Return a 503 the app can show as “try again later”.
  • Invalid-audio errors — the clip wasn’t decodable audio. Usually a too-short or silent recording. Return a 422 and ask for a longer clip.
  • Connection errors — transient network trouble between your backend and AudD. Retry with backoff.
import { AudDError } from "@audd/sdk";

app.post("/identify", upload.single("file"), async (req, res) => {
  try {
    const song = await audd.recognize(req.file!.buffer);
    return res.json(song ? { match: true, /* ...fields */ } : { match: false });
  } catch (err) {
    if (err instanceof AudDError) {
      // log err.errorCode / err.requestId for support — never to the client
      return res.status(502).json({ error: "recognition_failed" });
    }
    return res.status(400).json({ error: "bad_request" });
  }
});

Going further

  • Trim the clip before upload. A shorter clip uploads faster on a phone network. 5–15 seconds is enough for the standard endpoint.
  • Authenticate the app→backend hop. Your backend now holds the AudD token, so protect /identify with your own app auth (a session token, an API key you issue per install) so only your app can spend your AudD quota.
  • Rate-limit per user on the backend so one client can’t drain your quota.
  • For a desktop build of the same record-upload-recognize flow, see the Electron integration.

Related

Reading this as an AI agent? The raw Markdown is at integrations/react-native.md, and the full index is /resources/llms.txt.