songbrain 0.1.1 → 0.2.0

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/dist/index.d.cts CHANGED
@@ -96,16 +96,6 @@ interface Timeline {
96
96
  vocal_timeline: VocalChange[];
97
97
  lyric_hooks: LyricHook[] | null;
98
98
  }
99
- interface PlatformFit {
100
- tiktok: number;
101
- instagram_reels: number;
102
- youtube_shorts: number;
103
- }
104
- interface CaptionIdeas {
105
- curiosity?: string;
106
- hype?: string;
107
- storytelling?: string;
108
- }
109
99
  interface BestMoment {
110
100
  /** 1 = strongest. */
111
101
  rank: number;
@@ -116,16 +106,14 @@ interface BestMoment {
116
106
  kind: string;
117
107
  /** 0-100. */
118
108
  score: number;
119
- why: string;
120
- explanation: string;
121
- judge_note: string;
109
+ /** One sentence: why this moment works. */
110
+ reason: string | null;
111
+ /** The measured audio signals behind the pick, e.g. "strong sustained energy after impact". */
112
+ signals: string[] | null;
122
113
  /** The words sung inside the window. */
123
114
  sung: string | null;
124
- platform_fit: PlatformFit;
125
115
  /** Beats inside the window. Omitted in view=summary. */
126
116
  beat_grid_sec?: number[];
127
- caption_ideas: CaptionIdeas | null;
128
- hashtags: string[] | null;
129
117
  }
130
118
  interface Word {
131
119
  w: string;
@@ -285,6 +273,8 @@ interface ShotPlan {
285
273
  interface ErrorInfo {
286
274
  code: string;
287
275
  message: string;
276
+ /** "req_…", also in the Songbrain-Request-Id header. */
277
+ request_id?: string;
288
278
  }
289
279
  /**
290
280
  * The song document. While processing, only `id`, `status`, `progress` and
@@ -310,6 +300,8 @@ interface Song {
310
300
  created_at?: string | null;
311
301
  external_ref?: string | null;
312
302
  billing?: Billing;
303
+ /** false for test-mode songs. */
304
+ livemode?: boolean;
313
305
  }
314
306
  /** The 202 body of POST /songs. */
315
307
  interface SongCreated {
@@ -320,6 +312,7 @@ interface SongCreated {
320
312
  billing: Billing;
321
313
  external_ref?: string | null;
322
314
  url: string;
315
+ livemode?: boolean;
323
316
  }
324
317
  /** GET /songs/{id}/shot-plan and GET /examples/{id}/shot-plan. */
325
318
  interface SongShotPlan {
@@ -335,10 +328,14 @@ interface SongListItem {
335
328
  external_ref: string | null;
336
329
  created_at: string | null;
337
330
  billing: Billing;
331
+ livemode?: boolean;
338
332
  }
339
333
  interface SongList {
340
334
  object: "list";
341
335
  data: SongListItem[];
336
+ has_more?: boolean;
337
+ /** Pass as `startingAfter` for the next page. */
338
+ next_cursor?: string | null;
342
339
  }
343
340
  interface DeleteResult {
344
341
  id: string;
@@ -395,11 +392,44 @@ interface Pricing {
395
392
  }
396
393
  /** A webhook event body. */
397
394
  interface WebhookEvent<T = Record<string, unknown>> {
398
- type: "song.done" | "song.failed" | "account.low_balance" | (string & {});
395
+ /** "evt_…". Stays the same across retries of one event: dedupe on it. */
396
+ id: string;
397
+ type: "song.done" | "song.failed" | "account.low_balance" | "ping" | (string & {});
399
398
  /** Unix seconds. */
400
399
  created: number;
400
+ livemode: boolean;
401
401
  data: T;
402
402
  }
403
+ /** From the X-RateLimit-* headers. See `Songbrain.lastRateLimit`. */
404
+ interface RateLimitInfo {
405
+ /** Requests per minute. */
406
+ limit: number;
407
+ remaining: number | null;
408
+ /** Seconds until the window has room again. */
409
+ reset: number | null;
410
+ }
411
+ /** POST /webhooks/test. */
412
+ interface WebhookTestResult {
413
+ delivered: boolean;
414
+ status_code: number | null;
415
+ latency_ms: number;
416
+ event_id: string;
417
+ }
418
+ interface WebhookDelivery {
419
+ event_id: string;
420
+ type: WebhookEvent["type"];
421
+ song_id: string | null;
422
+ url: string;
423
+ attempt: number;
424
+ status_code: number | null;
425
+ delivered: boolean;
426
+ latency_ms: number | null;
427
+ created_at: string;
428
+ }
429
+ interface WebhookDeliveryList {
430
+ object: "list";
431
+ data: WebhookDelivery[];
432
+ }
403
433
 
404
434
  declare const DEFAULT_BASE_URL = "https://api.songbrain.ai/v1";
405
435
  /** A Blob/File, a Buffer or bytes, or a local file path (Node). */
@@ -411,7 +441,10 @@ interface SongbrainOptions {
411
441
  baseUrl?: string;
412
442
  /** Timeout for each HTTP request in ms (uploads included). Default 60 000. */
413
443
  timeoutMs?: number;
414
- /** How often to retry 429 and 5xx responses, with backoff. Default 3. */
444
+ /**
445
+ * How often to retry network errors, 429 and 5xx responses, with backoff. Default 3.
446
+ * Song creation is retried too, because every create sends an `Idempotency-Key`.
447
+ */
415
448
  maxRetries?: number;
416
449
  /** A custom fetch implementation. Defaults to the global fetch. */
417
450
  fetch?: typeof fetch;
@@ -429,6 +462,18 @@ interface AnalyzeOptions {
429
462
  externalRef?: string;
430
463
  /** File name for an upload. Its extension must match the audio format. Taken from the path or File name, or guessed from the bytes. */
431
464
  filename?: string;
465
+ /**
466
+ * Test mode: free, never charged, no audio needed. The song is done right away and
467
+ * returns the Sugar Rush example analysis with `livemode: false`. `song.done` is
468
+ * still sent to `webhookUrl`. Made for CI and integration tests.
469
+ */
470
+ test?: boolean;
471
+ /**
472
+ * Sent as `Idempotency-Key`. By default a random UUID per call, reused across the
473
+ * client's own retries, so a retried create never makes a second song. Pass your
474
+ * own (e.g. your job id) to make retries across processes safe too (24 h window).
475
+ */
476
+ idempotencyKey?: string;
432
477
  /** Wait until the song is done and return the full document (default true). */
433
478
  wait?: boolean;
434
479
  /** Time between status checks while waiting. Default 5 000. */
@@ -440,6 +485,18 @@ interface WaitOptions {
440
485
  pollIntervalMs?: number;
441
486
  timeoutMs?: number;
442
487
  }
488
+ interface ListSongsOptions {
489
+ /** Page size, 1-100. Default 20. */
490
+ limit?: number;
491
+ /** A song id, usually the previous page's `next_cursor`. Returns the songs after it. */
492
+ startingAfter?: string;
493
+ }
494
+ interface IterSongsOptions {
495
+ /** Songs per request, 1-100. Default 100. */
496
+ pageSize?: number;
497
+ /** Start after this song id. */
498
+ startingAfter?: string;
499
+ }
443
500
  interface GetSongOptions {
444
501
  /** "summary" drops word timings and beat arrays (about 3x smaller). */
445
502
  view?: "full" | "summary";
@@ -462,11 +519,19 @@ declare class Songbrain {
462
519
  readonly timeoutMs: number;
463
520
  readonly maxRetries: number;
464
521
  private readonly fetchImpl;
522
+ /**
523
+ * `{ limit, remaining, reset }` from the `X-RateLimit-*` headers of the last response
524
+ * that had them (null before that). `reset` is seconds until the window has room.
525
+ */
526
+ lastRateLimit: RateLimitInfo | null;
527
+ /** The `Songbrain-Request-Id` of the last response (null before the first one). */
528
+ lastRequestId: string | null;
465
529
  /** @internal Overridable in tests. */
466
530
  _sleep: (ms: number) => Promise<void>;
467
531
  constructor(options?: SongbrainOptions);
468
532
  /**
469
- * Analyse a song from a file or a public URL. Pass exactly one of `file` or `audioUrl`.
533
+ * Analyse a song from a file or a public URL. Pass exactly one of `file` or `audioUrl`
534
+ * (or neither with `test: true`).
470
535
  * Typically 60-90 s. With `wait: true` (default) resolves to the full document;
471
536
  * with `wait: false` to the 202 body `{ id, status: "processing", eta_sec, billing }`.
472
537
  */
@@ -483,16 +548,31 @@ declare class Songbrain {
483
548
  getSong(songId: string, options?: GetSongOptions): Promise<Song>;
484
549
  /** Only the story and the shot plan: `{ id, status, song, story, shot_plan }`. */
485
550
  shotPlan(songId: string): Promise<SongShotPlan>;
486
- /** Your songs, newest first. `limit` is 1-100 (default 20). */
487
- listSongs(options?: {
488
- limit?: number;
489
- }): Promise<SongList>;
551
+ /** One page of your songs, newest first: `{ object: "list", data, has_more, next_cursor }`. */
552
+ listSongs(options?: ListSongsOptions): Promise<SongList>;
553
+ /**
554
+ * All your songs, newest first, fetching pages as needed.
555
+ *
556
+ * ```ts
557
+ * for await (const item of sb.iterSongs()) console.log(item.id, item.status);
558
+ * ```
559
+ */
560
+ iterSongs(options?: IterSongsOptions): AsyncGenerator<SongListItem, void, undefined>;
490
561
  /** Delete a song's audio and analysis now. 409 `still_processing` while it runs. */
491
562
  deleteSong(songId: string): Promise<DeleteResult>;
492
563
  /** Free songs left this month, credit balance and price per song. */
493
564
  account(): Promise<Account>;
494
565
  /** Prices and limits. No key needed. */
495
566
  pricing(): Promise<Pricing>;
567
+ /**
568
+ * Send a signed `ping` event to `url` right now:
569
+ * `{ delivered, status_code, latency_ms, event_id }`. Checks your receiver before real songs.
570
+ */
571
+ testWebhook(url: string): Promise<WebhookTestResult>;
572
+ /** The last webhook delivery attempts for your account, newest first. */
573
+ webhookDeliveries(options?: {
574
+ limit?: number;
575
+ }): Promise<WebhookDeliveryList>;
496
576
  /** Example analyses of Songbrain's own songs. No key needed. */
497
577
  examples(): Promise<ExampleList>;
498
578
  /** A full example document, in the same format as `getSong`. No key needed. */
@@ -504,9 +584,10 @@ declare class Songbrain {
504
584
  }
505
585
 
506
586
  /**
507
- * Every API error has the shape `{ error: { code, message } }`. The client
508
- * turns it into a `SongbrainError` (or a subclass) with `status`, `code` and
509
- * `message`.
587
+ * Every API error has the shape `{ error: { code, message, request_id } }`.
588
+ * The client turns it into a `SongbrainError` (or a subclass) with `status`,
589
+ * `code`, `message` and `requestId`. The message ends with
590
+ * ` (request_id: req_…)` when there is one; quote it when you contact support.
510
591
  */
511
592
  declare class SongbrainError extends Error {
512
593
  /** HTTP status, or 0 when the error did not come from an HTTP response. */
@@ -515,7 +596,9 @@ declare class SongbrainError extends Error {
515
596
  readonly code: string;
516
597
  /** The parsed response body, when there was one. */
517
598
  readonly body: unknown;
518
- constructor(status: number, code: string, message: string, body?: unknown);
599
+ /** The `Songbrain-Request-Id` of the failed request (`req_…`), or null. */
600
+ readonly requestId: string | null;
601
+ constructor(status: number, code: string, message: string, body?: unknown, requestId?: string | null);
519
602
  }
520
603
  /** 401: the API key is missing, invalid or revoked. */
521
604
  declare class AuthenticationError extends SongbrainError {
@@ -530,7 +613,7 @@ declare class NotFound extends SongbrainError {
530
613
  declare class RateLimited extends SongbrainError {
531
614
  /** Seconds to wait, from the Retry-After header (null if absent). */
532
615
  readonly retryAfter: number | null;
533
- constructor(status: number, code: string, message: string, body?: unknown, retryAfter?: number | null);
616
+ constructor(status: number, code: string, message: string, body?: unknown, retryAfter?: number | null, requestId?: string | null);
534
617
  }
535
618
  /** The song was accepted but its analysis failed. Paid credits are refunded automatically. */
536
619
  declare class AnalysisFailed extends SongbrainError {
@@ -558,13 +641,17 @@ type RawBody = string | Uint8Array | ArrayBuffer;
558
641
  declare function verifyWebhook(rawBody: RawBody, header: string | null | undefined, secret: string, toleranceSec?: number): boolean;
559
642
  /**
560
643
  * Verify the signature and return the parsed event:
561
- * `{ type: "song.done" | "song.failed" | "account.low_balance", created, data }`.
644
+ * `{ id, type: "song.done" | "song.failed" | "account.low_balance" | "ping", created, livemode, data }`.
562
645
  * Throws if the signature is not valid.
646
+ *
647
+ * `id` (`evt_…`, also the `Songbrain-Event-Id` header) stays the same when
648
+ * Songbrain retries an event (up to 10 attempts over about 3 days): store the
649
+ * ids you have handled and skip repeats.
563
650
  */
564
651
  declare function constructWebhookEvent<T = Record<string, unknown>>(rawBody: RawBody, header: string | null | undefined, secret: string, toleranceSec?: number): WebhookEvent<T>;
565
652
  /** Build a signature header value. Useful to test your webhook handler. */
566
653
  declare function signWebhook(rawBody: RawBody, secret: string, timestampSec?: number): string;
567
654
 
568
- declare const VERSION = "0.1.1";
655
+ declare const VERSION = "0.2.0";
569
656
 
570
- export { type Account, type Act, AnalysisFailed, type AnalyzeOptions, AuthenticationError, type BestMoment, type Billing, type CaptionIdeas, type Clip, DEFAULT_BASE_URL, type DeleteResult, type ErrorInfo, type ExampleList, type ExampleSummary, type FileInput, type FullSong, type FullSongSection, type GetSongOptions, type Instrument, InsufficientCredits, type KeyChange, type LoudnessCheck, type LyricHook, type LyricLine, type Lyrics, NotFound, type Phases, type PlatformFit, type Pricing, RateLimited, SIGNATURE_HEADER, type Scene, type SceneKind, type ScoreBlock, type Scores, type Section, type SectionName, type ShotPlan, type ShotStyle, type Song, type SongCreated, type SongDNA, type SongInfo, type SongList, type SongListItem, type SongShotPlan, type SongStatus, Songbrain, SongbrainError, type SongbrainOptions, type SoundsLike, type Story, type StoryBeats, type StoryElement, type Timeline, VERSION, type VocalChange, type WaitOptions, WaitTimeout, type WebhookEvent, type WhatToFix, type WhatWorks, type Word, constructWebhookEvent, signWebhook, verifyWebhook };
657
+ export { type Account, type Act, AnalysisFailed, type AnalyzeOptions, AuthenticationError, type BestMoment, type Billing, type Clip, DEFAULT_BASE_URL, type DeleteResult, type ErrorInfo, type ExampleList, type ExampleSummary, type FileInput, type FullSong, type FullSongSection, type GetSongOptions, type Instrument, InsufficientCredits, type IterSongsOptions, type KeyChange, type ListSongsOptions, type LoudnessCheck, type LyricHook, type LyricLine, type Lyrics, NotFound, type Phases, type Pricing, type RateLimitInfo, RateLimited, SIGNATURE_HEADER, type Scene, type SceneKind, type ScoreBlock, type Scores, type Section, type SectionName, type ShotPlan, type ShotStyle, type Song, type SongCreated, type SongDNA, type SongInfo, type SongList, type SongListItem, type SongShotPlan, type SongStatus, Songbrain, SongbrainError, type SongbrainOptions, type SoundsLike, type Story, type StoryBeats, type StoryElement, type Timeline, VERSION, type VocalChange, type WaitOptions, WaitTimeout, type WebhookDelivery, type WebhookDeliveryList, type WebhookEvent, type WebhookTestResult, type WhatToFix, type WhatWorks, type Word, constructWebhookEvent, signWebhook, verifyWebhook };
package/dist/index.d.ts CHANGED
@@ -96,16 +96,6 @@ interface Timeline {
96
96
  vocal_timeline: VocalChange[];
97
97
  lyric_hooks: LyricHook[] | null;
98
98
  }
99
- interface PlatformFit {
100
- tiktok: number;
101
- instagram_reels: number;
102
- youtube_shorts: number;
103
- }
104
- interface CaptionIdeas {
105
- curiosity?: string;
106
- hype?: string;
107
- storytelling?: string;
108
- }
109
99
  interface BestMoment {
110
100
  /** 1 = strongest. */
111
101
  rank: number;
@@ -116,16 +106,14 @@ interface BestMoment {
116
106
  kind: string;
117
107
  /** 0-100. */
118
108
  score: number;
119
- why: string;
120
- explanation: string;
121
- judge_note: string;
109
+ /** One sentence: why this moment works. */
110
+ reason: string | null;
111
+ /** The measured audio signals behind the pick, e.g. "strong sustained energy after impact". */
112
+ signals: string[] | null;
122
113
  /** The words sung inside the window. */
123
114
  sung: string | null;
124
- platform_fit: PlatformFit;
125
115
  /** Beats inside the window. Omitted in view=summary. */
126
116
  beat_grid_sec?: number[];
127
- caption_ideas: CaptionIdeas | null;
128
- hashtags: string[] | null;
129
117
  }
130
118
  interface Word {
131
119
  w: string;
@@ -285,6 +273,8 @@ interface ShotPlan {
285
273
  interface ErrorInfo {
286
274
  code: string;
287
275
  message: string;
276
+ /** "req_…", also in the Songbrain-Request-Id header. */
277
+ request_id?: string;
288
278
  }
289
279
  /**
290
280
  * The song document. While processing, only `id`, `status`, `progress` and
@@ -310,6 +300,8 @@ interface Song {
310
300
  created_at?: string | null;
311
301
  external_ref?: string | null;
312
302
  billing?: Billing;
303
+ /** false for test-mode songs. */
304
+ livemode?: boolean;
313
305
  }
314
306
  /** The 202 body of POST /songs. */
315
307
  interface SongCreated {
@@ -320,6 +312,7 @@ interface SongCreated {
320
312
  billing: Billing;
321
313
  external_ref?: string | null;
322
314
  url: string;
315
+ livemode?: boolean;
323
316
  }
324
317
  /** GET /songs/{id}/shot-plan and GET /examples/{id}/shot-plan. */
325
318
  interface SongShotPlan {
@@ -335,10 +328,14 @@ interface SongListItem {
335
328
  external_ref: string | null;
336
329
  created_at: string | null;
337
330
  billing: Billing;
331
+ livemode?: boolean;
338
332
  }
339
333
  interface SongList {
340
334
  object: "list";
341
335
  data: SongListItem[];
336
+ has_more?: boolean;
337
+ /** Pass as `startingAfter` for the next page. */
338
+ next_cursor?: string | null;
342
339
  }
343
340
  interface DeleteResult {
344
341
  id: string;
@@ -395,11 +392,44 @@ interface Pricing {
395
392
  }
396
393
  /** A webhook event body. */
397
394
  interface WebhookEvent<T = Record<string, unknown>> {
398
- type: "song.done" | "song.failed" | "account.low_balance" | (string & {});
395
+ /** "evt_…". Stays the same across retries of one event: dedupe on it. */
396
+ id: string;
397
+ type: "song.done" | "song.failed" | "account.low_balance" | "ping" | (string & {});
399
398
  /** Unix seconds. */
400
399
  created: number;
400
+ livemode: boolean;
401
401
  data: T;
402
402
  }
403
+ /** From the X-RateLimit-* headers. See `Songbrain.lastRateLimit`. */
404
+ interface RateLimitInfo {
405
+ /** Requests per minute. */
406
+ limit: number;
407
+ remaining: number | null;
408
+ /** Seconds until the window has room again. */
409
+ reset: number | null;
410
+ }
411
+ /** POST /webhooks/test. */
412
+ interface WebhookTestResult {
413
+ delivered: boolean;
414
+ status_code: number | null;
415
+ latency_ms: number;
416
+ event_id: string;
417
+ }
418
+ interface WebhookDelivery {
419
+ event_id: string;
420
+ type: WebhookEvent["type"];
421
+ song_id: string | null;
422
+ url: string;
423
+ attempt: number;
424
+ status_code: number | null;
425
+ delivered: boolean;
426
+ latency_ms: number | null;
427
+ created_at: string;
428
+ }
429
+ interface WebhookDeliveryList {
430
+ object: "list";
431
+ data: WebhookDelivery[];
432
+ }
403
433
 
404
434
  declare const DEFAULT_BASE_URL = "https://api.songbrain.ai/v1";
405
435
  /** A Blob/File, a Buffer or bytes, or a local file path (Node). */
@@ -411,7 +441,10 @@ interface SongbrainOptions {
411
441
  baseUrl?: string;
412
442
  /** Timeout for each HTTP request in ms (uploads included). Default 60 000. */
413
443
  timeoutMs?: number;
414
- /** How often to retry 429 and 5xx responses, with backoff. Default 3. */
444
+ /**
445
+ * How often to retry network errors, 429 and 5xx responses, with backoff. Default 3.
446
+ * Song creation is retried too, because every create sends an `Idempotency-Key`.
447
+ */
415
448
  maxRetries?: number;
416
449
  /** A custom fetch implementation. Defaults to the global fetch. */
417
450
  fetch?: typeof fetch;
@@ -429,6 +462,18 @@ interface AnalyzeOptions {
429
462
  externalRef?: string;
430
463
  /** File name for an upload. Its extension must match the audio format. Taken from the path or File name, or guessed from the bytes. */
431
464
  filename?: string;
465
+ /**
466
+ * Test mode: free, never charged, no audio needed. The song is done right away and
467
+ * returns the Sugar Rush example analysis with `livemode: false`. `song.done` is
468
+ * still sent to `webhookUrl`. Made for CI and integration tests.
469
+ */
470
+ test?: boolean;
471
+ /**
472
+ * Sent as `Idempotency-Key`. By default a random UUID per call, reused across the
473
+ * client's own retries, so a retried create never makes a second song. Pass your
474
+ * own (e.g. your job id) to make retries across processes safe too (24 h window).
475
+ */
476
+ idempotencyKey?: string;
432
477
  /** Wait until the song is done and return the full document (default true). */
433
478
  wait?: boolean;
434
479
  /** Time between status checks while waiting. Default 5 000. */
@@ -440,6 +485,18 @@ interface WaitOptions {
440
485
  pollIntervalMs?: number;
441
486
  timeoutMs?: number;
442
487
  }
488
+ interface ListSongsOptions {
489
+ /** Page size, 1-100. Default 20. */
490
+ limit?: number;
491
+ /** A song id, usually the previous page's `next_cursor`. Returns the songs after it. */
492
+ startingAfter?: string;
493
+ }
494
+ interface IterSongsOptions {
495
+ /** Songs per request, 1-100. Default 100. */
496
+ pageSize?: number;
497
+ /** Start after this song id. */
498
+ startingAfter?: string;
499
+ }
443
500
  interface GetSongOptions {
444
501
  /** "summary" drops word timings and beat arrays (about 3x smaller). */
445
502
  view?: "full" | "summary";
@@ -462,11 +519,19 @@ declare class Songbrain {
462
519
  readonly timeoutMs: number;
463
520
  readonly maxRetries: number;
464
521
  private readonly fetchImpl;
522
+ /**
523
+ * `{ limit, remaining, reset }` from the `X-RateLimit-*` headers of the last response
524
+ * that had them (null before that). `reset` is seconds until the window has room.
525
+ */
526
+ lastRateLimit: RateLimitInfo | null;
527
+ /** The `Songbrain-Request-Id` of the last response (null before the first one). */
528
+ lastRequestId: string | null;
465
529
  /** @internal Overridable in tests. */
466
530
  _sleep: (ms: number) => Promise<void>;
467
531
  constructor(options?: SongbrainOptions);
468
532
  /**
469
- * Analyse a song from a file or a public URL. Pass exactly one of `file` or `audioUrl`.
533
+ * Analyse a song from a file or a public URL. Pass exactly one of `file` or `audioUrl`
534
+ * (or neither with `test: true`).
470
535
  * Typically 60-90 s. With `wait: true` (default) resolves to the full document;
471
536
  * with `wait: false` to the 202 body `{ id, status: "processing", eta_sec, billing }`.
472
537
  */
@@ -483,16 +548,31 @@ declare class Songbrain {
483
548
  getSong(songId: string, options?: GetSongOptions): Promise<Song>;
484
549
  /** Only the story and the shot plan: `{ id, status, song, story, shot_plan }`. */
485
550
  shotPlan(songId: string): Promise<SongShotPlan>;
486
- /** Your songs, newest first. `limit` is 1-100 (default 20). */
487
- listSongs(options?: {
488
- limit?: number;
489
- }): Promise<SongList>;
551
+ /** One page of your songs, newest first: `{ object: "list", data, has_more, next_cursor }`. */
552
+ listSongs(options?: ListSongsOptions): Promise<SongList>;
553
+ /**
554
+ * All your songs, newest first, fetching pages as needed.
555
+ *
556
+ * ```ts
557
+ * for await (const item of sb.iterSongs()) console.log(item.id, item.status);
558
+ * ```
559
+ */
560
+ iterSongs(options?: IterSongsOptions): AsyncGenerator<SongListItem, void, undefined>;
490
561
  /** Delete a song's audio and analysis now. 409 `still_processing` while it runs. */
491
562
  deleteSong(songId: string): Promise<DeleteResult>;
492
563
  /** Free songs left this month, credit balance and price per song. */
493
564
  account(): Promise<Account>;
494
565
  /** Prices and limits. No key needed. */
495
566
  pricing(): Promise<Pricing>;
567
+ /**
568
+ * Send a signed `ping` event to `url` right now:
569
+ * `{ delivered, status_code, latency_ms, event_id }`. Checks your receiver before real songs.
570
+ */
571
+ testWebhook(url: string): Promise<WebhookTestResult>;
572
+ /** The last webhook delivery attempts for your account, newest first. */
573
+ webhookDeliveries(options?: {
574
+ limit?: number;
575
+ }): Promise<WebhookDeliveryList>;
496
576
  /** Example analyses of Songbrain's own songs. No key needed. */
497
577
  examples(): Promise<ExampleList>;
498
578
  /** A full example document, in the same format as `getSong`. No key needed. */
@@ -504,9 +584,10 @@ declare class Songbrain {
504
584
  }
505
585
 
506
586
  /**
507
- * Every API error has the shape `{ error: { code, message } }`. The client
508
- * turns it into a `SongbrainError` (or a subclass) with `status`, `code` and
509
- * `message`.
587
+ * Every API error has the shape `{ error: { code, message, request_id } }`.
588
+ * The client turns it into a `SongbrainError` (or a subclass) with `status`,
589
+ * `code`, `message` and `requestId`. The message ends with
590
+ * ` (request_id: req_…)` when there is one; quote it when you contact support.
510
591
  */
511
592
  declare class SongbrainError extends Error {
512
593
  /** HTTP status, or 0 when the error did not come from an HTTP response. */
@@ -515,7 +596,9 @@ declare class SongbrainError extends Error {
515
596
  readonly code: string;
516
597
  /** The parsed response body, when there was one. */
517
598
  readonly body: unknown;
518
- constructor(status: number, code: string, message: string, body?: unknown);
599
+ /** The `Songbrain-Request-Id` of the failed request (`req_…`), or null. */
600
+ readonly requestId: string | null;
601
+ constructor(status: number, code: string, message: string, body?: unknown, requestId?: string | null);
519
602
  }
520
603
  /** 401: the API key is missing, invalid or revoked. */
521
604
  declare class AuthenticationError extends SongbrainError {
@@ -530,7 +613,7 @@ declare class NotFound extends SongbrainError {
530
613
  declare class RateLimited extends SongbrainError {
531
614
  /** Seconds to wait, from the Retry-After header (null if absent). */
532
615
  readonly retryAfter: number | null;
533
- constructor(status: number, code: string, message: string, body?: unknown, retryAfter?: number | null);
616
+ constructor(status: number, code: string, message: string, body?: unknown, retryAfter?: number | null, requestId?: string | null);
534
617
  }
535
618
  /** The song was accepted but its analysis failed. Paid credits are refunded automatically. */
536
619
  declare class AnalysisFailed extends SongbrainError {
@@ -558,13 +641,17 @@ type RawBody = string | Uint8Array | ArrayBuffer;
558
641
  declare function verifyWebhook(rawBody: RawBody, header: string | null | undefined, secret: string, toleranceSec?: number): boolean;
559
642
  /**
560
643
  * Verify the signature and return the parsed event:
561
- * `{ type: "song.done" | "song.failed" | "account.low_balance", created, data }`.
644
+ * `{ id, type: "song.done" | "song.failed" | "account.low_balance" | "ping", created, livemode, data }`.
562
645
  * Throws if the signature is not valid.
646
+ *
647
+ * `id` (`evt_…`, also the `Songbrain-Event-Id` header) stays the same when
648
+ * Songbrain retries an event (up to 10 attempts over about 3 days): store the
649
+ * ids you have handled and skip repeats.
563
650
  */
564
651
  declare function constructWebhookEvent<T = Record<string, unknown>>(rawBody: RawBody, header: string | null | undefined, secret: string, toleranceSec?: number): WebhookEvent<T>;
565
652
  /** Build a signature header value. Useful to test your webhook handler. */
566
653
  declare function signWebhook(rawBody: RawBody, secret: string, timestampSec?: number): string;
567
654
 
568
- declare const VERSION = "0.1.1";
655
+ declare const VERSION = "0.2.0";
569
656
 
570
- export { type Account, type Act, AnalysisFailed, type AnalyzeOptions, AuthenticationError, type BestMoment, type Billing, type CaptionIdeas, type Clip, DEFAULT_BASE_URL, type DeleteResult, type ErrorInfo, type ExampleList, type ExampleSummary, type FileInput, type FullSong, type FullSongSection, type GetSongOptions, type Instrument, InsufficientCredits, type KeyChange, type LoudnessCheck, type LyricHook, type LyricLine, type Lyrics, NotFound, type Phases, type PlatformFit, type Pricing, RateLimited, SIGNATURE_HEADER, type Scene, type SceneKind, type ScoreBlock, type Scores, type Section, type SectionName, type ShotPlan, type ShotStyle, type Song, type SongCreated, type SongDNA, type SongInfo, type SongList, type SongListItem, type SongShotPlan, type SongStatus, Songbrain, SongbrainError, type SongbrainOptions, type SoundsLike, type Story, type StoryBeats, type StoryElement, type Timeline, VERSION, type VocalChange, type WaitOptions, WaitTimeout, type WebhookEvent, type WhatToFix, type WhatWorks, type Word, constructWebhookEvent, signWebhook, verifyWebhook };
657
+ export { type Account, type Act, AnalysisFailed, type AnalyzeOptions, AuthenticationError, type BestMoment, type Billing, type Clip, DEFAULT_BASE_URL, type DeleteResult, type ErrorInfo, type ExampleList, type ExampleSummary, type FileInput, type FullSong, type FullSongSection, type GetSongOptions, type Instrument, InsufficientCredits, type IterSongsOptions, type KeyChange, type ListSongsOptions, type LoudnessCheck, type LyricHook, type LyricLine, type Lyrics, NotFound, type Phases, type Pricing, type RateLimitInfo, RateLimited, SIGNATURE_HEADER, type Scene, type SceneKind, type ScoreBlock, type Scores, type Section, type SectionName, type ShotPlan, type ShotStyle, type Song, type SongCreated, type SongDNA, type SongInfo, type SongList, type SongListItem, type SongShotPlan, type SongStatus, Songbrain, SongbrainError, type SongbrainOptions, type SoundsLike, type Story, type StoryBeats, type StoryElement, type Timeline, VERSION, type VocalChange, type WaitOptions, WaitTimeout, type WebhookDelivery, type WebhookDeliveryList, type WebhookEvent, type WebhookTestResult, type WhatToFix, type WhatWorks, type Word, constructWebhookEvent, signWebhook, verifyWebhook };