# audd-c


<div className="sdk-page-header-links">
  <a className="button button--primary" href="https://github.com/AudDMusic/audd-c" target="_blank" rel="noopener">View on GitHub</a>
  <a className="button button--secondary" href="https://github.com/AudDMusic/audd-c/releases" target="_blank" rel="noopener">Releases</a>
</div>

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

```cmake
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:

```sh
cc myapp.c -laudd -lcurl -lpthread -o myapp
```

```c
#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;
}
```

:::warning 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](https://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`.

```c
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:

```c
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:

```c
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)`.

```c
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](https://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.

```c
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.

:::warning 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.

```c
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:

```c
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](https://github.com/AudDMusic/audd-c#what-you-get-back).

## 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:

- **Callbacks** — AudD POSTs each match (and each lifecycle
  notification) to a URL you control. See
  [Receive callbacks from a stream](#receive-callbacks-from-a-stream).
- **Longpoll** — your code polls AudD's `/longpoll/` endpoint
  synchronously over HTTP. See
  [Poll for stream events (longpoll)](#poll-for-stream-events-longpoll).

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

```c
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

```c
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:

```c
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

```c
#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](https://github.com/AudDMusic/audd-c#streams).

## 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.

:::warning 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:

```c
audd_streams_set_callback_url(client, "https://audd.tech/empty/", NULL);
```

:::

```c
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.

```c
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`:

```c
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`).

:::warning

The custom-catalog endpoint requires special access. Contact
[api@audd.io](mailto:api@audd.io) to enable it. Calls without access
return `AUDD_ERR_CUSTOM_CATALOG_ACCESS`.

:::

```c
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

```c
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`:

```c
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:

```c
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

```c
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:

```c
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](#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.

```c
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](https://dashboard.audd.io) · [HTTP API reference](/) · [Other SDKs](/sdks)
