If you own a catalog — a label, a sample pack, unreleased masters, a production library — you often need the opposite of commercial-music detection. The question is “is my track in here”, not “is there any copyrighted music here”. This recipe uploads your own recordings to a private custom catalog on your account, then runs incoming content through recognition and identifies the matches that came from your catalog rather than the public database.
What you’ll build
A two-part workflow. First, a one-time (or ongoing) ingestion step: your tracks go into your account’s private fingerprint database via POST api.audd.io/upload/. Second, a recognition path that sends incoming content through the API and inspects each match to answer one question — did this match come from my private catalog, or from the public 160-million-song database?
The distinguishing signal is the audio_id field. A custom-catalog match carries an audio_id (the ID of the track you uploaded), and its artist and title may be null, because a private master isn’t a public release with public tags. A public-database match has artist/title populated and no audio_id. That single check is what separates “someone reused my track” from “someone used a commercial song” — and the latter is the copyright-scanner recipe, not this one.
Prerequisites
- An API token from dashboard.audd.io.
- Custom-catalog upload access. The upload endpoint requires special access on your account — email [email protected] to have it enabled. Recognition against your catalog works on your normal token once the tracks are ingested.
- Python 3.10+ with the official SDK: pip install audd
- The tracks you want to detect, as audio files (MP3, WAV, FLAC, M4A, OGG, AAC, WMA, or AIFF).
Walkthrough
Step 1: Ingest your tracks into the custom catalog
Uploading a track fingerprints it and stores it in your account’s private database. From then on, any recognition call on your token can match against it. This is a fingerprint store, not file storage — you’re teaching the API what your recordings sound like, not hosting the files.
Upload requires special access on your account (email [email protected] to enable it), but the call itself is simple: you send each track with an integer audio_id that you assign — your own track ID — and AudD fingerprints it under that ID. The ID is yours to choose; it’s what comes back on every later recognition that matches this track. The upload response itself carries no payload ({"status": "success", "result": null}), so there’s nothing to read back from it — you already hold the ID, because you set it.
custom_catalog.add(audio_id, source) takes the ID you assign and the track (a path, URL, or bytes):
The custom catalog is private to your account. Tracks you upload are only matchable from your own token. They are not added to the public 160-million-song database, and other accounts cannot recognize against them.
The custom catalog is private to your account. Tracks you upload are only matchable from your own token. They are not added to the public 160-million-song database, and other accounts cannot recognize against them.
Maintain your own mapping from audio_id to whatever your track means to you (release name, internal ID, rights holder). The API stores the fingerprint and the audio_id; the business meaning lives on your side.
Step 2: Recognize incoming content
Now run a piece of incoming content through recognition. A single recognition call checks the audio against both the public database and your private catalog at once — you don’t choose one or the other. What differs is how you read the result.
For short clips, use the standard endpoint. For full tracks, DJ sets, videos, or anything of arbitrary length, use the enterprise endpoint (it chunks server-side and returns one match per recognized segment). Always set limit on enterprise calls during development.
The standard call returns a single match or None on no match. The enterprise call returns a list (empty on no match). Neither raises on “nothing recognized” — an empty/None result is a clean verdict, distinct from an error.
Step 3: Classify each match — yours or public
This is the core of the recipe. For every match, check audio_id. If it’s set, the match came from your private catalog; look it up in your local map. If it’s absent, it’s a public-database match (commercial music), which this recipe ignores.
audio_id is the only reliable discriminator. Don’t key off artist/title being null — a public match can have sparse tags too. The presence of audio_id is what means “this is from your catalog”.
Step 4: Wire it into an upload handler
Putting it together: accept incoming content, recognize it, and report which of your tracks (if any) it reuses.
reuses_my_catalog: true with a populated my_matches list is your “this content contains one of my tracks” verdict — a sample, a leak of an unreleased master, or a straight re-upload, depending on what you put in the catalog and where the content came from.
What you get back
A custom-catalog match and a public match differ in exactly the fields you key on. Here are both, side by side, as they appear in the result:
On enterprise responses each match additionally carries start_offset and end_offset — the seconds into the incoming content where the match begins and ends. Use those to point at where in the upload your track was reused; use timecode to know which part of your track was lifted.
Handling errors
This workflow has two distinct failure surfaces — ingestion and recognition.
- Authentication errors (AudDAuthenticationError) — bad or missing token. Fail at startup.
- Subscription / access errors (AudDSubscriptionError) — the upload endpoint isn’t enabled on your token. Custom-catalog upload needs special access; email [email protected]. This surfaces on ingestion, not on recognition.
- Invalid-audio errors (AudDInvalidAudioError) — the file (whether a track you’re ingesting or content you’re scanning) wasn’t decodable audio/video. Treat as a user/input error, not a server error.
- Connection errors (AudDConnectionError) — transient. Retry with backoff. Do not blindly auto-retry an upload/ingest call — a retried ingest can fingerprint the same track twice and hand you two audio_ids. Make ingestion idempotent on your side (track which files you’ve already uploaded).
Going further
- Reading additional fields beyond the typed result properties: every result and per-provider block round-trips unknown fields through Pydantic’s model_extra map. If a custom-catalog response carries metadata you want that isn’t a typed property, read it from model_extra.
- To detect commercial music in user uploads (the inverse of this recipe — someone using a song you don’t own), see Build a copyright scanner for user-uploaded content.
- To turn a confirmed match into a filing for a takedown, see Generate DMCA takedown evidence.
- Monitoring live streams for reuse of your catalog (a Twitch or radio stream playing your tracks) uses the same audio_id classification on stream callbacks — see the streams endpoints.
- Build a copyright scanner for user-uploaded content
- Generate DMCA takedown evidence
- Python SDK docs
- API reference

