Skip to main content

audd-c

Open .md

Recognize music in audio clips, long broadcasts, and live streams from C.

include(FetchContent)
FetchContent_Declare(audd
GIT_REPOSITORY https://github.com/AudDMusic/audd-c.git
GIT_TAG v1.5.7
)
FetchContent_MakeAvailable(audd)
target_link_libraries(your_app PRIVATE audd)

Or, with the library installed system-wide:

cc myapp.c -laudd -lcurl -lpthread -o myapp
#include <audd.h>
#include <stdio.h>

int main(void)
{
/* 10 reqs/day; get a real token at https://dashboard.audd.io */
audd_client_t *client = audd_client_new("test", NULL);

audd_recognition_t *r = NULL;
audd_error_t err = audd_recognize(client, "https://audd.tech/example.mp3",
NULL, &r);
if (err == AUDD_OK && r != NULL) {
printf("%s — %s\n",
audd_recognition_get_artist(r),
audd_recognition_get_title(r));
}

audd_recognition_free(r);
audd_client_free(client);
return 0;
}
Every _new has a matching _free

C has no destructors. Every handle the SDK hands back — audd_client_t, audd_recognition_t, audd_enterprise_result_t, audd_stream_list_t, audd_stream_callback_match_t, audd_stream_callback_notification_t, audd_lyrics_list_t, plus heap strings from audd_streams_get_callback_url and audd_advanced_raw_request — must be released with the matching _free call. All _free functions are NULL-safe.

Authentication

Get your API token at dashboard.audd.io.

audd_client_new(token, options) resolves the API token in this order:

  1. The api_token argument, when non-NULL and non-empty.
  2. The AUDD_API_TOKEN environment variable.
  3. Otherwise the client is still constructed; the first network call returns AUDD_ERR_AUTHENTICATION.
audd_client_t *client = audd_client_new("your-api-token", NULL); /* explicit */
audd_client_t *client = audd_client_new(NULL, NULL); /* AUDD_API_TOKEN */

To fail fast at construction time, use audd_client_new_strict — it writes AUDD_ERR_AUTHENTICATION into its out-param and returns NULL when no token is configured:

audd_error_t err = AUDD_OK;
audd_client_t *client = audd_client_new_strict(NULL, NULL, &err);
if (client == NULL) {
fprintf(stderr, "audd: %s\n", audd_error_string(err));
return 1;
}

The public "test" token is capped at 10 requests/day on the standard endpoint only — fine for hello-worlds, not enough for production.

For long-running services that pull tokens from a secret manager, rotate without rebuilding the client:

audd_client_set_api_token(client, "new-token");

The swap is atomic with respect to in-flight requests: in-flight calls finish under the previous token; subsequent calls use the new value. audd_client_api_token(client) returns a borrowed pointer to the in-effect token (NULL when none set).

Recognize a clip

audd_recognize takes a 5–25-second clip — URL or local file path — and disambiguates three outcomes via the return code plus the out-param:

  • AUDD_OK + *out != NULL — match found.
  • AUDD_OK + *out == NULL — clip processed, no match.
  • anything else — error; check audd_last_error_message(client).
audd_recognition_t *r = NULL;
audd_error_t e = audd_recognize(client, "https://audd.tech/example.mp3", NULL, &r);
/* or: audd_recognize(client, "/path/to/clip.mp3", NULL, &r); */

if (e == AUDD_OK && r != NULL) {
printf("%s — %s @ %s\n",
audd_recognition_get_artist(r),
audd_recognition_get_title(r),
audd_recognition_get_timecode(r));
printf("song page: %s\n", audd_recognition_get_song_link(r));

const char *cover = audd_recognition_thumbnail_url(r);
if (cover) printf("cover art: %s\n", cover);
}
audd_recognition_free(r); /* NULL-safe */

For in-memory data, use audd_recognize_bytes(client, buf, len, opts, &r) — the buffer is copied, so it's safe to free after the call.

A recognition exposes artist, title, album, release_date, label, timecode, song_link, isrc / upc (enterprise plans), plus audd_recognition_thumbnail_url, audd_recognition_streaming_url(r, provider), audd_recognition_preview_url, and audd_recognition_extra(r, "key") for fields not yet wrapped. Strings are owned by the recognition handle — strdup them if they need to outlive audd_recognition_free. Full reference: github.com/AudDMusic/audd-c#what-you-get-back.

Process a long audio file

