AudD API Tutorial: Recognize Songs with Python & FastAPI

This guide shows how to put AudD behind your own backend instead of exposing the provider token in a mobile or web client. We will recognize a song from an uploaded audio clip, request extra metadata, normalize the result and wrap the flow in FastAPI.

Published August 27, 2026 · Python · FastAPI · Music recognition APIs

AudD's standard recognition endpoint accepts a short audio file or a public URL and returns a JSON result containing fields such as artist, title, album, release date, label, timecode and a song link. You can also request additional metadata from providers such as Apple Music, Spotify, Deezer and MusicBrainz.

Architecture used in this guide: client records or selects audio → your FastAPI backend receives it → backend sends it to AudD → backend converts the provider response into your own stable JSON schema → client receives the normalized result.

1. Why put AudD behind FastAPI?

You can technically call a recognition API directly from a client, but doing that in a production app would expose your API token and couple your application to one provider's response format. A backend layer gives you better control over:

2. Get your AudD API token

Create an account in the AudD dashboard and obtain an API token. Store it as a server-side environment variable instead of writing it directly into source code.

export AUDD_API_TOKEN="your-token-here"

For local development you can also use a .env file, but make sure that file is excluded from Git.

3. Test recognition with curl first

Before adding FastAPI, verify that your token and sample audio work directly against AudD:

curl https://api.audd.io/ \
  -F api_token="$AUDD_API_TOKEN" \
  -F file=@sample.mp3 \
  -F return="apple_music,spotify"

You can also send a public audio URL instead of uploading the file:

curl https://api.audd.io/ \
  -F api_token="$AUDD_API_TOKEN" \
  -F url="https://example.com/sample.mp3" \
  -F return="apple_music,spotify"

AudD recommends the URL approach when the file is already publicly available because their server can fetch it directly. For audio recorded locally in a mobile app, multipart upload is usually the more natural backend workflow.

4. Install the FastAPI dependencies

pip install fastapi uvicorn httpx python-multipart

python-multipart is required for file uploads in FastAPI. httpx gives us an async HTTP client so the recognition call does not block the server worker.

5. Build a minimal recognition endpoint

import os

import httpx
from fastapi import FastAPI, File, HTTPException, UploadFile

app = FastAPI()

AUDD_API_TOKEN = os.environ["AUDD_API_TOKEN"]
AUDD_URL = "https://api.audd.io/"


@app.post("/api/recognize")
async def recognize_song(file: UploadFile = File(...)):
    audio = await file.read()

    if not audio:
        raise HTTPException(status_code=400, detail="Empty audio file")

    async with httpx.AsyncClient(timeout=20.0) as client:
        response = await client.post(
            AUDD_URL,
            data={
                "api_token": AUDD_API_TOKEN,
                "return": "apple_music,spotify",
            },
            files={
                "file": (
                    file.filename or "audio.mp3",
                    audio,
                    file.content_type or "application/octet-stream",
                )
            },
        )

    response.raise_for_status()
    payload = response.json()

    if payload.get("status") != "success":
        raise HTTPException(status_code=502, detail="Recognition provider failed")

    result = payload.get("result")

    if result is None:
        return {"matched": False, "song": None}

    return {
        "matched": True,
        "song": normalize_song(result),
    }


def normalize_song(result: dict) -> dict:
    spotify = result.get("spotify") or {}
    apple_music = result.get("apple_music") or {}

    return {
        "artist": result.get("artist"),
        "title": result.get("title"),
        "album": result.get("album"),
        "release_date": result.get("release_date"),
        "label": result.get("label"),
        "timecode": result.get("timecode"),
        "song_link": result.get("song_link"),
        "spotify_url": (spotify.get("external_urls") or {}).get("spotify"),
        "apple_music_url": apple_music.get("url"),
    }

Run the API:

uvicorn main:app --reload

Then send a local audio file to your own backend:

curl http://localhost:8000/api/recognize \
  -F file=@sample.mp3

6. Why normalize the response?

Your client should ideally not know that AudD is the provider. Instead of returning the raw provider object everywhere, convert it into your own application model.

{
  "matched": true,
  "song": {
    "artist": "Example Artist",
    "title": "Example Song",
    "album": "Example Album",
    "release_date": "2024-01-01",
    "label": "Example Label",
    "timecode": "00:14",
    "song_link": "https://...",
    "spotify_url": "https://open.spotify.com/...",
    "apple_music_url": "https://music.apple.com/..."
  }
}

That becomes especially useful if you later test ACRCloud, ShazamKit or another service. Your app can keep consuming the same internal model while only the backend adapter changes.

