Skip to content

qdzsh/suno-api

Repository files navigation

Suno API

A self-hosted REST API that wraps Suno.ai's music generation service, providing an OpenAI-compatible interface. Written in Go using the Gin framework.


Features

  • Music generation via Inspiration Mode (description prompt) and Custom Mode (manual lyrics + style tags)
  • Song continuation — extend an existing clip from a specified timestamp
  • Lyrics generation — generate lyrics independently from music
  • Vocal gender control — specify female or male vocals via a dedicated field
  • Negative style tags — exclude unwanted genres or styles from generation
  • Instrumental mode — generate music without vocals
  • OpenAI-compatible /v1/chat/completions endpoint — drop-in compatible with OpenAI clients and frontends (e.g., ChatNextWeb)
  • Streaming and non-streaming response support for the chat interface
  • Customizable response templates — format chat responses using Go template syntax
  • JWT auto-refresh — refreshes the Suno session token automatically every 30 minutes
  • Task queue with persistence — tasks survive process restarts; unfinished tasks are recovered on startup
  • Multiple database backends — SQLite (default), MySQL, and PostgreSQL
  • Swagger UI — interactive API documentation at /swagger/index.html
  • Proxy support — route outbound requests through an HTTP proxy
  • Rotating log support — optional daily log rotation
  • Docker and Docker Compose deployment

How It Works

  1. Credential bootstrap — on startup, SESSION_ID and COOKIE from the environment are loaded into an in-memory account instance.
  2. JWT exchange — the service immediately calls auth.suno.com using your session credentials to obtain a short-lived JWT. This JWT is refreshed every 30 minutes automatically.
  3. Request handling — incoming API requests are authenticated (if SECRET_TOKEN is set), validated, and forwarded to the Suno backend (studio-api-prod.suno.com) using the active JWT.
  4. Task queue — submitted generation tasks are stored in the database and placed on an internal channel-based queue. A background worker polls Suno's feed endpoint every 5 seconds until the task reaches a terminal state (SUCCESS or FAILURE).
  5. Task recovery — on startup, any tasks that were in progress before a previous shutdown are re-queued automatically.
  6. Response — callers poll the fetch endpoints to retrieve completed task data, or use the chat completions interface which handles polling internally.

Prerequisites

  • A Suno.ai account (free or paid)
  • Browser DevTools access to capture session credentials (one-time setup)
  • One of:
    • Docker + Docker Compose (recommended)
    • Go 1.21+ toolchain (for building from source)

Getting Credentials

This is the most critical part of the setup. You need to extract two values from your browser while logged in to Suno: a Session ID and a Cookie string. The service uses these to obtain a JWT from Suno's auth server on your behalf.

Step 1: Log In to Suno

Open https://suno.com in your browser and log in to your account. Make sure you are fully authenticated before proceeding.

Step 2: Navigate to the Create Page

Go to https://suno.com/create. You must be on this page to trigger the generation API call that you will intercept.

Step 3: Open Developer Tools

Press F12 (or Ctrl+Shift+I on Windows/Linux, Cmd+Option+I on macOS) to open browser DevTools. Switch to the Network tab.

Enable Preserve log (checkbox in the Network tab toolbar) to prevent the log from clearing.

Step 4: Generate a Song

Click the "Create" button in Suno's UI to generate any song. This will trigger a POST request to Suno's API that you will capture.

Step 5: Find the Target Request

In the Network tab, filter requests by typing generate in the search box. Look for a POST request to a URL like:

https://studio-api-prod.suno.com/api/generate/v2/

Click on that request to inspect it.

Step 6: Extract SESSION_ID

  1. In the request details panel, click the Headers tab.
  2. Scroll down to the Request Headers section.
  3. Find the Authorization header. Its value will look like Bearer <JWT>. You do not need this JWT — it is short-lived.
  4. Switch to the Cookies tab (or look in the request headers for the cookie header).
  5. Find the cookie named __client or look for a header named clerk-session-id. The session ID typically appears in the URL of the token exchange call.

Alternative approach (recommended):

  1. In the Network tab, filter by tokens or search for auth.suno.com.
  2. Find a POST request to:
    https://auth.suno.com/v1/client/sessions/<SESSION_ID>/tokens
    
  3. The <SESSION_ID> segment in that URL is your SESSION_ID value. It looks like sess_xxxxxxxxxxxxxxxxxxxxxxxx.