For broadcasts, podcasts, and full sets longer than 25 seconds, use audd_recognize_enterprise. AudD chunks the file server-side and returns every match.

audd_enterprise_options_t opts = audd_enterprise_options_default();
opts.limit = 10; /* ALWAYS set this in development */

audd_enterprise_result_t *result = NULL;
audd_error_t e = audd_recognize_enterprise(client, "/path/to/broadcast.mp3",
&opts, &result);
if (e == AUDD_OK) {
for (size_t i = 0; i < audd_enterprise_result_count(result); ++i) {
const audd_enterprise_match_t *m = audd_enterprise_result_at(result, i);
printf("%s %s — %s (score=%d)\n",
audd_enterprise_match_get_timecode(m),
audd_enterprise_match_get_artist(m),
audd_enterprise_match_get_title(m),
audd_enterprise_match_get_score(m));
}
}
audd_enterprise_result_free(result);

audd_enterprise_options_default() sets every numeric field to -1 ("not set"). Other knobs: skip, every, skip_first_seconds, use_timecode, accurate_offsets. audd_recognize_enterprise_bytes accepts an in-memory buffer.

Enterprise calls bill per 12 seconds of audio processed

Always set opts.limit during development. The unbounded default can ingest many hours of audio on a single call.

Each match exposes score, timecode, artist, title, album, release_date, label, song_link, isrc / upc (enterprise plans), start_offset, end_offset, _thumbnail_url, _streaming_url(provider). Match pointers are borrowed — do not free them individually; free the parent result.

Get streaming-service metadata

Pass return_metadata to populate provider sub-objects on the result. Without it, those blocks are NULL.

const char *want[] = { "apple_music", "spotify", NULL };
audd_recognize_options_t opts = { .return_metadata = want };

audd_recognition_t *r = NULL;
audd_error_t e = audd_recognize(client, "https://audd.tech/example.mp3", &opts, &r);

if (e == AUDD_OK && r != NULL) {
const audd_apple_music_t *am = audd_recognition_apple_music(r);
if (am) printf("Apple Music: %s\n", audd_apple_music_get_url(am));

const audd_spotify_t *sp = audd_recognition_spotify(r);
if (sp) printf("Spotify URI: %s\n", audd_spotify_get_uri(sp));

/* Or resolve any provider — direct URL when the metadata block is set,
else the lis.tn redirect when song_link is on lis.tn. */
printf("%s\n", audd_recognition_streaming_url(r, AUDD_PROVIDER_SPOTIFY));
printf("preview: %s\n", audd_recognition_preview_url(r));

audd_recognition_free(r);
}

Valid providers (the strings in return_metadata): apple_music, spotify, deezer, napster, musicbrainz. Each adds latency. The audd_provider_t enum (used by audd_recognition_streaming_url) covers SPOTIFY, APPLE_MUSIC, DEEZER, NAPSTER, YOUTUBE.

The market option (ISO 3166 region code) controls the Apple Music storefront:

audd_recognize_options_t opts = { .return_metadata = want, .market = "GB" };

For each provider sub-object's full field shape, see the upstream API reference and the GitHub README.

Monitor a live audio stream

A stream in AudD is a long-running audio source — typically a radio URL, HLS, Icecast, or SHOUTcast endpoint — that AudD ingests continuously and recognizes against. Each stream is identified by a radio_id you pick.

There are two ways to consume recognition events:

Both modes need a callback URL configured on the account; otherwise events are never delivered. If your consumers will only longpoll — e.g., browser widgets, mobile apps, or scripts that can't expose a public URL — set the account's callback URL to https://audd.tech/empty/ in advance; it's a placeholder URL that discards POSTs. Consumers themselves don't need to configure anything.

Receive callbacks from a stream

Setup is three steps: configure the callback URL, register the stream, and parse incoming POSTs.

1. Configure the account callback URL

const char *want[] = { "apple_music", "spotify", NULL };
audd_streams_set_callback_url_options_t copts = { .return_metadata = want };

audd_streams_set_callback_url(client,
"https://your-app.example.com/audd-callback", &copts);

return_metadata is serialized into the URL as ?return=apple_music,spotify (the server reads it from the URL string).

2. Register the stream

int radio_id = 1;          /* any integer you choose — your handle for this stream */

audd_add_stream_request_t req = {
.url = "https://radio.example.com/stream.mp3",
.radio_id = radio_id,
.callbacks = NULL, /* "before" → callback at song start */
};
audd_streams_add(client, &req);

Other stream-management calls:

