pub struct SunoClient<C> { /* private fields */ }Expand description
A client for the Suno library API, owning the account’s ClerkAuth.
Holds the Clock so api_request can back off on a
429 or transient failure, and an [AdaptiveLimiter] that paces reactively:
it waits nowhere until a 429, then halves the rate and ramps it back on
sustained success.
Implementations§
Source§impl<C: Clock> SunoClient<C>
impl<C: Clock> SunoClient<C>
Sourcepub fn new(auth: ClerkAuth, clock: C) -> Self
pub fn new(auth: ClerkAuth, clock: C) -> Self
Create a client from a fresh or already-authenticated ClerkAuth.
Sourcepub async fn list_clips(
&self,
http: &impl Http,
liked: bool,
limit: Option<usize>,
) -> Result<(Vec<Clip>, bool, bool)>
pub async fn list_clips( &self, http: &impl Http, liked: bool, limit: Option<usize>, ) -> Result<(Vec<Clip>, bool, bool)>
List clips across the whole library, or only liked clips.
Walks the cursor-paginated POST /api/feed/v3 feed, hard-capped at
[MAX_PAGES], truncating to limit when set.
The complete flag is true only when paging ended on a server-reported
has_more == false; any other stop (missing has_more, no usable cursor,
limit, [MAX_PAGES], transport error) yields false so the caller
never treats a truncated listing as authoritative for deletion.
any_filtered is true when the downloadable filter dropped any clip,
which likewise denies deletion authority since it may have hidden a
manifest-tracked clip.
Sourcepub async fn get_clip(&self, http: &impl Http, id: &str) -> Result<Clip>
pub async fn get_clip(&self, http: &impl Http, id: &str) -> Result<Clip>
Fetch one clip by ID.
Tries the dedicated /api/clip/{id} endpoint first, then falls back to
scanning the library feed if that endpoint yields no matching clip.
Sourcepub async fn request_wav(&self, http: &impl Http, id: &str) -> Result<()>
pub async fn request_wav(&self, http: &impl Http, id: &str) -> Result<()>
Ask Suno to render a clip to lossless WAV (server-side, asynchronous).
Sourcepub async fn wav_url(
&self,
http: &impl Http,
id: &str,
) -> Result<Option<String>>
pub async fn wav_url( &self, http: &impl Http, id: &str, ) -> Result<Option<String>>
Read the rendered WAV URL for a clip, or None while it is not ready.
A 404 maps to None (not yet rendered), and it skips the shared retry
so the caller’s poll loop owns that budget.
Sourcepub async fn aligned_lyrics(
&self,
http: &impl Http,
id: &str,
) -> Result<AlignedLyrics>
pub async fn aligned_lyrics( &self, http: &impl Http, id: &str, ) -> Result<AlignedLyrics>
Fetch a clip’s word- and line-level aligned (synced) lyrics.
The trailing slash on .../aligned_lyrics/v2/ is required. An
instrumental or un-alignable clip returns 200 with empty arrays, and a
404 is treated the same way, so an absent alignment is “no synced
lyrics” rather than a run failure.
Sourcepub async fn get_clips_by_ids(
&self,
http: &impl Http,
ids: &[&str],
concurrency: usize,
) -> Result<Vec<Clip>>
pub async fn get_clips_by_ids( &self, http: &impl Http, ids: &[&str], concurrency: usize, ) -> Result<Vec<Clip>>
Fetch specific clips by id, batch-first with a per-id fallback.
Used by lineage resolution to gap-fill ancestors absent from a normal
listing, including trashed ones. Ids are fetched in one batch via
get_songs_by_ids; any the batch omits fall
back to one GET /api/clip/{id} each, bounded by concurrency, attempted
once, with a 404 skipped. A 429 while batching propagates rather than
fanning out into per-id requests.
No downloadability filter is applied: an ancestor may be an infill or context artefact the walk must still traverse, so these clips are for resolution only and must never be treated as download candidates. Ids are deduplicated in order and the result preserves that order, matched by id.
Sourcepub async fn get_songs_by_ids(
&self,
http: &impl Http,
ids: &[&str],
) -> Result<Vec<Clip>>
pub async fn get_songs_by_ids( &self, http: &impl Http, ids: &[&str], ) -> Result<Vec<Clip>>
Batch-fetch clips by id via GET /api/clips/get_songs_by_ids?ids=…&ids=….
The pure batch primitive: deduplicated ids are split into
[GET_SONGS_CHUNK] chunks and matched back by id, preserving input order.
Ids the batch does not return are left for the caller to fill.
The endpoint is undocumented and may be unavailable: a chunk it cannot
serve (any 4xx/5xx, transport failure, or unexpected body) yields
nothing for that chunk rather than erroring, so an outage degrades rather
than breaks. A 429 rides the retry and then propagates rather than
fanning out into per-id requests; an auth failure likewise propagates.
Sourcepub async fn get_clip_parent(
&self,
http: &impl Http,
id: &str,
) -> Result<Option<Clip>>
pub async fn get_clip_parent( &self, http: &impl Http, id: &str, ) -> Result<Option<Clip>>
Fetch a clip’s immediate parent, or None when the clip is a root.
A root’s parent comes back as 200 with a bodiless, id-less clip (not a
404); [parse_clip] gates on a non-empty id so that maps to Ok(None).
The 404 arm is a fallback for the alternative encoding. Any other
failure propagates rather than being mistaken for a root.
Sourcepub async fn get_playlists(&self, http: &impl Http) -> Result<Vec<Playlist>>
pub async fn get_playlists(&self, http: &impl Http) -> Result<Vec<Playlist>>
List the account’s own playlists, paging /api/playlist/me.
Trashed and share-list playlists are excluded by query. Paging stops on
the first empty page, is hard-capped at [MAX_PAGES], and de-duplicates
by id so a server that ignores the page parameter cannot loop or inflate
the set.
A hard failure propagates: the caller then refuses every playlist
deletion this run, so a dropped fetch can never remove a .m3u8.
Sourcepub async fn get_playlist_clips(
&self,
http: &impl Http,
id: &str,
) -> Result<(Vec<Clip>, bool)>
pub async fn get_playlist_clips( &self, http: &impl Http, id: &str, ) -> Result<(Vec<Clip>, bool)>
Fetch one playlist’s clips in Suno order via /api/playlist/{id}/.
playlist_clips[] is already ordered with trashed members excluded, so
order is preserved and no downloadability filter is applied. Only clips
with a non-empty id are kept.
The returned bool is a completeness signal for deletion authority:
true only when num_total_results is present, equals the raw count, and
no member was dropped for a missing id. A short or id-missing page returns
false, so a Mirror playlist under library = "off" is never
authoritative unless its whole member set was seen.
Sourcepub async fn get_billing_info(&self, http: &impl Http) -> Result<BillingInfo>
pub async fn get_billing_info(&self, http: &impl Http) -> Result<BillingInfo>
Read the authenticated account’s billing information.
Sourcepub async fn list_stems(
&self,
http: &impl Http,
clip_id: &str,
) -> Result<(Vec<Stem>, bool)>
pub async fn list_stems( &self, http: &impl Http, clip_id: &str, ) -> Result<(Vec<Stem>, bool)>
List a clip’s already-separated stems (free, read-only).
Pages GET /api/clip/{id}/stems/pages then .../stems?page=P (0-indexed).
This endpoint only reads: it never spends credits or triggers separation,
so it is safe on the bulk mirror path. Call only when the clip’s
has_stem is true.
The complete flag is true only when the page count came back and every
page drained after at least one stem was seen. An empty listing
(pages == 0 or a 400/404 on the page-count endpoint), a transport
failure, a partial drain, or a count above [MAX_PAGES] all yield
false, so the caller KEEPS existing local stems rather than reading the
absence as “no stems”.