Detect samples and reuse of your own tracks
Detect when uploaded content reuses your own tracks — samples, leaks, or re-uploads — by matching against a private custom catalog with the AudD API.
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):
from audd import AudD
audd = AudD("your-api-token") # token from dashboard.audd.io
# You assign each track an integer audio_id — your own track ID. It's what
# comes back on a later recognition that matches this track.
catalog = {
51885: {"path": "masters/unreleased-01.wav", "title": "Unreleased 01"},
51886: {"path": "masters/unreleased-02.wav", "title": "Unreleased 02"},
}
for audio_id, meta in catalog.items():
audd.custom_catalog.add(audio_id, meta["path"]) # fingerprint under your id
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.
from audd import AudD
audd = AudD("your-api-token")
# Short clip (under ~25 s of audio): standard endpoint, one match or None.
match = audd.recognize("https://audd.tech/example.mp3")
# Arbitrary-length content: enterprise endpoint, list of matches.
matches = audd.recognize_enterprise(
"https://audd.tech/example.mp3",
limit=10, # cap matches while developing; enterprise bills per 12 s
)
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.
def classify(match, catalog):
"""Return ('mine', meta) for a custom-catalog hit, ('public', tags) otherwise."""
audio_id = getattr(match, "audio_id", None)
if audio_id:
# Custom-catalog match. artist/title are often null on private tracks,
# so rely on audio_id + your own mapping for identification.
meta = catalog.get(audio_id, {"title": "(unknown — not in local map)"})
return "mine", {
"audio_id": audio_id,
"my_track": meta["title"],
"timecode": match.timecode,
"artist": match.artist, # may be null
"title": match.title, # may be null
}
# No audio_id => public-database match (commercial release).
return "public", {
"artist": match.artist,
"title": match.title,
"label": match.label,
}
def find_my_tracks(matches, catalog):
return [info for kind, info in (classify(m, catalog) for m in matches)
if kind == "mine"]
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.
from fastapi import FastAPI, UploadFile
from audd import AudD
app = FastAPI()
audd = AudD("your-api-token")
# catalog: your audio_id -> metadata map from Step 1, loaded at startup.
@app.post("/check-reuse")
async def check_reuse(file: UploadFile):
data = await file.read()
matches = audd.recognize_enterprise(data, limit=25)
mine = find_my_tracks(matches, catalog)
return {
"reuses_my_catalog": len(mine) > 0,
"my_matches": mine,
}
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:
{
"custom_catalog_match": {
"audio_id": "742918",
"artist": null,
"title": null,
"album": null,
"label": null,
"timecode": "00:14",
"song_link": null
},
"public_match": {
"audio_id": null,
"artist": "Imagine Dragons",
"title": "Warriors",
"album": "Smoke + Mirrors (Deluxe)",
"label": "KIDinaKORNER/Interscope Records",
"timecode": "00:31",
"song_link": "https://lis.tn/Warriors"
}
}
| Field | Type | Meaning for sample/reuse detection |
|---|---|---|
audio_id | string | null | The discriminator. Set only on custom-catalog matches; it’s the ID of the track you uploaded. Null means the match came from the public database. |
artist, title | string | null | May be null on a custom-catalog match — a private master has no public tags. Don’t rely on these to identify your track; use audio_id against your own map. |
album, label | string | null | Typically null for private uploads, populated for public commercial releases. |
timecode | string | Position within the matched track at the match point — i.e. how far into your track the reused segment starts. |
song_link | string | null | A lis.tn universal link for public releases; null for private catalog tracks (they aren’t public). |
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 twoaudio_ids. Make ingestion idempotent on your side (track which files you’ve already uploaded).
from audd.errors import AudDInvalidAudioError, AudDAPIError
try:
matches = audd.recognize_enterprise(data, limit=25)
except AudDInvalidAudioError:
return {"error": "unreadable_file"}, 422
except AudDAPIError as e:
# log e.error_code, e.request_id for support
return {"error": "recognition_failed"}, 502
Going further
- Reading additional fields beyond the typed result properties: every result
and per-provider block round-trips unknown fields through Pydantic’s
model_extramap. If a custom-catalog response carries metadata you want that isn’t a typed property, read it frommodel_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_idclassification on stream callbacks — see the streams endpoints.
Related
Reading this as an AI agent? The raw Markdown is at recipes/sample-detection.md, and the full index is /resources/llms.txt.