int radio_id = 1;          /* your stream's handle from when you called audd_streams_add */

audd_streams_set_url(client, radio_id, "https://radio.example.com/new.mp3");
audd_streams_delete(client, radio_id);

audd_stream_list_t *list = NULL;
audd_streams_list(client, &list);
for (size_t i = 0; i < audd_stream_list_count(list); ++i) {
const audd_stream_t *s = audd_stream_list_at(list, i);
printf("%d %s running=%d\n",
audd_stream_get_radio_id(s),
audd_stream_get_url(s),
audd_stream_get_running(s));
}
audd_stream_list_free(list);

3. Parse incoming POST bodies

audd_parse_callback(body, size, &match, &notif, &err) is transport- agnostic and pure: feed it raw bytes from any HTTP framework. Exactly one of match / notif is non-NULL on success.

libmicrohttpd — canonical recipe

#include <audd.h>
#include <microhttpd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct ctx { char *body; size_t len; };

static void dispatch(const char *body, size_t len)
{
audd_stream_callback_match_t *m = NULL;
audd_stream_callback_notification_t *n = NULL;
char *err = NULL;
if (audd_parse_callback(body, len, &m, &n, &err) != AUDD_OK) {
audd_string_free(err); return;
}
if (m) {
const audd_stream_callback_song_t *s = audd_stream_callback_match_get_song(m);
printf("matched on radio_id=%lld: %s — %s\n",
(long long)audd_stream_callback_match_get_radio_id(m),
audd_stream_callback_song_get_artist(s),
audd_stream_callback_song_get_title(s));
audd_stream_callback_match_free(m);
} else if (n) {
printf("notification #%d: %s\n",
audd_stream_callback_notification_get_code(n),
audd_stream_callback_notification_get_message(n));
audd_stream_callback_notification_free(n);
}
}

static enum MHD_Result handler(void *cls, struct MHD_Connection *conn,
const char *url, const char *method, const char *version,
const char *upload_data, size_t *upload_data_size, void **req_cls)
{
struct ctx *c = *req_cls;
if (c == NULL) { *req_cls = calloc(1, sizeof(*c)); return MHD_YES; }
if (*upload_data_size != 0) { /* buffer */
c->body = realloc(c->body, c->len + *upload_data_size);
memcpy(c->body + c->len, upload_data, *upload_data_size);
c->len += *upload_data_size; *upload_data_size = 0;
return MHD_YES;
}
dispatch(c->body, c->len); /* complete */
struct MHD_Response *r = MHD_create_response_from_buffer(
2, "ok", MHD_RESPMEM_PERSISTENT);
enum MHD_Result rv = MHD_queue_response(conn, MHD_HTTP_OK, r);
MHD_destroy_response(r);
free(c->body); free(c); *req_cls = NULL;
return rv;
}

int main(void)
{
struct MHD_Daemon *d = MHD_start_daemon(MHD_USE_INTERNAL_POLLING_THREAD,
8080, NULL, NULL, &handler, NULL, MHD_OPTION_END);
getchar(); MHD_stop_daemon(d);
}

A audd_stream_callback_match_t carries radio_id, timestamp, play_length, the top song, alternative candidates (rare; may have different artist/title from the top match), and the full unparsed body. A audd_stream_callback_song_t mirrors a recognition's metadata. A audd_stream_callback_notification_t carries radio_id, stream_running (-1/0/1), code, message, and time.

Other HTTP servers (mongoose, civetweb, your own) — see the GitHub README.

Poll for stream events (longpoll)

For consumers that can't expose a public callback URL — embedded devices, scripts behind NAT, native clients — poll /longpoll/ instead. The C consumer is built around three callback functions plus a blocking audd_longpoll_run_by_radio_id. Pick any integer radio_id as your handle for the stream.

Longpoll requires a configured callback URL

Unless a callback URL is configured for the account, longpoll requests just return keepalives — no events ever fire. The SDK preflights this on your first run and surfaces AUDD_ERR_INVALID_REQUEST with guidance if it's missing; set opts.skip_callback_check = 1 once you've configured one.

If you only want longpoll and have no real receiver, set https://audd.tech/empty/ — it's a placeholder URL that discards incoming POSTs:

audd_streams_set_callback_url(client, "https://audd.tech/empty/", NULL);
static audd_longpoll_t *g_handle;

static void on_match(const audd_stream_callback_match_t *m, void *ud)
{
const audd_stream_callback_song_t *s = audd_stream_callback_match_get_song(m);
printf("matched: %s — %s\n",
audd_stream_callback_song_get_artist(s),
audd_stream_callback_song_get_title(s));
}