Step 7: Extract COOKIE

  1. Click on the POST request to studio-api-prod.suno.com/api/generate/v2/ found in Step 5.
  2. In the Headers tab, scroll to Request Headers.
  3. Find the cookie header. Its value is a long semicolon-separated string.
  4. Copy the entire value of the cookie header. This is your COOKIE value.

Important: The cookie string must include all key=value pairs. It is typically several hundred characters long.

Step 8: Extract DEVICE_ID (Optional)

  1. On the same generate request, look for a request header named device-id.
  2. Its value is a UUID (e.g., a1b2c3d4-e5f6-7890-abcd-ef1234567890).
  3. You can use this value as DEVICE_ID, or leave it unset to use the built-in default.

Environment Variables

Variable Description Default Required
SESSION_ID Clerk session ID extracted from browser DevTools (see Getting Credentials) Yes
COOKIE Full browser cookie string for suno.com Yes
BASE_URL Suno backend API base URL https://studio-api-prod.suno.com No
EXCHANGE_TOKEN_URL Clerk token exchange endpoint template (%s is replaced with SESSION_ID) https://auth.suno.com/v1/client/sessions/%s/tokens?_clerk_js_version=5.117.0 No
DEVICE_ID Device UUID sent as device-id header on generate requests a1b2c3d4-e5f6-7890-abcd-ef1234567890 No
SECRET_TOKEN Bearer token required in the Authorization header for all /suno/* API calls. If empty, authentication is disabled. No
PORT Port the HTTP server listens on 8000 No
SQL_DSN Database connection string. If empty, SQLite is used. MySQL format: user:pass@tcp(host:port)/dbname. PostgreSQL format: postgres://user:pass@host:port/dbname — (SQLite) No
SQLITE_PATH SQLite database file path (used only when SQL_DSN is empty) api.db?_busy_timeout=5000 No
PROXY HTTP proxy URL for outbound requests to Suno (e.g., http://127.0.0.1:7890) No
CHAT_OPENAI_BASE Base URL of the OpenAI-compatible API used for tool call processing in the /v1/chat/completions route https://api.openai.com No*
CHAT_OPENAI_KEY API key for the OpenAI-compatible endpoint above No*
CHAT_OPENAI_MODEL Model name to use for tool call extraction gpt-4o No
CHAT_TIME_OUT Timeout in seconds for waiting on a Suno generation task 600 No
CHAT_TEMPLATE_DIR Directory containing Go template files for formatting chat responses ./template No
DEBUG Set to true to enable verbose debug logging false No
ROTATE_LOGS Set to true to rotate log files daily false No
LOG_DIR Directory where log files are written ./logs No
PPROF Set to true to enable pprof profiling server on port 8005 false No

*CHAT_OPENAI_BASE and CHAT_OPENAI_KEY are required only when using the /v1/chat/completions endpoint.


Deployment

Docker Compose (Recommended)

Clone the repo (see Build from Source), then create a docker-compose.yml in the repo root — it builds the image from source:

version: '3.4'

services:
  sunoapi:
    build: .
    image: suno-api:local
    container_name: sunoapi
    restart: always
    ports:
      - "8000:8000"
    volumes:
      - ./logs:/logs
      - ./template:/template
    environment:
      - PORT=8000
      - SESSION_ID=sess_your_session_id_here
      - COOKIE=your_full_cookie_string_here
      - DEVICE_ID=a1b2c3d4-e5f6-7890-abcd-ef1234567890
      - SECRET_TOKEN=your_api_secret_token
      - SQL_DSN=root:password@tcp(127.0.0.1:3306)/sunoapi
      - TZ=UTC
      - ROTATE_LOGS=false
      - PPROF=false
      - DEBUG=false
      - CHAT_TEMPLATE_DIR=/template
      - CHAT_OPENAI_MODEL=gpt-4o
      - CHAT_OPENAI_BASE=https://api.openai.com
      - CHAT_OPENAI_KEY=sk-your_openai_key_here

Start the service:

docker compose up -d

View logs:

docker compose logs -f sunoapi

Docker Run

Build the image from the cloned repo first:

docker build -t suno-api:local .

Then run it:

docker run --name suno-api -d \
  -p 8000:8000 \
  -e SESSION_ID=sess_your_session_id_here \
  -e COOKIE="your_full_cookie_string_here" \
  -e DEVICE_ID=a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -e SECRET_TOKEN=your_api_secret_token \
  -e CHAT_OPENAI_BASE=https://api.openai.com \
  -e CHAT_OPENAI_KEY=sk-your_openai_key_here \
  -v $(pwd)/logs:/logs \
  -v $(pwd)/template:/template \
  suno-api:local

Build from Source

Requirements: Go 1.21 or later.

git clone https://github.com/qdzsh/suno-api.git
cd suno-api

go mod download

go build -o suno-api .

Set environment variables and run:

export SESSION_ID=sess_your_session_id_here
export COOKIE="your_full_cookie_string_here"
export SECRET_TOKEN=your_api_secret_token

./suno-api

The server starts on port 8000 by default.


API Reference

All /suno/* endpoints require the Authorization: Bearer <SECRET_TOKEN> header if SECRET_TOKEN is set.

Interactive API docs (Swagger UI) are available at:

http://localhost:8000/swagger/index.html

GET /ping

Health check endpoint. No authentication required.

Response:

{
  "message": "pong"
}

POST /suno/submit/music

Submit a music generation task. Returns a task_id immediately; the generation happens asynchronously. Poll /suno/fetch/:taskId for results.

Request body:

Field Type Description Required
prompt string Song lyrics (Custom Mode). Use structured sections like [Verse], [Chorus], [Bridge]. Ignored if gpt_description_prompt is set. No
gpt_description_prompt string Free-form description of the song (Inspiration Mode). When set, Suno generates lyrics automatically. Mutually exclusive with prompt. No
title string Song title (Custom Mode only) No
tags string Style tags describing the genre and mood (e.g., "pop, synths, upbeat"). Custom Mode. No
negative_tags string Style tags to exclude from generation (e.g., "metal, heavy"). No
vocal_gender string Preferred vocal gender: "female" or "male". Appended to tags as "female vocals" or "male vocals". No
mv string Model version: "chirp-v4" or "chirp-fenix" (v5.5, default). No
make_instrumental boolean Set to true to generate an instrumental track without vocals. Default: false. No
continue_clip_id string Clip ID of an existing song to continue from. Requires task_id. No
continue_at float Timestamp in seconds within the source clip to continue generation from. No
task_id string Required when using continue_clip_id. The task ID of the original song task. No

Example — Custom Mode (lyrics + style tags):

curl -X POST http://localhost:8000/suno/submit/music \
  -H "Authorization: Bearer your_secret_token" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Midnight Drive",
    "prompt": "[Verse]\nCity lights blur past the window\nEngine hum cuts through the night\n\n[Chorus]\nMidnight drive, nowhere to go\nJust the road and the radio glow",
    "tags": "synthwave, cinematic, atmospheric",
    "negative_tags": "metal, aggressive",
    "vocal_gender": "female",
    "mv": "chirp-fenix",
    "make_instrumental": false
  }'

Example — Inspiration Mode (description-based):

curl -X POST http://localhost:8000/suno/submit/music \
  -H "Authorization: Bearer your_secret_token" \
  -H "Content-Type: application/json" \
  -d '{
    "gpt_description_prompt": "An uplifting acoustic folk song about new beginnings in spring",
    "mv": "chirp-fenix"
  }'

Example — Instrumental:

curl -X POST http://localhost:8000/suno/submit/music \
  -H "Authorization: Bearer your_secret_token" \
  -H "Content-Type: application/json" \
  -d '{
    "gpt_description_prompt": "Epic orchestral battle theme",
    "make_instrumental": true,
    "mv": "chirp-fenix"
  }'

Response:

{
  "code": 200,
  "message": "success",
  "data": "3f2a1b4c-8e7d-4f9a-bc12-d3e4f5a6b7c8"
}

The data field contains the task_id. Use it to poll for results.


POST /suno/submit/lyrics

Submit a lyrics generation task. Blocks until Suno returns completed lyrics (up to 2 minutes), then stores and returns a task_id.

Request body:

Field Type Description Required
prompt string Description or theme for the lyrics to generate Yes

Example:

curl -X POST http://localhost:8000/suno/submit/lyrics \
  -H "Authorization: Bearer your_secret_token" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "A melancholic love song about distance and longing"
  }'

Response:

{
  "code": 200,
  "message": "success",
  "data": "lyr_9a8b7c6d5e4f3a2b1c0d"
}

GET /suno/fetch/:taskId

Fetch the result of a single task by its ID.

Path parameter: taskId — the task ID returned from a submit endpoint.

Example:

curl http://localhost:8000/suno/fetch/3f2a1b4c-8e7d-4f9a-bc12-d3e4f5a6b7c8 \
  -H "Authorization: Bearer your_secret_token"

Response (music task, completed):

{
  "code": 200,
  "message": "success",
  "data": [
    {
      "id": "clip_abc123",
      "audio_url": "https://cdn1.suno.ai/clip_abc123.mp3",
      "video_url": "https://cdn1.suno.ai/clip_abc123.mp4",
      "image_url": "https://cdn1.suno.ai/image_abc123.jpeg",
      "image_large_url": "https://cdn1.suno.ai/image_large_abc123.jpeg",
      "title": "Midnight Drive",
      "status": "complete",
      "created_at": "2025-01-01T00:00:00.000Z",
      "major_model_version": "v4",
      "model_name": "chirp-fenix",
      "is_public": false,
      "play_count": 0,
      "upvote_count": 0,
      "metadata": {
        "tags": "synthwave, cinematic, atmospheric, female vocals",
        "prompt": "[Verse]\nCity lights blur past...",
        "duration": 183.5,
        "stream": false,
        "error_message": null
      }
    }
  ]
}

Task status values:

Status Meaning
NOT_START Task created but not yet queued
SUBMITTED Submitted to Suno, awaiting queue entry
QUEUED In Suno's generation queue
IN_PROGRESS Actively generating
SUCCESS Generation complete
FAILURE Generation failed

POST /suno/fetch

Fetch the results of multiple tasks in a single request.

Request body:

Field Type Description Required
ids string[] Array of task IDs to fetch Yes
action string Unused — reserved for future use No

Example:

curl -X POST http://localhost:8000/suno/fetch \
  -H "Authorization: Bearer your_secret_token" \
  -H "Content-Type: application/json" \
  -d '{
    "ids": [
      "3f2a1b4c-8e7d-4f9a-bc12-d3e4f5a6b7c8",
      "9a8b7c6d-5e4f-3a2b-1c0d-ef1234567890"
    ]
  }'

Response:

{
  "code": 200,
  "message": "success",
  "data": [
    {
      "task_id": "3f2a1b4c-8e7d-4f9a-bc12-d3e4f5a6b7c8",
      "action": "MUSIC",
      "status": "SUCCESS",
      "submit_time": 1735689600,
      "start_time": 1735689605,
      "finish_time": 1735689720,
      "data": [ ... ]
    }
  ]
}

GET /suno/account

Returns the current account status including credit balance and subscription information.

Example:

curl http://localhost:8000/suno/account \
  -H "Authorization: Bearer your_secret_token"

Response:

{
  "code": 200,
  "message": "success",
  "data": {
    "certificate": {
      "session_id": "sess_xxxxxxxxxxxxxxxxxxxxxxxx",
      "cookie": "...",
      "jwt": "eyJ...",
      "last_update": 1735689600,
      "credits_left": 450,
      "monthly_limit": 500,
      "monthly_usage": 50,
      "period": "2025-01",
      "is_active": true
    },
    "msg": ""
  }
}

POST /v1/chat/completions

OpenAI-compatible chat completions endpoint. Accepts a natural language message describing the song you want, calls an OpenAI-compatible LLM to extract generation parameters via function calling, then submits a music generation task to Suno and streams or returns the result.

This endpoint does not require SECRET_TOKEN authentication. It requires CHAT_OPENAI_BASE, CHAT_OPENAI_KEY, and a valid chat_resp template to be configured.

Request body: Standard OpenAI ChatCompletionRequest format.

Field Type Description
model string Use "suno-v3", "suno-v3.5", "chirp-v4", or "chirp-fenix"
messages array Conversation messages. Describe the song in the last user message.
stream boolean Set to true for Server-Sent Events streaming

Example (non-streaming):

curl -X POST http://localhost:8000/v1/chat/completions \
  -H "Authorization: Bearer your_openai_key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "chirp-fenix",
    "messages": [
      {
        "role": "user",
        "content": "Create an upbeat pop song about summer vacation at the beach"
      }
    ],
    "stream": false
  }'

Example (streaming):

curl -X POST http://localhost:8000/v1/chat/completions \
  -H "Authorization: Bearer your_openai_key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "chirp-fenix",
    "messages": [
      {
        "role": "user",
        "content": "Create a dark jazz song about city rain"
      }
    ],
    "stream": true
  }'

The endpoint polls Suno every 5 seconds for up to 5 minutes until generation completes, sending SSE progress events during streaming.


OpenAI-Compatible Interface

Using with Chat Clients

The /v1/chat/completions endpoint is compatible with any OpenAI API client. Configure your client to point to this service:

  • Base URL: http://your-server:8000
  • API Key: any value (pass-through, not validated by this service)
  • Model: chirp-fenix (v5.5), chirp-v4, suno-v3, or suno-v3.5

Response Template Customization

The chat response format is controlled by Go templates located in the CHAT_TEMPLATE_DIR directory (default: ./template). Edit suno.yaml in that directory using Go template syntax.

Three template keys are used:

Key When it is rendered
chat_stream_submit Sent once when the task is first submitted (streaming mode)
chat_stream_tick Sent on each polling tick while the task is pending (streaming mode)
chat_resp Rendered when generation is complete (both streaming and non-streaming)

The template data context is a TaskWithData[SunoSongs] struct containing:

  • .TaskID — the internal task ID
  • .Status — current task status string
  • .Data — slice of SunoSong objects, each with .AudioURL, .Title, .Metadata.Tags, etc.

Important Notes

Credential Expiry and JWT Refresh

Suno uses Clerk for authentication. The SESSION_ID and COOKIE values are long-lived browser session credentials. The service uses them to obtain a short-lived JWT from Clerk's token endpoint on startup and refreshes it every 30 minutes automatically.

The JWT refresh interval is intentionally set to 30 minutes. Refreshing too frequently (e.g., every few seconds) triggers Clerk security flags that block the generate endpoint while still allowing read operations — a subtle failure mode that is difficult to diagnose.

When to update your credentials:

  • If you receive 422 Unprocessable Entity or 401 Unauthorized errors from the generate endpoint after the service has been running for a long time.
  • If Suno logs you out of all browser sessions (which invalidates the SESSION_ID).
  • If you change your Suno account password.

To update credentials: Stop the service, update SESSION_ID and COOKIE in your environment configuration, then restart.

To verify your credentials are still valid: Call GET /suno/account. If is_active is true and credits_left is a reasonable number, the credentials are working.

Quick credential refresh trick: Log in to suno.com in your browser, go to the Create page, and generate a song. Then re-extract the SESSION_ID and COOKIE from the new network request. This always gives you fresh, valid credentials.

Database and Task Persistence

  • SQLite (default): No configuration needed. Database is stored in api.db in the working directory. Suitable for single-instance deployments.
  • MySQL: Set SQL_DSN=user:password@tcp(host:3306)/dbname. Recommended for production or multi-container deployments.
  • PostgreSQL: Set SQL_DSN=postgres://user:password@host:5432/dbname.

Tasks are persisted to the database immediately upon submission. If the process is restarted, all unfinished tasks are automatically re-queued and polling resumes.

Suno Credit Usage

Each music generation request typically consumes credits from your Suno account. Check your remaining balance at any time:

curl http://localhost:8000/suno/account \
  -H "Authorization: Bearer your_secret_token" | jq '.data.certificate.credits_left'

The monthly credit limit and usage are also reported in the account response.

Task Timeout

Tasks that do not complete within CHAT_TIME_OUT seconds (default: 600 seconds / 10 minutes) are automatically marked as FAILURE. The polling worker for background music tasks uses a 10-minute hard limit.

Proxy Configuration

If your server cannot reach suno.com directly, configure an HTTP proxy:

PROXY=http://your-proxy-host:port

SOCKS5 proxies are also supported by the underlying TLS client library.

Security

  • Set SECRET_TOKEN to a strong random string to protect all /suno/* endpoints.
  • The /v1/chat/completions endpoint is not protected by SECRET_TOKEN. Place it behind a reverse proxy with your own authentication if you need to restrict access.
  • Never expose SESSION_ID or COOKIE values in logs or public repositories. These values give full access to your Suno account.

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors