Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

rs-suno is a download-only command line tool that mirrors your Suno.ai library to local files. It is written in Rust and modelled on rclone: you point it at a destination directory and it keeps that directory in step with your Suno library.

The binary is called suno. The crate published to crates.io is rs-suno, so you install it with cargo install rs-suno and then run suno.

What it does

  • Downloads your whole library, plus liked songs and playlists, as tagged audio files (MP3, FLAC, or WAV).
  • Mirrors changes on every run: it downloads new clips, updates tags and artwork, renames or re-encodes files that changed, and, with sync, removes local files whose clips have left your library.
  • Embeds rich metadata: core tags (title, artist, album, date) plus Suno details (style, model, creator, and remix lineage), a front cover, and unsynced lyrics.
  • Groups remixes and edits into lineage albums and writes M3U8 playlists, including a synthetic “Liked Songs” list.
  • Is safe to run unattended from cron or a systemd timer, with careful deletion rules so a bad listing can never wipe your library.

Two verbs, like rclone

rs-suno follows the rclone model of two clear verbs:

  • sync mirrors a source to a destination, including deleting local files that are no longer present upstream. This is the full mirror.
  • copy is additive: it downloads and updates, but never deletes.

If you only ever want to accumulate files, use copy. If you want the destination to be a faithful mirror of your library, use sync. Deletion is governed by strict safety rules described in Sync, copy and deletion safety.

Requirements

Where to go next

Installation and ffmpeg

Install the CLI

With Cargo

If you have a Rust toolchain, install the published crate from crates.io:

cargo install rs-suno

This builds and installs the suno binary into ~/.cargo/bin. Make sure that directory is on your PATH.

Pre-built binaries

Pre-built binaries for common platforms are attached to each GitHub release. Download the archive for your platform, extract the suno binary, and place it somewhere on your PATH.

Verify the install

suno version

This prints the build version and target, the resolved config path, and the detected ffmpeg:

suno 0.1.0 (x86_64-unknown-linux-gnu)
config: /home/alice/.config/suno/config.toml
ffmpeg: 6.1.1 (detected at /usr/bin/ffmpeg)

If the last line reads ffmpeg: not found on PATH, install ffmpeg as below.

ffmpeg

rs-suno shells out to ffmpeg for two jobs:

  • transcoding the server-rendered lossless audio to FLAC, and
  • transcoding a clip’s video preview to an animated WebP cover when you pass --animated-covers.

You therefore need an ffmpeg build with FLAC and animated-WebP (libwebp_anim) support. Most distribution packages include both. suno runs the first ffmpeg it finds on your PATH.

Install ffmpeg

PlatformCommand
Debian, Ubuntusudo apt install ffmpeg
Fedorasudo dnf install ffmpeg
Archsudo pacman -S ffmpeg
macOS (Homebrew)brew install ffmpeg
Windows (winget)winget install Gyan.FFmpeg

Check ffmpeg has what you need

Confirm ffmpeg is on your PATH and can encode the formats:

ffmpeg -hide_banner -encoders | grep -E 'flac|libwebp'

You should see both the flac audio encoder and the libwebp/libwebp_anim video encoders. FLAC is required for the default audio format; the WebP encoder is only needed if you use --animated-covers. MP3 and WAV downloads do not need ffmpeg for the audio itself, but FLAC does, so keeping a full ffmpeg build is the simplest path.

Next steps

With the binary and ffmpeg in place, set up your token in Authentication, then create a config with suno config init.

Authentication

Suno has no public API and issues no API keys. rs-suno authenticates the same way the Suno web app does: with your Clerk __client session cookie. You paste that cookie into rs-suno once, and it mints the short-lived tokens it needs from there.

How it works

  • You supply your __client session token (a long string).
  • On each run, rs-suno sends that token to Clerk (clerk.suno.com) and mints a short-lived JSON Web Token (JWT).
  • It refreshes the JWT automatically, shortly before it expires, so long runs do not stall.
  • Only the minted JWT is sent to the Suno API. Your __client cookie is sent only to Clerk, never to Suno’s API host.

If authentication fails partway through a run, rs-suno stops that account cleanly rather than hammering the server, and re-authenticates on the next run.

Get your __client token

The token lives in your browser once you are logged in to Suno:

  1. Log in at suno.com in your browser.
  2. Open the browser developer tools (F12 on most browsers).
  3. Go to the storage or application panel and find Cookies.
  4. Select the Suno/Clerk origin and copy the value of the cookie named __client.

rs-suno accepts the token in whichever form is convenient: the raw value, a __client=<value> assignment, or the full Cookie: header string. Treat this value like a password. Anyone with it can access your library.

Provide the token

You can supply the token three ways, in order of precedence:

  1. The --token <TOKEN> flag.
  2. The SUNO_TOKEN environment variable (or the per-account SUNO_<LABEL>_TOKEN).
  3. The token field in your config file, which is the usual place for it.

The interactive setup writes it to the config for you:

suno config init

See Configuration for the file format and for running multiple accounts.

Check and refresh a token

Confirm a stored token still works by re-minting its JWT:

suno auth refresh <account>

On success it prints the account and its display name. If the account label is omitted, it uses your single configured account, or --all to check every one.

When a token stops working (you logged out, or Suno rotated the session), update it:

suno config add-account <account> --token <new-token>

Keeping the token safe

rs-suno never prints your token or a minted JWT:

  • suno config show redacts every token, printing [redacted].
  • The --token flag hides its environment value in help output.
  • The __client cookie is only ever sent to Clerk; the Suno API only ever receives the short-lived JWT.

Never commit a token to source control or paste it into logs or issues.

Configuration

Most people keep their token and destination in a config file so a run is just suno sync or suno copy. Flags and environment variables can still override the file for one-off runs and automation.

Config file location

By default the config lives at:

  • Linux and macOS: $XDG_CONFIG_HOME/suno/config.toml, or ~/.config/suno/config.toml.
  • Windows: %APPDATA%\suno\config.toml.

Point at a different file with --config <PATH> or the SUNO_CONFIG environment variable. suno version prints the resolved path.

Create a config

The quickest way is the interactive setup:

suno config init

It prompts for an account label (default default), your __client token, and an optional library root, then writes the file. It will not overwrite an existing config unless you pass --yes.

Add another account later:

suno config add-account work

Print the current config with every token redacted:

suno config show

File format

The config is TOML with an optional [defaults] table and one [accounts.<label>] table per account:

[defaults]
format = "flac"
retries = 3
min_newest = 1
animated_covers = false

[accounts.me]
token = "<your __client token>"
root = "/home/alice/music/suno"

[accounts.work]
token = "<another token>"
root = "/home/alice/music/suno-work"
format = "mp3"

Account settings

KeyTypeDefaultDescription
tokenstringThe __client session token for the account.
rootpathDefault destination directory. Used when a command omits DEST, and required by --all.
formatmp3 | flac | wavflacAudio format for downloads.
retriesinteger3Download retry attempts per clip before it is logged as failed.
min_newestinteger1Minimum newest clips kept when a recency filter would otherwise select nothing.
animated_coversboolfalseAlso write animated WebP covers from clip video previews.

Any account key except token and root may also be set under [defaults] to apply to every account.

Multiple accounts

Each account has its own token and its own root. Account roots must not nest inside one another: a config where one account’s root is a parent of another’s is rejected, so two libraries can never share or overwrite files. Run one account with --account <label>, or every account in isolation with --all (each writes to its own root).

If exactly one account is configured, it is used automatically and you can omit --account.

Precedence

For every setting, the first value found wins, in this order:

  1. Command-line flag (for example --format wav).
  2. Environment variable (per-account SUNO_<LABEL>_* before global SUNO_*).
  3. Config file ([accounts.<label>] before [defaults]).
  4. The built-in default.

Environment variables

VariableEquivalentNotes
SUNO_TOKEN--tokenAlso SUNO_<LABEL>_TOKEN for one account.
SUNO_ACCOUNT--account
SUNO_CONFIG--config
SUNO_DRY_RUN--dry-run
SUNO_YES--yes
SUNO_FORMAT--formatmp3, flac, or wav.
SUNO_RETRIES--retries
SUNO_MIN_NEWEST--min-newest
SUNO_ANIMATED_COVERS--animated-coverstrue or false.

Per-account variants use the account label upper-cased with hyphens turned into underscores, so account my-lib reads SUNO_MY_LIB_TOKEN, SUNO_MY_LIB_FORMAT, and so on. A per-account variable overrides the matching global one.

Running without a config

You do not need a config file for the read-only and one-off commands. With --token (or SUNO_TOKEN) set and no config present, rs-suno runs against a single implicit account, which is handy for ls, lsjson, and fetch.

Commands reference

suno [OPTIONS] <COMMAND>
CommandPurpose
syncMirror your library to a directory, including deletions.
copyDownload and update, never delete.
checkReport what sync or copy would change, touching nothing.
lsList clips in a readable table.
lsjsonList clips as newline-delimited JSON.
fetchDownload one clip by ID or URL.
configCreate and inspect the config file.
authRefresh and test authentication.
versionPrint version and environment information.
completionsEmit a shell completion script.

Global options

These apply to every command and may appear before or after the subcommand.

FlagShortEnvDescription
--account <LABEL>SUNO_ACCOUNTRun against one configured account.
--allRun every configured account in isolation (sync/copy). Conflicts with --account.
--config <PATH>SUNO_CONFIGPath to the config file.
--dry-run-nSUNO_DRY_RUNReport changes without writing to disk or deleting.
--verbose-vIncrease verbosity. Repeatable (-vv).
--quiet-qDecrease verbosity. Repeatable (-qq).
--yes-ySUNO_YESSkip confirmation prompts (such as a destructive sync).
--token <TOKEN>SUNO_TOKENThe __client token. Never printed. Overrides config and env.

Verbosity

Verbosity is relative to the default level of 0.

LevelFlagOutput
Silent-qqErrors only.
Quiet-qPer-run summary, warnings, and errors.
DefaultSummary plus a single progress line.
Verbose-vA line per clip as it is downloaded, tagged, renamed, skipped, or deleted.

Machine-readable output (ls rows and lsjson objects) goes to stdout; progress and summaries go to stderr, so a piped lsjson stays clean.

sync

Mirror selected clips into a destination: download new clips, update tags and artwork, rename or re-encode changed files, and remove local files whose clips have left your library. Deletion is governed by strict safety rules; see Sync, copy and deletion safety.

suno sync [OPTIONS] [DEST]

DEST is the local directory to mirror into. If omitted, the account’s configured root is used.

FlagDefaultDescription
--format <mp3|flac|wav>flacAudio format for downloads.
--limit <N>Mirror only the N most recent clips.
--since <SPEC>Mirror clips newer than 7d, 2w, or last-run.
--min-newest <N>1Newest clips always kept when a recency filter applies.
--retries <N>3Download retry attempts per clip.
--animated-coversoffAlso write animated WebP covers from video previews.

When sync would delete files and --yes was not passed, it lists them and asks for confirmation on an interactive terminal. Without a terminal it refuses and asks you to pass --yes or use copy.

# Mirror everything to the configured root, in FLAC:
suno sync

# Mirror the last two weeks to a specific directory, in MP3:
suno sync /music/suno --format mp3 --since 2w

copy

Additive download and update: same selection and flags as sync, but it never deletes and never prompts.

suno copy /music/suno-archive

check

Report what sync or copy would do without writing anything. It accepts every sync flag.

suno check [OPTIONS] [DEST]
FlagDescription
--exit-codeExit 1 when changes are pending, 0 when up to date (useful in CI).
suno check /music/suno --exit-code

check never touches disk, so it is safe to run at any time.

ls

List selected clips as a tab-separated table (ID, DURATION, TITLE, TAGS). The title is truncated to 48 characters. A header prints only to a terminal, so piping stays clean.

suno ls [OPTIONS]
FlagDefaultDescription
--likedoffList only liked clips.
--limit <N>Stop after the first N clips.
--since <SPEC>Show clips newer than 7d, 2w, or last-run.
--format <text|json>textOutput format; json matches lsjson.
suno ls --limit 20
suno ls --liked | column -t -s $'\t'

lsjson

List selected clips as newline-delimited JSON (one object per line). Equivalent to ls --format json, and it accepts the same flags. The schema is stable for scripting: fields are only added, never removed or renamed. Every field is present on every object; nullable fields are null when Suno supplied no value.

FieldTypeDescription
idstringSuno clip UUID.
titlestringDisplay title; Untitled when blank.
statusstringFor example complete.
durationnumberSeconds.
created_atstringISO 8601 UTC.
is_likedboolWhether the clip is liked.
has_vocalboolWhether the clip has a vocal track.
clip_typestringFor example gen or edit.
tagsstringComma-separated style tags.
promptstring | nullUser prompt.
gpt_description_promptstring | nullAuto-generated description prompt.
lyricsstring | nullLyrics text; null if instrumental.
model_namestringFor example chirp-v4.
major_model_versionstringFor example v4.
display_namestringAccount display name.
handlestringAccount handle.
album_titlestring | nullLineage album title.
root_ancestor_idstring | nullRoot clip of the lineage.
lineage_statusstring | nullFor example root.
edited_clip_idstring | nullSource clip if this is a remix.
audio_urlstringAudio CDN URL.
image_urlstringCover image URL.
image_large_urlstringLarge cover image URL.
video_urlstringClip video URL.
video_cover_urlstringVideo cover image URL.
# Titles of liked clips:
suno lsjson --liked | jq -r '.title'

fetch

Download one clip by ID or URL to a path outside any mirrored library. The clip is written directly and is never tracked or reconciled, so fetch never affects a sync destination.

suno fetch [OPTIONS] <ID_OR_URL> [DEST]

ID_OR_URL is a clip UUID or a Suno URL containing it. DEST defaults to the current directory; when it is a directory the file is named <id>.<ext>.

FlagShortDefaultDescription
--format <mp3|flac|wav>flacAudio format.
--output <PATH>-oExplicit output file path, overriding DEST and auto-naming.
suno fetch 3f2a1b4c-aaaa-bbbb-cccc-ddddeeee0001
suno fetch https://suno.com/song/3f2a1b4c-... -o track.flac

config

Manage the config file. See Configuration for the file format.

suno config init                     # interactively create a config
suno config add-account [LABEL]      # add an account to an existing config
suno config show                     # print the config with tokens redacted

auth

suno auth refresh [ACCOUNT]

Re-mint an account’s JWT to confirm its stored token still works. With no account it uses your single configured account, or --all to check every one. See Authentication.

version

suno version

Print the build version and target, the resolved config path, and the detected ffmpeg.

completions

suno completions <SHELL>

Emit a shell completion script to stdout. SHELL is one of bash, zsh, fish, powershell, or elvish. Redirect it to the location your shell reads.

suno completions bash > ~/.local/share/bash-completion/completions/suno

Summaries

A sync or copy run ends with a summary on stderr:

Sync complete: me
  downloaded    12
  tagged         3
  renamed        1
  deleted        2
  skipped      129
  failed         0
  total        147
Duration: 43.2s

A --dry-run or check run reports the pending counts instead, and makes no changes:

Dry run: me (no changes made)
  to download   12
  to tag         3
  to rename      1
  to delete      2
  up to date   129
  total        147

Sync, copy and deletion safety

sync is the reason rs-suno exists: it keeps a local directory as a faithful mirror of your Suno library, and it does so without ever putting your files at risk. This chapter explains what a run does and the rules that make deletion safe.

The mirror model

  • copy is additive. It downloads new clips and updates existing files, but it never deletes anything.
  • sync is a full mirror. It does everything copy does, and it also removes local files whose clips are no longer in your library.

Both verbs share the same selection and the same incremental engine. The only difference is whether local files may be removed.

What a run does

Each run works in three stages:

  1. Select. Enumerate the library, liked feed, and playlists, then apply any --limit or --since filter.
  2. Plan. Compare the desired state against a manifest of what is already on disk, and decide a set of actions.
  3. Execute. Apply the actions: download, re-encode, retag, rename, write artwork, and (for sync) delete.

A --dry-run, or the check command, stops after the plan and prints what it would do, touching nothing.

Incremental by default

rs-suno keeps a manifest beside the destination and only does work that is needed:

  • Skip unchanged. A clip whose metadata hash, artwork hash, and file size all match the manifest is left alone.
  • Retag and re-art in place. When only tags or artwork changed, the file is updated in place. The audio is not downloaded again.
  • Rename in place. When only the target path changed (for example a retitled clip), the existing file is moved, not re-downloaded.
  • Re-encode on format change. Changing --format replaces the file by re-encoding, without pre-deleting the old one.
  • Re-download missing or empty files. A clip whose local file is absent, or is zero bytes, is treated as missing and downloaded again.

This makes repeat runs fast and cheap, which is what makes frequent scheduled runs practical.

Deletion safety

Deletion is the one irreversible action, so it is hedged with several independent rules. All of them must agree before a single file is removed.

Delete only what has truly left every source

A file is a candidate for deletion only when its clip is absent from every mirror source feeding that destination. A clip that is still present in any source is kept. In addition:

  • copy always wins. A clip held by a copy source is never deleted, even if a sync source no longer lists it.
  • Private clips are preserved. A clip marked private is never deleted.
  • Trashed counts as removed. A clip you have trashed in Suno is treated as gone and its local file is removed (unless a copy source or the private rule preserves it).

The fully-enumerated gate

rs-suno will not delete anything unless the listing it is comparing against was fully enumerated: the feed drained completely, with no transport error and no truncation, and no narrowing filter was applied. In practice this means:

  • A network or listing error disables deletion for that run.
  • --limit and --since narrow the listing, so a run using either never deletes. Use them freely for quick top-ups without any deletion risk.

A missing clip in a partial or filtered listing might still exist upstream, so it is never read as a deletion.

The mass-deletion abort

As a final backstop, a run aborts before deleting when the listing looks catastrophically wrong:

  • An empty listing that would delete your whole library is refused.
  • A delete that would remove at least half of a non-trivial library is refused.

Either abort exits with the safety code (7) and removes nothing. If you really do intend a mass deletion, confirm it explicitly with --min-newest 0 --yes. A stored min_newest = 0 or a habitual --yes alone will not disarm the empty-listing guard.

The confirmation prompt

When a sync would delete files and you did not pass --yes:

  • On an interactive terminal, it lists the files and asks Proceed? [y/N]. Anything other than y or yes aborts with no changes.
  • Without a terminal (a pipe, cron, or CI), it refuses and tells you to pass --yes or use copy.
suno sync will delete 3 local file(s) that are no longer in the source:
  me/Weather/me-Old Draft [b3c4d5e6].flac
  ...
Proceed? [y/N]

Tidying up

After removing files, sync prunes any directories left empty, so the tree does not accumulate stale folders. The destination root itself is always kept.

Robustness

Beyond deletion, several rules protect an in-progress run:

  • One run at a time. A sync or copy takes an exclusive lock (.suno.lock) on the destination, so two runs cannot corrupt the same library.
  • Atomic writes. Files are written to a temporary sibling and renamed into place, so an interrupted write never leaves a half-written file.
  • Size verification. A download whose byte count does not match what the server promised is rejected as a truncated transfer and retried.
  • Rate-limit backoff. A 429 response is retried with exponential backoff that honours the server’s Retry-After header.
  • Resumable. Progress is recorded as it happens, so an interrupted run simply continues on the next run. This is what makes unattended cron or systemd runs safe.

Failure handling

Failures are classified so one bad clip never derails a whole run:

  • Authentication failure stops that account cleanly and re-authenticates on the next run.
  • Transient failure (a timeout, a 5xx, a rate limit) is retried up to --retries times, then recorded and skipped.
  • A single clip’s failure never aborts the run. Other clips still download, and the failure is reported in the summary and log.

What a run leaves behind

Alongside the mirrored audio, a run keeps a few dotfiles at the destination:

FilePurpose
.suno-manifest.jsonThe record of what is on disk, used for incremental runs.
.suno-lineage.jsonThe durable archive of resolved remix and edit lineage.
.suno-last-runTimestamp used by --since last-run.
.suno-audit.logAppend-only log of every deletion and rename.
.suno-failures.logAppend-only log of clips that failed after all retries.
.suno.lockPresent only while a run is active.

The audit and failure logs are not written during a --dry-run or check.

Recipes

# Full mirror to the configured root, prompting before any deletion:
suno sync

# Full mirror, unattended (approve deletions up front):
suno sync --yes

# Fast top-up of just the last week, with no deletion risk:
suno sync --since 7d

# See exactly what a mirror would change, changing nothing:
suno check --exit-code

Lineage and albums

Suno lets you build on a clip: remix it, extend it, or edit it. Each new clip records the one it came from, which forms a lineage that runs back to an original root clip. rs-suno follows that lineage to group related clips into albums and to lay out files predictably.

Root resolution

For every clip, rs-suno walks the lineage back to its root ancestor, the original clip a family of remixes and edits grew from. It fills gaps by looking up parents directly, and it keeps a durable archive of what it has resolved (see the lineage store below), so ancestry stays stable across runs even after Suno purges an intermediate clip.

Albums from lineage

Clips that share a root are grouped into one lineage album. The album title is chosen simply:

  • If the root ancestor is a real, distinct clip, the album takes the root clip’s title.
  • Otherwise (a clip that is its own root), the album takes the clip’s own title.

So a song and all its remixes and extensions land in one album named after the original, while a standalone clip sits in an album of its own name.

File and folder layout

Files are named deterministically from the clip and its lineage:

{creator}/{album}/{creator}-{title} [{id8}]
  • {creator} is your display name (falling back to your handle).
  • {album} is the lineage album title described above.
  • {title} is the clip’s title.
  • {id8} is the first eight characters of the clip id.

For example, a FLAC download might land at:

alice/Neon Horizon/alice-Neon Horizon (Remix) [8d9e0f1a].flac

Names are made safe for the filesystem. Unicode is preserved where it is valid in a path; awkward characters are replaced, and over-long components are shortened.

Collision safety

Two protections make the layout collision-free:

  • Same-title clips never clash. The [{id8}] suffix in every file name keeps two clips with the same title in separate files.
  • Distinct roots never share a folder. If two different roots happen to have the same album title, the folders are separated by a short root-id suffix, so one album can never absorb another’s tracks.

Lineage tags

The lineage is also written into each file’s metadata, including the parent clip, the root clip, and a compact summary of the chain. See Artwork and animated covers for the full list of tags.

The lineage store

Resolved ancestry is saved beside your library in .suno-lineage.json. It is an append-durable archive: once an ancestor is known, it is kept, even if the clip is later trashed or removed upstream. This keeps album grouping stable over time. Because it cannot be rebuilt once Suno purges old clips, a corrupt store stops the run rather than being silently discarded.

Artwork and animated covers

Every download is tagged and carries cover art, so your library looks right in any music player. Artwork also lands as sidecar files so folder-based browsers and media servers pick it up.

Metadata tags

rs-suno writes rich metadata into each file. MP3 uses ID3v2.4 frames and FLAC uses Vorbis comments.

Core tags

  • Title
  • Artist and album artist (your creator name, falling back to Suno)
  • Album (the lineage album title; see Lineage and albums)
  • Date (the clip’s creation date)

Suno tags

  • Style (the clip’s style tags) and a style summary
  • Model (name and version, for example chirp-v4)
  • Creator handle
  • Parent clip, root clip, and a compact lineage summary

Lyrics

Unsynced lyrics (plain text, without timestamps) are embedded when the clip has them.

Cover art

Each download carries and produces static JPEG cover art:

  • Embedded front cover inside the audio file, so players that read embedded art show it.
  • A per-song cover written beside each audio file, sharing the track’s name with a .jpg extension.
  • An album cover named folder.jpg in each album folder, which folder-based players and media servers use as the album thumbnail. It is chosen deterministically from the most-played art-bearing clip in the album.

Animated covers

Suno clips have a short looping video preview. rs-suno can turn that into an animated WebP cover. This is opt-in, because it costs an extra transcode per clip.

Enable it per run with --animated-covers, or set animated_covers = true in your config.

With animated covers on, and for clips that have a video preview, rs-suno also writes:

  • a per-song animated cover beside each audio file, sharing the track’s name with a .webp extension, and
  • an album animated cover named cover.webp in each album folder, chosen from the earliest clip in the album that has a video preview.

The static .jpg covers are always written as well, so players without WebP support still show art.

ffmpeg requirement

Animated covers are transcoded from the clip’s video preview with ffmpeg, so you need an ffmpeg build with animated-WebP (libwebp_anim) support. See Installation and ffmpeg. Without it, use the default static covers.

A note on WAV

The WAV format carries only limited metadata. When you download in WAV, lyrics and embedded album art are omitted, and rs-suno warns you. Choose FLAC (the default) or MP3 if you want the full set of tags and embedded art.

What lands on disk

For an album with animated covers enabled, the layout looks like:

alice/
  Neon Horizon/
    folder.jpg
    cover.webp
    alice-Neon Horizon [a1b2c3d4].flac
    alice-Neon Horizon [a1b2c3d4].jpg
    alice-Neon Horizon [a1b2c3d4].webp
    alice-Neon Horizon (Remix) [8d9e0f1a].flac
    alice-Neon Horizon (Remix) [8d9e0f1a].jpg
    alice-Neon Horizon (Remix) [8d9e0f1a].webp

Without --animated-covers, the .webp files and cover.webp are simply not written.

Playlists (M3U8)

rs-suno writes your Suno playlists as .m3u8 files so any player can open them against your mirrored library.

What gets written

  • One playlist per Suno playlist. Each of your playlists is written as an extended M3U8 file, with its members in the order Suno holds them.
  • A synthetic “Liked Songs” playlist. Your liked clips are written as a Liked Songs.m3u8, in order, even though Suno has no explicit playlist for them.

Playlist files are written at the root of the destination directory. Each file is named after the playlist, made safe for the filesystem, with an .m3u8 extension.

Format

The files are extended M3U8: a header, the playlist name, and one #EXTINF entry per track giving its duration and title, followed by the track’s path relative to the playlist. Relative paths mean the playlist keeps working if you move the whole library.

#EXTM3U
#PLAYLIST:Neon Nights
#EXTINF:217,Neon Horizon
alice/Neon Horizon/alice-Neon Horizon [a1b2c3d4].flac
#EXTINF:182,Electric Storm
alice/Weather/alice-Electric Storm [3f2a1b4c].flac

Members not in your library

A playlist can reference clips you have not downloaded (for example someone else’s track, or a clip excluded by a filter). Rather than write a broken path, rs-suno records the member as a comment noting it is not in the library, using the member’s own title. The rest of the playlist stays valid and in order.

Ordering and safety

  • Order is preserved exactly as Suno reports it.
  • A playlist is only written when its members were listed completely. If a playlist’s listing fails, that playlist is skipped for the run rather than written half-empty. The synthetic “Liked Songs” playlist is likewise only written when the liked feed was fully enumerated.

Playlists are regular mirror artefacts: they are rewritten when their name, order, or any member’s path, title, or duration changes, and kept in step by every sync or copy.

Scheduling and exit codes

rs-suno is built to run unattended. Runs are incremental and resumable, and every outcome maps to a distinct exit code so a scheduler or CI job can react correctly.

Exit codes

CodeMeaningWhen
0SuccessAll requested work completed.
1General errorAn unexpected, uncategorised failure.
2Usage errorUnknown command, invalid flag, or missing argument.
3Config errorMissing or invalid config, unknown account, conflicting flags.
4Authentication failureThe token expired or was rejected and could not be refreshed.
5Partial failureSome clips failed after all retries; others succeeded.
6Transient failure (exhausted)Every clip failed with transient errors; nothing progressed.
7Safety abortA deletion safety rule triggered; no files were deleted.
8InterruptedThe run received an interrupt; partial progress is preserved.

check --exit-code is the exception to this table: it exits 1 to signal that changes are pending, and 0 when the destination is already up to date.

Running unattended

For a scheduled job, avoid interactive prompts:

  • Use copy if you never want deletions. It never prompts.
  • Use sync --yes if you do want the full mirror including deletions. Without a terminal, a sync that would delete files refuses unless --yes is passed.
  • Provide the token from the environment or the config file, not a flag in a script.

The deletion safety rules still apply under --yes: the fully-enumerated gate and the mass-deletion abort will still stop a run that looks wrong. See Sync, copy and deletion safety.

Incremental top-ups

--since last-run mirrors only what changed since the previous successful run, using a timestamp kept beside the library. It is a cheap way to run often. Remember that any recency filter (--since or --limit) disables deletion for that run, so pair frequent top-ups with an occasional full sync if you want deletions reconciled.

cron

# Full mirror every night at 02:30, additive only.
30 2 * * *  SUNO_TOKEN=... /usr/local/bin/suno copy /music/suno >> /var/log/suno.log 2>&1

Prefer keeping the token in the config file (readable only by your user) over putting it in the crontab.

systemd timer

~/.config/systemd/user/suno.service:

[Unit]
Description=Mirror the Suno library
After=network-online.target

[Service]
Type=oneshot
ExecStart=%h/.cargo/bin/suno sync --yes

~/.config/systemd/user/suno.timer:

[Unit]
Description=Run suno sync daily

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

Enable it with:

systemctl --user enable --now suno.timer

Persistent=true catches up a run missed while the machine was off, which pairs well with rs-suno being resumable.

After a run

  • The per-run summary reports counts and duration on stderr.
  • Clips that failed after all retries are listed in .suno-failures.log.
  • Every deletion and rename is recorded in .suno-audit.log.

Because runs are resumable, a job that exits 5, 6, or 8 can simply be run again; it continues from where it left off. An exit of 4 means the token needs attention (see Authentication); an exit of 7 means a safety rule stopped a suspicious deletion and should be investigated before forcing it.

Troubleshooting and FAQ

Troubleshooting

ffmpeg is not found

suno version ends with ffmpeg: not found on PATH. Install ffmpeg and make sure it is on your PATH. See Installation and ffmpeg. rs-suno needs ffmpeg to produce FLAC (the default format) and animated WebP covers.

FLAC or animated covers fail to encode

Your ffmpeg build is missing an encoder. Check it with:

ffmpeg -hide_banner -encoders | grep -E 'flac|libwebp'

If flac is missing, download in MP3 or install a fuller ffmpeg build. If libwebp/libwebp_anim is missing, drop --animated-covers or install an ffmpeg with animated-WebP support.

Authentication failed (exit 4)

The stored token has expired or was rejected. Confirm and re-mint it:

suno auth refresh <account>

If that still fails, the session was rotated (you logged out, or Suno reset it). Get a fresh __client token and update the account:

suno config add-account <account> --token <new-token>

See Authentication.

“multiple accounts configured; pass –account”

You have more than one account and ran a single-account command without saying which. Add --account <label>, or use --all for sync/copy to run every account.

“account has no configured root and no DEST was given”

The account has no root in its config and you did not pass a destination. Give a DEST on the command line, or set root for the account. --all always needs each account to have a root.

“another suno run is active”

A run holds an exclusive lock (.suno.lock) on the destination while it works. If a previous run crashed, the lock file can be left behind. Once you are sure no run is active, delete the .suno.lock file in the destination directory and try again.

A sync aborted with a safety warning (exit 7)

A deletion safety rule stopped the run because the deletion looked wrong: the listing was empty, or it would have removed a large fraction of your library. Nothing was deleted. This is usually a transient listing problem, so try again later. If you genuinely intend a mass deletion, confirm it explicitly with --min-newest 0 --yes. See deletion safety.

Nothing is being deleted

That is expected when you use --since or --limit. A narrowed or filtered listing is not authoritative, so deletion is disabled for that run. Run a plain sync (no recency filter) to reconcile deletions.

A run stopped saying the manifest or lineage store is corrupt

rs-suno refuses to run against a damaged .suno-manifest.json or .suno-lineage.json rather than risk re-downloading everything or losing archived lineage. Restore the file from a backup, or move it aside to start fresh (a fresh manifest will re-verify existing files rather than re-download unchanged ones).

FAQ

What is the difference between sync and copy?

sync mirrors, including deleting local files whose clips have left your library. copy only ever adds and updates. Use copy if you want an archive that never loses files.

How do I preview what a run would do?

Use check (or add --dry-run to sync/copy). Both report the pending changes and touch nothing. check --exit-code exits 1 when changes are pending, for CI.

Can I mirror more than one account?

Yes. Configure each account with its own token and root, then run --all to mirror every account into its own directory, or --account <label> for one. Account roots may not nest inside one another.

Does fetch need a config?

No. fetch can run with just --token (or SUNO_TOKEN). It downloads a single clip to a path you choose and never touches a mirrored library.

Where does my library go?

To the DEST you pass, or the account’s configured root. The config path itself is shown by suno version.

Which format should I choose?

FLAC (the default) is lossless and carries full metadata and embedded art. MP3 is smaller and widely compatible. WAV is lossless but carries limited metadata, so lyrics and embedded art are omitted.

Is it safe to run on a schedule?

Yes. Runs are incremental and resumable, an exclusive lock prevents overlap, and the deletion safety rules prevent a bad listing from wiping files. See Scheduling and exit codes.