static void on_notification(const audd_stream_callback_notification_t *n, void *ud)
{
printf("notification: %s\n",
audd_stream_callback_notification_get_message(n));
}

static void on_error(audd_error_t err, const char *msg, void *ud)
{
fprintf(stderr, "terminal: %s — %s\n", audd_error_string(err), msg);
}

static void on_sigint(int sig) { audd_longpoll_close(g_handle); }

int main(void)
{
audd_client_t *client = audd_client_new(NULL, NULL); /* reads AUDD_API_TOKEN */
if (!client) return 1;

int radio_id = 1; /* any integer you choose — your handle for this stream */

audd_longpoll_callbacks_t cb = {
.on_match = on_match,
.on_notification = on_notification,
.on_error = on_error,
.user_data = NULL,
};
signal(SIGINT, on_sigint);

audd_error_t rc = audd_longpoll_run_by_radio_id(
client, radio_id, NULL, &cb, &g_handle);
if (rc != AUDD_OK) {
fprintf(stderr, "longpoll failed: %s\n", audd_error_string(rc));
}
/* Blocks until on_error fires or audd_longpoll_close is called.
The handle is freed automatically on return — don't use it after. */

audd_client_free(client);
return 0;
}

audd_longpoll_run_by_radio_id blocks the calling thread. audd_longpoll_close is idempotent and safe to call from any thread (including a signal handler, as above) — it's the only way to stop a longpoll without a terminal error.

The handle written to out_handle is owned by the running call: it's populated before the first poll request so external code can stash it for audd_longpoll_close, and it becomes invalid the moment audd_longpoll_run_by_radio_id returns. Don't call audd_longpoll_close on it after that point.

audd_longpoll_options_t (passed in place of NULL above) exposes since_time (Unix timestamp to resume from; 0 = "from now"), timeout (server-side wait; default 50 seconds), and skip_callback_check (set to 1 to bypass the preflight described above). audd_longpoll_options_default() returns a zero-initialized struct you can mutate.

Tokenless consumers

When the polling client must not hold the api_token — a browser widget, a mobile app, a shipped embedded device — keep the token on a trusted server and ship an opaque per-stream identifier to the client instead.

On the server, call audd_streams_derive_longpoll_category(client, radio_id, out_category) (out_category must point to at least 10 bytes). It runs without any network I/O and writes a short opaque string. Send only that string to the consumer.

char category[10];
audd_error_t rc = audd_streams_derive_longpoll_category(client, radio_id, category);
if (rc != AUDD_OK) { /* handle */ }
/* ship `category` to the tokenless consumer over your own channel */

On the consumer, build a tokenless client and pass the received string to audd_longpoll_run:

audd_client_t *consumer = audd_client_new(NULL, NULL); /* no api_token needed */
audd_longpoll_run(consumer, category, NULL, &cb, &g_handle);

audd_derive_longpoll_category(api_token, radio_id, out_category) is the same function without the client wrapper, for code paths that hold the token directly.

Add a song to your custom catalog

The custom-catalog endpoint adds songs to a private fingerprint database for your account. After upload, future audd_recognize calls on the same account can match against your tracks. It is not how you submit audio for music recognition — for that, use audd_recognize (or audd_recognize_enterprise).

danger

The custom-catalog endpoint requires special access. Contact api@audd.io to enable it. Calls without access return AUDD_ERR_CUSTOM_CATALOG_ACCESS.

int audio_id = 1; /* any integer you choose — your reference to the song */

audd_custom_catalog_add(client, audio_id, "https://my.cdn/song.mp3");

/* Or from in-memory bytes: */
audd_custom_catalog_add_bytes(client, audio_id, buf, buf_len);

The SDK exposes only add — there is no public list or delete. Track audio_id ↔ song mappings yourself. Re-using an audio_id re-fingerprints that slot.

Handle errors

C has no exceptions. Every fallible call returns an audd_error_t sentinel that classifies the failure into a category (AUDD_ERR_QUOTA, AUDD_ERR_INVALID_AUDIO, AUDD_ERR_CONNECTION, …). For the human- readable detail and the AudD numeric error code, query the client.

Idiomatic error handling

audd_recognition_t *r = NULL;
audd_error_t e = audd_recognize(client, "/path/to/clip.mp3", NULL, &r);

