@audd/sdk 1.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AudD (https://audd.io)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,335 @@
1
+ # audd
2
+
3
+ [![CI](https://github.com/AudDMusic/audd-node/actions/workflows/ci.yml/badge.svg)](https://github.com/AudDMusic/audd-node/actions/workflows/ci.yml)
4
+ [![Contract](https://github.com/AudDMusic/audd-node/actions/workflows/contract.yml/badge.svg)](https://github.com/AudDMusic/audd-node/actions/workflows/contract.yml)
5
+ [![npm](https://img.shields.io/npm/v/@audd/sdk.svg)](https://www.npmjs.com/package/@audd/sdk)
6
+
7
+ Official TypeScript / Node.js SDK for the [AudD](https://audd.io) music
8
+ recognition API.
9
+
10
+ AudD identifies music from a short audio clip, a URL, a long file, or a
11
+ live stream. The HTTPS API is a plain form-POST — every endpoint is
12
+ documented at **[docs.audd.io](https://docs.audd.io)** and you can call
13
+ it from anywhere `fetch` works. This package adds typed result models
14
+ with helpers for cover art, streaming-provider URLs, and previews;
15
+ `AbortSignal`-aware async; ESM and CJS dual-build; and a separate
16
+ browser-safe entry point for tokenless longpoll widgets.
17
+
18
+ ## Quickstart
19
+
20
+ ```bash
21
+ npm install @audd/sdk
22
+ ```
23
+
24
+ Recognize from a URL:
25
+
26
+ ```ts
27
+ import { AudD } from "@audd/sdk";
28
+
29
+ const audd = new AudD("test"); // grab a real token at https://dashboard.audd.io
30
+ const song = await audd.recognize("https://audd.tech/example.mp3");
31
+ if (song) {
32
+ console.log(`${song.artist} — ${song.title}`);
33
+ }
34
+ ```
35
+
36
+ Recognize a local file (Node):
37
+
38
+ ```ts
39
+ import { AudD } from "@audd/sdk";
40
+ import { readFile } from "node:fs/promises";
41
+
42
+ const audd = new AudD("test");
43
+
44
+ // Pass a path…
45
+ const song = await audd.recognize("./clip.mp3");
46
+
47
+ // …or pass bytes directly.
48
+ const bytes = await readFile("./clip.mp3");
49
+ const song2 = await audd.recognize(bytes);
50
+ ```
51
+
52
+ A `null` return means the server completed the request successfully but
53
+ found no match — distinct from an error, which throws.
54
+
55
+ ## Authentication
56
+
57
+ Pass the token literally:
58
+
59
+ ```ts
60
+ const audd = new AudD("d29ebb...");
61
+ ```
62
+
63
+ Or set `AUDD_API_TOKEN` in the environment and construct without
64
+ arguments:
65
+
66
+ ```ts
67
+ const audd = new AudD();
68
+ ```
69
+
70
+ For long-running services that rotate credentials, swap the token at
71
+ runtime without aborting in-flight requests:
72
+
73
+ ```ts
74
+ audd.setApiToken(nextToken);
75
+ ```
76
+
77
+ ## What you get back
78
+
79
+ By default, `recognize` resolves to a typed `RecognitionResult` with core
80
+ tags plus AudD's universal song link — no metadata-block opt-in needed:
81
+
82
+ ```ts
83
+ const song = await audd.recognize("https://audd.tech/example.mp3");
84
+ if (!song) return;
85
+
86
+ // Core fields
87
+ console.log(song.artist, song.title, song.album);
88
+ console.log(song.releaseDate, song.label, song.timecode);
89
+
90
+ // AudD's universal song page — links into every provider
91
+ console.log(song.songLink);
92
+
93
+ // Helpers — driven off songLink, work without any `return` opt-in
94
+ console.log(song.thumbnailUrl); // cover-art image, or null
95
+ console.log(song.streamingUrl("spotify")); // direct or lis.tn redirect
96
+ console.log(song.streamingUrls()); // map of provider -> URL
97
+ ```
98
+
99
+ If you need provider-specific metadata blocks, opt in per call. Request
100
+ only what you need — each provider you ask for adds latency:
101
+
102
+ ```ts
103
+ const song = await audd.recognize("https://audd.tech/example.mp3", {
104
+ return: ["apple_music", "spotify"],
105
+ });
106
+ console.log(song?.appleMusic?.url); // direct Apple Music link
107
+ console.log(song?.spotify?.uri); // spotify:track:...
108
+ console.log(song?.previewUrl()); // first preview across requested providers, or null
109
+ ```
110
+
111
+ Valid `return` values: `apple_music`, `spotify`, `deezer`, `napster`,
112
+ `musicbrainz`. Blocks are `undefined` when not requested.
113
+
114
+ `streamingUrl(provider)` prefers the direct provider URL when you
115
+ requested that block via `return`, then falls back to the lis.tn redirect
116
+ when `songLink` is on `lis.tn`. YouTube has only the redirect path.
117
+
118
+ ## Reading additional metadata
119
+
120
+ Every model carries an `extras` map with any server-side fields outside
121
+ the typed surface, plus a `rawResponse` of the full unparsed JSON. Use
122
+ `extras` to read undocumented or beta fields:
123
+
124
+ ```ts
125
+ console.log(song.extras); // any non-typed top-level fields
126
+ console.log(song.rawResponse); // the whole result object as the server returned it
127
+ ```
128
+
129
+ ## Long files (enterprise)
130
+
131
+ `recognizeEnterprise` accepts files up to several hours and returns a
132
+ flat array of matches:
133
+
134
+ ```ts
135
+ const matches = await audd.recognizeEnterprise("./show.mp3", {
136
+ return: ["apple_music", "musicbrainz"],
137
+ limit: 20,
138
+ });
139
+
140
+ for (const m of matches) {
141
+ console.log(m.timecode, m.score, m.artist, m.title, m.isrc);
142
+ }
143
+ ```
144
+
145
+ The default per-call timeout is **1 hour** for this endpoint (60s for
146
+ standard recognition); override with `timeoutMs`.
147
+
148
+ ## Errors
149
+
150
+ Every server error is a typed exception. Use `instanceof` to branch:
151
+
152
+ ```ts
153
+ import {
154
+ AudD,
155
+ AudDAPIError,
156
+ AudDAuthenticationError,
157
+ AudDQuotaError,
158
+ AudDSubscriptionError,
159
+ AudDInvalidAudioError,
160
+ AudDRateLimitError,
161
+ AudDConnectionError,
162
+ } from "@audd/sdk";
163
+
164
+ try {
165
+ await audd.recognize("./clip.mp3");
166
+ } catch (err) {
167
+ if (err instanceof AudDAuthenticationError) {
168
+ // 900 / 901 / 903 — token rejected
169
+ } else if (err instanceof AudDQuotaError) {
170
+ // 902 — out of credits
171
+ } else if (err instanceof AudDSubscriptionError) {
172
+ // 904 / 905 — endpoint not enabled on this token
173
+ } else if (err instanceof AudDInvalidAudioError) {
174
+ // 300 / 400 / 500 — file unreadable / too short / unsupported
175
+ } else if (err instanceof AudDRateLimitError) {
176
+ // 611 — too many requests, slow down
177
+ } else if (err instanceof AudDConnectionError) {
178
+ // network failure or aborted request
179
+ } else if (err instanceof AudDAPIError) {
180
+ console.error(err.errorCode, err.serverMessage, err.requestId);
181
+ } else {
182
+ throw err;
183
+ }
184
+ }
185
+ ```
186
+
187
+ Every `AudDAPIError` exposes `errorCode`, `serverMessage`, `httpStatus`,
188
+ `requestId`, `requestedParams`, `requestMethod`, `brandedMessage`, and
189
+ `rawResponse`. The full hierarchy lives in
190
+ [`src/errors.ts`](src/errors.ts).
191
+
192
+ ## Configuration
193
+
194
+ ```ts
195
+ import { AudD } from "@audd/sdk";
196
+
197
+ const audd = new AudD("...token...", {
198
+ maxRetries: 3, // retry budget per call
199
+ backoffFactorMs: 500, // initial backoff (ms), jittered, exponential
200
+ fetch: customFetch, // bring your own fetch (proxy, mTLS, observability)
201
+ onEvent: (e) => { // request/response/exception inspection hook
202
+ console.log(e.method, e.httpStatus, e.elapsedMs, e.requestId);
203
+ },
204
+ });
205
+ ```
206
+
207
+ Per-call cancellation via `AbortSignal`, including for multi-hour
208
+ enterprise calls:
209
+
210
+ ```ts
211
+ const controller = new AbortController();
212
+ setTimeout(() => controller.abort(), 30_000);
213
+
214
+ const matches = await audd.recognizeEnterprise("./show.mp3", {
215
+ signal: controller.signal,
216
+ limit: 50,
217
+ });
218
+ ```
219
+
220
+ The constructor also accepts an options-only form
221
+ (`new AudD({ apiToken, ... })`) if you'd rather pass everything as one
222
+ object — equivalent to the two-argument form above.
223
+
224
+ A single client instance handles concurrent requests fine; spin up one
225
+ per process, not one per call.
226
+
227
+ ## Streams
228
+
229
+ Real-time recognition over a live audio stream. Once a stream is
230
+ registered, AudD POSTs each match to your callback URL — or if you
231
+ can't host one, drains events to a longpoll endpoint instead.
232
+
233
+ ```ts
234
+ await audd.streams.setCallbackUrl("https://your.app/audd-callback", {
235
+ returnMetadata: ["apple_music", "musicbrainz"],
236
+ });
237
+
238
+ await audd.streams.add({
239
+ url: "https://stream.example/live.m3u8",
240
+ radioId: 12345,
241
+ });
242
+
243
+ const streams = await audd.streams.list();
244
+ ```
245
+
246
+ Parse incoming callback POSTs into a typed payload:
247
+
248
+ ```ts
249
+ const payload = audd.streams.parseCallback(reqBodyJson);
250
+ if (payload.isResult) {
251
+ for (const r of payload.result!.results) {
252
+ console.log(r.artist, r.title, r.score);
253
+ }
254
+ } else if (payload.isNotification) {
255
+ console.log(payload.notification!.notificationCode,
256
+ payload.notification!.notificationMessage);
257
+ }
258
+ ```
259
+
260
+ ### Receiving events without a callback URL (longpoll)
261
+
262
+ Useful when you can't expose a public HTTPS receiver. Before the first
263
+ event, the SDK runs a one-time `getCallbackUrl` preflight — AudD
264
+ silently discards events for accounts without any callback URL set, so
265
+ this catches the trap early. Pass `skipCallbackCheck: true` to opt out.
266
+
267
+ ```ts
268
+ for await (const event of audd.streams.longpoll(category, { timeout: 30 })) {
269
+ console.log(event);
270
+ }
271
+ ```
272
+
273
+ `category` is a 9-character string derived locally from your token and
274
+ `radioId`:
275
+
276
+ ```ts
277
+ const category = audd.streams.deriveLongpollCategory(12345);
278
+ ```
279
+
280
+ ### Browser / widget consumers
281
+
282
+ The `audd/longpoll` sub-entry exports a tokenless `LongpollConsumer` for
283
+ front-end use. It carries no api_token — your server derives the
284
+ category and ships it to the browser. Bundlers tree-shake the auth
285
+ client out of the resulting bundle.
286
+
287
+ ```ts
288
+ import { LongpollConsumer } from "@audd/sdk/longpoll";
289
+
290
+ const consumer = new LongpollConsumer("abc123def");
291
+ for await (const event of consumer.iterate({ timeout: 30 })) {
292
+ console.log(event);
293
+ }
294
+ ```
295
+
296
+ ## Custom catalog (advanced — not for music recognition)
297
+
298
+ > The custom-catalog endpoint is **not** how you submit audio for
299
+ > recognition. For recognition, use `recognize()` or
300
+ > `recognizeEnterprise()`. This endpoint adds songs to your private
301
+ > fingerprint database. Requires special access — contact api@audd.io.
302
+
303
+ ```ts
304
+ await audd.customCatalog.add({
305
+ audioId: 42,
306
+ source: "https://example.com/my-track.mp3",
307
+ });
308
+ ```
309
+
310
+ A raw-request escape hatch is available under `audd.advanced.rawRequest`
311
+ for endpoints not yet wrapped on this SDK.
312
+
313
+ ## Resource cleanup
314
+
315
+ Both `AudD` and `LongpollConsumer` implement `Symbol.asyncDispose` for
316
+ [explicit resource management](https://github.com/tc39/proposal-explicit-resource-management):
317
+
318
+ ```ts
319
+ {
320
+ await using audd = new AudD("...");
321
+ await audd.recognize("...");
322
+ } // close() called automatically here
323
+ ```
324
+
325
+ Older runtimes can call `close()` manually.
326
+
327
+ ## License
328
+
329
+ MIT — see [LICENSE](./LICENSE).
330
+
331
+ ## Support
332
+
333
+ - Documentation: https://docs.audd.io
334
+ - Tokens: https://dashboard.audd.io
335
+ - Email: api@audd.io
@@ -0,0 +1,37 @@
1
+ interface HttpResponse {
2
+ jsonBody: unknown;
3
+ httpStatus: number;
4
+ requestId: string | null;
5
+ rawText: string;
6
+ }
7
+ type FetchLike = typeof globalThis.fetch;
8
+ interface HttpClientOptions {
9
+ apiToken: string;
10
+ fetch?: FetchLike;
11
+ /** Per-call timeout in ms. Defaults: 60_000 standard, 3_600_000 enterprise. */
12
+ defaultTimeoutMs?: number;
13
+ }
14
+ /**
15
+ * Form-data field value. Strings go through directly; Blob carries binary
16
+ * payloads with a filename when constructed via `new File()`.
17
+ */
18
+ type FormFieldValue = string | Blob | undefined;
19
+ declare class HttpClient {
20
+ private apiToken;
21
+ private readonly fetchImpl;
22
+ private readonly defaultTimeoutMs;
23
+ constructor(opts: HttpClientOptions);
24
+ /** Atomically swap the token used for subsequent requests. */
25
+ setApiToken(newToken: string): void;
26
+ postForm(url: string, fields: Record<string, FormFieldValue>, opts?: {
27
+ timeoutMs?: number;
28
+ signal?: AbortSignal;
29
+ }): Promise<HttpResponse>;
30
+ get(url: string, params: Record<string, string | undefined>, opts?: {
31
+ timeoutMs?: number;
32
+ signal?: AbortSignal;
33
+ }): Promise<HttpResponse>;
34
+ private send;
35
+ }
36
+
37
+ export { type FetchLike as F, HttpClient as H };
@@ -0,0 +1,37 @@
1
+ interface HttpResponse {
2
+ jsonBody: unknown;
3
+ httpStatus: number;
4
+ requestId: string | null;
5
+ rawText: string;
6
+ }
7
+ type FetchLike = typeof globalThis.fetch;
8
+ interface HttpClientOptions {
9
+ apiToken: string;
10
+ fetch?: FetchLike;
11
+ /** Per-call timeout in ms. Defaults: 60_000 standard, 3_600_000 enterprise. */
12
+ defaultTimeoutMs?: number;
13
+ }
14
+ /**
15
+ * Form-data field value. Strings go through directly; Blob carries binary
16
+ * payloads with a filename when constructed via `new File()`.
17
+ */
18
+ type FormFieldValue = string | Blob | undefined;
19
+ declare class HttpClient {
20
+ private apiToken;
21
+ private readonly fetchImpl;
22
+ private readonly defaultTimeoutMs;
23
+ constructor(opts: HttpClientOptions);
24
+ /** Atomically swap the token used for subsequent requests. */
25
+ setApiToken(newToken: string): void;
26
+ postForm(url: string, fields: Record<string, FormFieldValue>, opts?: {
27
+ timeoutMs?: number;
28
+ signal?: AbortSignal;
29
+ }): Promise<HttpResponse>;
30
+ get(url: string, params: Record<string, string | undefined>, opts?: {
31
+ timeoutMs?: number;
32
+ signal?: AbortSignal;
33
+ }): Promise<HttpResponse>;
34
+ private send;
35
+ }
36
+
37
+ export { type FetchLike as F, HttpClient as H };