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.
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.
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:
- keeping the AudD token private;
- rate limiting and abuse protection;
- audio validation and upload limits;
- logging and recognition analytics;
- normalizing metadata into your own schema;
- switching to another recognition provider later.
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
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,
}
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:
- Standard Recognition: short clips and normal song-identification requests;
- Audio Streams: continuous radio/live-stream monitoring with results delivered in real time;
- Enterprise: large audio or video files that may contain multiple songs.
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
- keep the provider token on the server;
- set upload size and request time limits;
- rate-limit recognition per user/device/account;
- do not persist raw audio longer than necessary;
- log latency and match/no-match rates, not secrets;
- normalize the provider response into your own model;
- treat optional metadata and provider IDs as nullable;
- test with noisy, quiet and short real-world recordings;
- handle provider outages without crashing the client flow;
- review current AudD pricing and limits before scaling traffic.
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
- AudD Music Recognition API documentation
- AudD Python SDK documentation
- AudD Audio Streams documentation
- AudD Enterprise endpoint documentation
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.