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.

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>

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( &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.

Source

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.

Source

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

Source

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.

Source

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

Fetch a clip’s word- and line-level aligned (synced) lyrics.

GET /api/gen/{id}/aligned_lyrics/v2/ (the trailing slash is required) on the studio-api host, authenticated with the same JWT as every other library read. The v2 shape carries both a flat word-level list and a line-level list with section labels and nested per-word timing (see AlignedLyrics).

An instrumental or un-alignable clip returns 200 with empty arrays, which maps to an empty AlignedLyrics; a 404 (no alignment for the clip) is treated the same way, so an absent endpoint is “no synced lyrics” rather than a run failure — the caller then writes no synced artefact, exactly as an empty cover URL writes no cover. Rides the adaptive rate limiter like the other reads.

Source

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.

Source

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.

Source

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.

Source

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

Source

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

Read the authenticated account’s billing information.

Source

pub async fn list_stems( &mut self, http: &impl Http, clip_id: &str, ) -> Result<(Vec<Stem>, bool)>

List a clip’s already-separated stems (free, read-only).

Uses the live stems shape: first GET /api/clip/{id}/stems/pages for the page count ({"pages": N}), then GET /api/clip/{id}/stems?page=P for each P in 0..N (the pages are 0-indexed), whose body is {"stems": [<clip>, ...]} where each stem is a full clip object. Every request rides the shared limiter and retry. This endpoint only reads: it never spends credits and never triggers separation, so it is safe on the bulk mirror path. The caller must only invoke it when the clip’s has_stem is true.

Returns the collected stems paired with a complete flag that is true only when the listing was fully and authoritatively enumerated: the page count came back and every one of its pages drained, AFTER at least one stem was seen. This encodes the deletion-safety invariant: an empty listing (pages == 0, or a 400/404 on the page-count endpoint, which Suno returns for a clip with zero stems), a transport failure, or a partial drain (a page error mid-enumeration surfaces as Err) all yield a non-authoritative result, so the caller KEEPS any existing local stems and never reads the absence as “no stems”. A clip that declares more than [MAX_PAGES] pages is likewise a truncated listing and never authoritative. A stem is only ever removed from an authoritative (complete) listing that omits it, or when its owning clip’s audio is deleted.

Auto Trait Implementations§

§

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

§

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

§

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.