Skip to main content

SunoClient

Struct SunoClient 

Source
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>

Source

pub fn new(auth: ClerkAuth, clock: C) -> Self

Create a client from a fresh or already-authenticated ClerkAuth.

Source

pub fn auth(&self) -> &ClerkAuth

Borrow the underlying authenticator.

Source

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.

Source

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.

Source

pub async fn request_wav(&self, http: &impl Http, id: &str) -> Result<()>

Ask Suno to render a clip to lossless WAV (server-side, asynchronous).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub async fn get_billing_info(&self, http: &impl Http) -> Result<BillingInfo>

Read the authenticated account’s billing information.

Source

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

Auto Trait Implementations§

§

impl<C> !Freeze for SunoClient<C>

§

impl<C> !RefUnwindSafe for SunoClient<C>

§

impl<C> Send for SunoClient<C>
where C: Send,

§

impl<C> Sync for SunoClient<C>
where C: Sync,

§

impl<C> Unpin for SunoClient<C>
where C: Unpin,

§

impl<C> UnsafeUnpin for SunoClient<C>
where C: UnsafeUnpin,

§

impl<C> UnwindSafe for SunoClient<C>
where C: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.