pub struct SunoClient<C> { /* private fields */ }Expand description
A client for the Suno library API, owning the account’s ClerkAuth.
The Clock is held so api_request can back off
through the port on a 429 or transient failure — the engine still sleeps
nowhere itself. The [AdaptiveLimiter] paces reactively: an unthrottled
listing waits nowhere, and only after a 429 does it space requests out,
halving the rate and ramping it back after a run of clean successes so pacing
tracks Suno’s real limit rather than a fixed constant.
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(
&mut self,
http: &impl Http,
liked: bool,
limit: Option<usize>,
) -> Result<(Vec<Clip>, bool)>
pub async fn list_clips( &mut self, http: &impl Http, liked: bool, limit: Option<usize>, ) -> Result<(Vec<Clip>, bool)>
List clips across the whole library, or only liked clips.
Walks the cursor-paginated POST /api/feed/v3 feed, following
next_cursor until the server reports the end. Once limit clips have
been collected it stops at the next page boundary and truncates to
limit. Paging is hard-capped at [MAX_PAGES] so a runaway
has_more can never loop forever. When liked is set the feed filter
scopes to liked clips (liked: "True").
Returns the clips paired with a complete flag that is true only when
paging ended because the server reported has_more == false (the feed
fully drained). A missing has_more, a has_more == true page with no
usable next_cursor, a limit stop, exhausting [MAX_PAGES], or any
transport error all yield false (or propagate) so the caller can refuse
to treat a truncated listing as authoritative for deletion.
Sourcepub async fn get_clip(&mut self, http: &impl Http, id: &str) -> Result<Clip>
pub async fn get_clip(&mut 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, since that endpoint’s exact shape is not yet
confirmed against the live API.
Sourcepub async fn request_wav(&mut self, http: &impl Http, id: &str) -> Result<()>
pub async fn request_wav(&mut self, http: &impl Http, id: &str) -> Result<()>
Ask Suno to render a clip to lossless WAV (server-side, asynchronous).
Sourcepub async fn wav_url(
&mut self,
http: &impl Http,
id: &str,
) -> Result<Option<String>>
pub async fn wav_url( &mut self, http: &impl Http, id: &str, ) -> Result<Option<String>>
Read the rendered WAV URL for a clip, or None while it is not ready.
Sourcepub async fn get_clips_by_ids(
&mut self,
http: &impl Http,
ids: &[&str],
) -> Result<Vec<Clip>>
pub async fn get_clips_by_ids( &mut self, http: &impl Http, ids: &[&str], ) -> Result<Vec<Clip>>
Fetch specific clips by id, one GET /api/clip/{id} per id.
Used by lineage resolution to gap-fill ancestors that are absent from a
normal listing, including trashed ones. The v3 feed has no batch by-id
filter, so each id is fetched individually; /api/clip/{id} returns any
clip, trashed or artefact, with the full field set. Unlike
list_clips, no downloadability filter is applied: an
ancestor may itself be an infill or context-window artefact that the
lineage walk must still traverse. Clips returned here are ancestors for
resolution only and must never be treated as download candidates. Ids are
deduplicated in order, and an id that cannot be retrieved (a 404) is
skipped so the caller can fall back to the parent endpoint.
Sourcepub async fn get_clip_parent(
&mut self,
http: &impl Http,
id: &str,
) -> Result<Option<Clip>>
pub async fn get_clip_parent( &mut self, http: &impl Http, id: &str, ) -> Result<Option<Clip>>
Fetch a clip’s immediate parent via the dedicated parent endpoint.
Returns the parent clip, or None when the clip is a root (no parent) or
the endpoint yields no clip. Lineage resolution uses this as a fallback
when a missing ancestor cannot be retrieved by id. Only a 404 (the clip
has no parent) maps to None; any other failure, including a transient
5xx, propagates as an error rather than being mistaken for a root.
Sourcepub async fn get_playlists(&mut self, http: &impl Http) -> Result<Vec<Playlist>>
pub async fn get_playlists(&mut 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, so the result is
the account’s authoritative own set. Paging stops on the first empty page
and is hard-capped at [MAX_PAGES] so a server that ignores the page
parameter cannot loop forever. Only entries with a non-empty id are kept.
A hard failure propagates as an error; the caller treats that as “the
playlist listing did not fully enumerate” and refuses every playlist
deletion this run, so a dropped fetch can never remove a .m3u8.
Sourcepub async fn get_playlist_clips(
&mut self,
http: &impl Http,
id: &str,
) -> Result<(Vec<Clip>, bool)>
pub async fn get_playlist_clips( &mut self, http: &impl Http, id: &str, ) -> Result<(Vec<Clip>, bool)>
Fetch one playlist’s clips in Suno order via /api/playlist/{id}/.
The response’s playlist_clips[] is already ordered and trashed members
are excluded by Suno, so the order is preserved exactly and no
downloadability filter is applied: a playlist may legitimately contain any
clip. Each entry’s clip object is mapped (falling back to the entry
itself), and only clips with a non-empty id are kept.
The returned bool is a completeness signal for deletion authority: the
endpoint reports num_total_results (the playlist’s full member count)
alongside playlist_clips[], so true means every member came back on
this single page (returned == num_total_results). A short page (a
paginated or partially-listed playlist) returns false, so a Mirror
playlist area under library = "off" is never treated as authoritative
unless its whole member set was seen (D5).