switch (e) {
case AUDD_OK:
/* r is NULL on no-match, non-NULL on match */
break;
case AUDD_ERR_AUTHENTICATION:
fprintf(stderr, "check your token: [#%d] %s\n",
audd_last_error_code(client),
audd_last_error_message(client));
return 1;
case AUDD_ERR_QUOTA:
case AUDD_ERR_RATE_LIMIT:
fprintf(stderr, "throttled: %s\n", audd_last_error_message(client));
break;
case AUDD_ERR_INVALID_AUDIO:
fprintf(stderr, "audio rejected: %s\n", audd_last_error_message(client));
break;
case AUDD_ERR_CONNECTION:
fprintf(stderr, "network: %s\n", audd_last_error_message(client));
break;
default:
fprintf(stderr, "AudD #%d: %s (request_id=%s)\n",
audd_last_error_code(client),
audd_last_error_message(client),
audd_last_request_id(client));
}

audd_last_error_message, audd_last_error_code, and audd_last_request_id always return non-NULL strings (empty when there's nothing to report). They reflect the most recent failed call on client; check them right after a non-AUDD_OK return, before issuing another call.

audd_error_string(e) returns a stable English label for the sentinel itself (e.g. "authentication failed", "invalid audio") without querying a client — useful in error paths that don't have one yet.

For some codes — 19 (request_blocked, abuse, test_request_scope), 31337 (ip_ban), 903 (token_disabled) — the server populates branded human-readable text alongside the error. The SDK surfaces it via audd_last_error_message rather than exposing it as a fake recognition match.

Retry behavior

Recognition calls (audd_recognize, audd_recognize_enterprise) retry only on pre-upload network errors — DNS, connect, TLS — to avoid double-billing for in-progress uploads. Read-class calls (audd_streams_list, audd_streams_get_callback_url) retry on any network error plus 5xx and 408/429. Mutating calls (audd_streams_add, audd_streams_delete, audd_streams_set_*, audd_custom_catalog_*) retry only on pre-upload errors; 5xx is surfaced because the side effect may have already happened.

Defaults: max_attempts=3, backoff_ms=500 (geometric, doubles each attempt). Override via audd_options_t:

audd_options_t opts = audd_options_default();
opts.max_attempts = 5;
opts.backoff_ms = 1000;

audd_client_t *client = audd_client_new("token", &opts);

Set max_attempts = 1 to disable retries.

Configuration

Timeouts

audd_options_t carries two timeouts in seconds — a short one for the standard endpoint (and stream/catalog/advanced calls), a long one for enterprise:

audd_options_t opts = audd_options_default();
opts.standard_timeout_seconds = 30; /* default 60 */
opts.enterprise_timeout_seconds = 7200; /* default 3600 (1h) — bump for very long files */

audd_client_t *client = audd_client_new("token", &opts);

TLS and User-Agent

audd_options_t opts = audd_options_default();
opts.ca_bundle_path = "/etc/ssl/internal-ca.pem"; /* corp roots */
opts.user_agent_suffix = "myapp/2.1"; /* appended to "audd-c/<ver>" */

Both pointers are stored by reference (not copied) — keep them alive for the client's lifetime.

Custom allocator

For arena, tracking, or pool allocators, plug in your own once at process start, before any other audd_* call:

audd_allocator_t a = { my_malloc, my_free, my_realloc };
audd_set_allocator(&a);

Pass NULL to restore libc defaults. audd_set_allocator is not thread-safe.

Concurrency and lifecycle

A single audd_client_t * is safe to share across threads. A running audd_recognize can be cancelled from another thread by audd_client_free (terminal); a running longpoll, by audd_longpoll_close. audd_set_allocator is the one global mutator that is NOT thread-safe — call it once at process start.

Token rotation

audd_client_set_api_token(client, "new-token") — see Authentication.

Calling undocumented endpoints

For AudD endpoints not yet wrapped by typed functions on this SDK, hit them by name through audd_advanced_raw_request. It returns the raw JSON body as a heap string (free with audd_string_free), runs through the same retry policy, and surfaces transport / parsing failures through the usual sentinel + audd_last_error_message channel.

const char *params[] = { "audd_id", "12345", NULL };
char *body = NULL;
audd_advanced_raw_request(client, "getLinks", params, &body);
/* body is the raw JSON response. Parse with whatever you like. */
audd_string_free(body);

Typed wrappers ship as features stabilize; until then, this is the supported path for anything beta or one-off.


dashboard.audd.io · HTTP API reference · Other SDKs