For a deeper provider comparison, see ShazamKit vs ACRCloud vs AudD.

7. Add file-size and content-type validation

The minimal endpoint works, but do not accept unlimited uploads in production. Set an explicit size limit and reject obviously unsupported input.

MAX_AUDIO_BYTES = 10 * 1024 * 1024
ALLOWED_TYPES = {
    "audio/mpeg",
    "audio/mp4",
    "audio/wav",
    "audio/x-wav",
    "audio/aac",
    "audio/ogg",
}


async def validate_audio(file: UploadFile) -> bytes:
    if file.content_type not in ALLOWED_TYPES:
        raise HTTPException(status_code=415, detail="Unsupported audio format")

    audio = await file.read(MAX_AUDIO_BYTES + 1)

    if len(audio) > MAX_AUDIO_BYTES:
        raise HTTPException(status_code=413, detail="Audio file is too large")

    if not audio:
        raise HTTPException(status_code=400, detail="Empty audio file")

    return audio
Important: MIME type from the client is only one validation signal. For higher-risk upload systems, also inspect the actual file format and never trust the original filename as a storage path.

8. Handle provider failures cleanly

Network timeouts, provider errors and invalid JSON should not leak confusing exceptions to the client. Wrap the provider call and return a stable application error.

async def call_audd(audio: bytes, filename: str, content_type: str) -> dict:
    try:
        async with httpx.AsyncClient(timeout=20.0) as client:
            response = await client.post(
                AUDD_URL,
                data={
                    "api_token": AUDD_API_TOKEN,
                    "return": "apple_music,spotify",
                },
                files={"file": (filename, audio, content_type)},
            )
            response.raise_for_status()
            return response.json()
    except httpx.TimeoutException as exc:
        raise HTTPException(status_code=504, detail="Music recognition timed out") from exc
    except httpx.HTTPError as exc:
        raise HTTPException(status_code=502, detail="Music recognition service unavailable") from exc

9. Recognize from a URL instead of an upload

If the audio is already stored at a public URL, you can avoid moving the bytes through your API server.

from pydantic import BaseModel, HttpUrl


class RecognitionUrlRequest(BaseModel):
    url: HttpUrl


@app.post("/api/recognize-url")
async def recognize_song_url(body: RecognitionUrlRequest):
    async with httpx.AsyncClient(timeout=20.0) as client:
        response = await client.post(
            AUDD_URL,
            data={
                "api_token": AUDD_API_TOKEN,
                "url": str(body.url),
                "return": "apple_music,spotify",
            },
        )

    response.raise_for_status()
    payload = response.json()
    result = payload.get("result")

    return {
        "matched": result is not None,
        "song": normalize_song(result) if result else None,
    }
SSRF warning: if your backend accepts arbitrary URLs, do not blindly let it fetch private network addresses or internal services. Validate allowed schemes and destinations, or use pre-signed URLs from storage you control.

10. What does the return parameter do?

The standard endpoint can request additional provider metadata. AudD currently documents provider identifiers including apple_music, spotify, deezer and musicbrainz. Request only what the product actually uses because extra provider lookups can add latency.

"return": "apple_music,spotify,deezer,musicbrainz"

If all you need is basic recognition, skip the extra metadata and keep the request simpler.

11. What about ISRC and UPC?

Do not assume every standard recognition response will include an ISRC or UPC. AudD's SDK documentation notes that ISRC/UPC fields are associated with enterprise-plan capabilities. If those identifiers are important to your product, confirm the exact account/endpoint behavior you are paying for before designing your database schema around them.

A robust schema usually treats external identifiers as optional:

class SongIdentity:
    title: str | None
    artist: str | None
    isrc: str | None
    upc: str | None
    spotify_id: str | None
    apple_music_id: str | None

12. Short clips, streams and long files are different products

AudD separates recognition workflows:

Do not implement continuous monitoring by repeatedly calling the short-clip endpoint unless that architecture is deliberately chosen after evaluating cost, latency and provider guidance.

13. Production checklist

Final architecture

Mobile / Web Client
        |
        | audio clip
        v
FastAPI Recognition Endpoint
        |
        | validated multipart request
        v
AudD Adapter
        |
        | provider JSON
        v
Normalization Layer
        |
        | stable application schema
        v
Client + Database + Analytics

This keeps the most volatile part of the system—the external recognition provider—behind a small adapter. That is much easier to maintain than letting provider-specific response fields spread through a mobile app and database.

Official references

Building music recognition into an app?

I work on React Native, Android and Python backend systems, including audio recognition and metadata pipelines. If you need help turning a prototype into a production flow, get in touch.