Official Python SDK for the AudD music recognition API.
Project description
audd-python
Official Python SDK for music recognition API: identify music from a short audio clip, a long audio file, or a live stream.
The API itself is so simple that it can easily be used even without an SDK: docs.audd.io.
Quickstart
pip install audd
Get your API token at dashboard.audd.io.
Recognize from a URL:
from audd import AudD
audd = AudD("your-api-token")
result = audd.recognize("https://audd.tech/example.mp3")
if result:
print(f"{result.artist} — {result.title}")
Recognize from a local file:
from audd import AudD
audd = AudD("your-api-token")
result = audd.recognize("/path/to/clip.mp3")
if result:
print(f"{result.artist} — {result.title}")
recognize() accepts a URL, a filesystem path, a file-like object opened in binary mode, or bytes — it auto-detects. It returns a RecognitionResult on a match, or None when the clip isn't recognized.
For files longer than 25 seconds (broadcasts, podcasts, full DJ sets), use recognize_enterprise(source, limit=...) — it returns a list[EnterpriseMatch], one per song detected across the file.
Authentication
Pass the token positionally:
audd = AudD("your-token")
Or omit it and set AUDD_API_TOKEN in the environment — the SDK reads it on construction:
import os
os.environ["AUDD_API_TOKEN"] = "your-token"
audd = AudD()
For long-running services that rotate tokens (e.g., from a secret manager), call audd.set_api_token(new_token). In-flight requests finish on the previous token; subsequent requests use the new one.
What you get back
By default recognize() returns the core tags plus AudD's universal song link — no metadata-block opt-in needed:
from audd import AudD
audd = AudD()
result = audd.recognize("https://audd.tech/example.mp3")
if result is None:
raise SystemExit("no match")
# Core tags
print(result.artist, "—", result.title)
print(result.album, result.release_date, result.label)
# AudD's universal song page (works in any browser, links into all providers)
print(result.song_link)
# Helpers — driven off song_link, work without any return_ opt-in
print(result.thumbnail_url) # cover-art URL, or None
print(result.streaming_url("spotify")) # direct or lis.tn redirect, or None
print(result.streaming_urls()) # {"spotify": "...", "deezer": "...", ...}
If you need provider-specific metadata blocks, opt in per call. Request only what you need — each provider you ask for adds latency:
result = audd.recognize(
"https://audd.tech/example.mp3",
return_=["apple_music", "spotify"],
)
print(result.apple_music.url) # direct Apple Music link
print(result.spotify.uri) # spotify:track:...
print(result.spotify.preview_url) # 30-second preview (only available via metadata block)
print(result.preview_url()) # first preview across requested providers, or None
Valid return_ values: apple_music, spotify, deezer, napster, musicbrainz. Attributes are None when not requested.
EnterpriseMatch (returned by recognize_enterprise) carries the same core tags plus score, start_offset, end_offset, isrc, upc. Access to isrc, upc, and score requires a Startup plan or higher — contact us for enterprise features.
For ad-hoc inspection during development, result.pretty_print() dumps the full state — typed fields plus everything in model_extra — as indented JSON.
Reading additional metadata
The typed models cover what AudD documents. To read undocumented or beta fields the server returns, go through model_extra:
result = audd.recognize("https://example.mp3", return_=["apple_music"])
# Top-level extras
genre = result.model_extra.get("genre")
# Nested extras inside a typed metadata block
artwork = result.apple_music.model_extra.get("artwork")
This is the supported API for fields outside the typed surface. Beta features and per-account custom fields show up here.
Async
Same surface, with await:
import asyncio
from audd import AsyncAudD
async def main():
async with AsyncAudD() as audd:
result = await audd.recognize("https://audd.tech/example.mp3")
print(result)
asyncio.run(main())
AsyncAudD exposes the same recognize, recognize_enterprise, streams, custom_catalog, and advanced namespaces as AudD. Use async with (or await audd.aclose()) to release the underlying httpx.AsyncClient.
Errors
Every server-side error becomes a typed exception. The hierarchy lets you handle whole families with one except:
AudDError
├── AudDConnectionError # network / TLS / timeout
├── AudDSerializationError # malformed JSON
└── AudDAPIError # status=error from server
├── AudDAuthenticationError # 900 / 901 / 903
├── AudDQuotaError # 902
├── AudDSubscriptionError # 904 / 905
│ └── AudDCustomCatalogAccessError # 904 from custom_catalog
├── AudDInvalidRequestError # 50 / 51 / 600 / 601 / 602 / 700–702 / 906
├── AudDInvalidAudioError # 300 / 400 / 500
├── AudDStreamLimitError # 610
├── AudDRateLimitError # 611
├── AudDNotReleasedError # 907
├── AudDBlockedError # 19 / 31337
├── AudDNeedsUpdateError # 20
└── AudDServerError # 100 / 1000 / unknown
Idiomatic catch:
from audd import AudD, AudDAuthenticationError, AudDInvalidAudioError, AudDAPIError
try:
result = AudD().recognize("https://example.mp3")
except AudDAuthenticationError as e:
raise SystemExit(f"check your token: [#{e.error_code}] {e.message}")
except AudDInvalidAudioError as e:
print(f"audio rejected: {e.message}")
except AudDAPIError as e:
# Catch-all for anything the server reported
print(f"AudD #{e.error_code}: {e.message} (request_id={e.request_id})")
Every AudDAPIError carries error_code, message, http_status, request_id, requested_params, request_method, branded_message, and raw_response — enough to log a full incident or open a support ticket.
Configuration
import httpx
from audd import AudD
audd = AudD(
"your-token",
max_retries=3, # per-call retry budget
backoff_factor=0.5, # initial backoff seconds (jittered)
httpx_client=httpx.Client(proxy="http://corp-proxy:8080"),
on_event=lambda e: print(e),
)
Timeouts. The default httpx timeouts are 30s connect / 60s read for standard endpoints, and 30s connect / 1 hour read for the enterprise endpoint (which can legitimately process multi-hour files). Override per call with timeout= (seconds).
Retries. Calls are classified by cost and retried accordingly:
| Class | Endpoints | Retried on |
|---|---|---|
RECOGNITION |
recognize, recognize_enterprise, advanced.* |
network errors and 5xx before the upload reaches the server |
READ |
streams.list, streams.get_callback_url, longpoll |
network errors and 5xx |
MUTATING |
streams.set_callback_url, streams.add, streams.delete, custom_catalog.add |
network errors and 5xx (idempotent on the server) |
RECOGNITION will not double-bill your account: once the server has accepted bytes, a 5xx after that is surfaced rather than retried.
Custom HTTP client. Inject your own httpx.Client (sync) or httpx.AsyncClient (async) to add proxies, mTLS, custom transports, or shared connection pools. The SDK adds its User-Agent if you don't set one.
Inspection. Pass an on_event= callable to receive a frozen AudDEvent for every request / response / exception — useful for metrics, tracing, or dropping a request_id into your logs. Events never carry the api_token or request bytes; exceptions raised from the hook are swallowed so observability can't break the request path.
Concurrency. A single AudD (or AsyncAudD) instance is safe to share across threads, asyncio tasks, or worker processes — construct it once at startup and reuse it. The recommended pattern is one client per process.
Streams
Real-time recognition off radio streams, broadcast feeds, and any other long-running URL. Configure once, then either receive callbacks on your server or poll for events.
audd.streams.set_callback_url("https://your.server/audd-callback")
audd.streams.add("https://your.stream.url/listen.m3u8", radio_id=42)
for stream in audd.streams.list():
print(stream.radio_id, stream.url, stream.stream_running)
Inside your callback receiver, hand the framework request to the SDK — it reads the body and parses it into a typed match or notification:
# Flask, FastAPI, Django, aiohttp — all supported via duck-typing.
match, notif = audd.streams.handle_callback(request)
if match is not None:
print(match.song.artist, "—", match.song.title, "score=", match.song.score)
for alt in match.alternatives:
# Alternatives may have a different artist/title than the top match
# (variant catalog releases, near-duplicates).
print(" alt:", alt.artist, "—", alt.title)
elif notif is not None:
print("notification:", notif.notification_message)
handle_callback(request) reads + parses; on AsyncAudD it awaits the body read. If you already have the bytes (queue consumer, replay tool), call audd.streams.parse_callback(body) instead — it accepts a dict, bytes, or str.
Longpoll
If you can't expose a public callback URL, longpoll instead. AudD still requires a callback URL to be configured for the account (https://audd.tech/empty/ works as a no-op receiver), and the SDK preflights this for you — pass skip_callback_check=True to skip if you've already verified.
The poll handle exposes three iterators — matches, notifications, errors — populated by a background thread (or task, in async). Use it as a context manager for clean shutdown:
category = audd.streams.derive_longpoll_category(radio_id=42)
with audd.streams.longpoll(category, timeout=30) as poll:
for match in poll.matches:
print(match.song.artist, "—", match.song.title)
To consume matches, notifications, and errors concurrently, use AsyncAudD and asyncio.gather:
import asyncio
from audd import AsyncAudD
async def main():
async with AsyncAudD() as audd:
category = audd.streams.derive_longpoll_category(42)
poll = await audd.streams.longpoll(category, timeout=30)
async with poll:
async def consume_matches():
async for m in poll.matches:
print(m.song.artist, "—", m.song.title)
async def watch_errors():
async for err in poll.errors:
print("terminal:", err)
return
await asyncio.gather(consume_matches(), watch_errors())
asyncio.run(main())
derive_longpoll_category is a local computation: MD5(MD5(api_token) + radio_id)[:9]. The category alone is sufficient to subscribe — the api_token is never sent over the wire for longpolls.
Tokenless consumers
For browser widgets, embedded extensions, or any context where shipping the api_token would leak it: derive the category server-side, ship only the category to the consumer, and have the consumer use LongpollConsumer:
from audd import LongpollConsumer
# `category` was derived on your server and shared with this process.
with LongpollConsumer(category="abc123def") as consumer:
with consumer.iterate(timeout=30) as poll:
for match in poll.matches:
print(match.song.artist, "—", match.song.title)
AsyncLongpollConsumer is the async equivalent.
Custom catalog (advanced)
The custom-catalog endpoint is NOT how you submit audio for music recognition. For recognition, use
recognize()(orrecognize_enterprise()for files longer than 25 seconds). The custom-catalog endpoint adds songs to your private fingerprint database so futurerecognize()calls on your account can identify your own tracks. Requires special access — contact api@audd.io.
audd.custom_catalog.add(audio_id=42, source="https://my.song.mp3")
Spec contract
This SDK is built against the audd-openapi spec. Contract tests in tests/contract/ validate the parser against the canonical fixture set on every push, on a daily cron, and whenever the spec updates.
License
MIT — see LICENSE.
Support
- Documentation: https://docs.audd.io
- Tokens: https://dashboard.audd.io
- Issues: https://github.com/AudDMusic/audd-python/issues
- Email: api@audd.io
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file audd-1.5.5.tar.gz.
File metadata
- Download URL: audd-1.5.5.tar.gz
- Upload date:
- Size: 31.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
869e1196d7a6ccfd4704b651bdcb701a53e3c7fa70a82619a048dfec0dcfc0ee
|
|
| MD5 |
5aa8f93921aca00740120627d1ec3eb7
|
|
| BLAKE2b-256 |
42fa50f166095a960934ea36ae43352a6b440b1c8542c3a1e07f7cde5d870c96
|
Provenance
The following attestation bundles were made for audd-1.5.5.tar.gz:
Publisher:
release.yml on AudDMusic/audd-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
audd-1.5.5.tar.gz -
Subject digest:
869e1196d7a6ccfd4704b651bdcb701a53e3c7fa70a82619a048dfec0dcfc0ee - Sigstore transparency entry: 1455035921
- Sigstore integration time:
-
Permalink:
AudDMusic/audd-python@7e20de698193c86bf0e4b660e87fc4bb3675ffbf -
Branch / Tag:
refs/tags/v1.5.5 - Owner: https://github.com/AudDMusic
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7e20de698193c86bf0e4b660e87fc4bb3675ffbf -
Trigger Event:
push
-
Statement type:
File details
Details for the file audd-1.5.5-py3-none-any.whl.
File metadata
- Download URL: audd-1.5.5-py3-none-any.whl
- Upload date:
- Size: 36.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
15f7c561922751bf2bb91a8f9de0cf047b6ed1c727c6aab1ac4566d457763a80
|
|
| MD5 |
b5dd94c0b5e67ed91bc396cc9831dcf9
|
|
| BLAKE2b-256 |
7396006cf91b9735f2d5e52ae4f168be7b7c73fa70fa003e222091bc39f2bd6b
|
Provenance
The following attestation bundles were made for audd-1.5.5-py3-none-any.whl:
Publisher:
release.yml on AudDMusic/audd-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
audd-1.5.5-py3-none-any.whl -
Subject digest:
15f7c561922751bf2bb91a8f9de0cf047b6ed1c727c6aab1ac4566d457763a80 - Sigstore transparency entry: 1455036015
- Sigstore integration time:
-
Permalink:
AudDMusic/audd-python@7e20de698193c86bf0e4b660e87fc4bb3675ffbf -
Branch / Tag:
refs/tags/v1.5.5 - Owner: https://github.com/AudDMusic
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7e20de698193c86bf0e4b660e87fc4bb3675ffbf -
Trigger Event:
push
-
Statement type: