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.
- 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
femaleormalevocals via a dedicated field - Negative style tags — exclude unwanted genres or styles from generation
- Instrumental mode — generate music without vocals
- OpenAI-compatible
/v1/chat/completionsendpoint — 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
- Credential bootstrap — on startup,
SESSION_IDandCOOKIEfrom the environment are loaded into an in-memory account instance. - JWT exchange — the service immediately calls
auth.suno.comusing your session credentials to obtain a short-lived JWT. This JWT is refreshed every 30 minutes automatically. - Request handling — incoming API requests are authenticated (if
SECRET_TOKENis set), validated, and forwarded to the Suno backend (studio-api-prod.suno.com) using the active JWT. - 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 (
SUCCESSorFAILURE). - Task recovery — on startup, any tasks that were in progress before a previous shutdown are re-queued automatically.
- Response — callers poll the fetch endpoints to retrieve completed task data, or use the chat completions interface which handles polling internally.
- 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)
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.
Open https://suno.com in your browser and log in to your account. Make sure you are fully authenticated before proceeding.
Go to https://suno.com/create. You must be on this page to trigger the generation API call that you will intercept.
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.
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.
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.
- In the request details panel, click the Headers tab.
- Scroll down to the Request Headers section.
- Find the
Authorizationheader. Its value will look likeBearer <JWT>. You do not need this JWT — it is short-lived. - Switch to the Cookies tab (or look in the request headers for the
cookieheader). - Find the cookie named
__clientor look for a header namedclerk-session-id. The session ID typically appears in the URL of the token exchange call.
Alternative approach (recommended):
- In the Network tab, filter by
tokensor search forauth.suno.com. - Find a POST request to:
https://auth.suno.com/v1/client/sessions/<SESSION_ID>/tokens - The
<SESSION_ID>segment in that URL is yourSESSION_IDvalue. It looks likesess_xxxxxxxxxxxxxxxxxxxxxxxx.
- Click on the POST request to
studio-api-prod.suno.com/api/generate/v2/found in Step 5. - In the Headers tab, scroll to Request Headers.
- Find the
cookieheader. Its value is a long semicolon-separated string. - Copy the entire value of the
cookieheader. This is yourCOOKIEvalue.
Important: The cookie string must include all key=value pairs. It is typically several hundred characters long.
- On the same generate request, look for a request header named
device-id. - Its value is a UUID (e.g.,
a1b2c3d4-e5f6-7890-abcd-ef1234567890). - You can use this value as
DEVICE_ID, or leave it unset to use the built-in default.
| 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_BASEandCHAT_OPENAI_KEYare required only when using the/v1/chat/completionsendpoint.
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_hereStart the service:
docker compose up -dView logs:
docker compose logs -f sunoapiBuild 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:localRequirements: 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-apiThe server starts on port 8000 by default.
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
Health check endpoint. No authentication required.
Response:
{
"message": "pong"
}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.
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"
}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 |
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": [ ... ]
}
]
}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": ""
}
}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.
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, orsuno-v3.5
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 ofSunoSongobjects, each with.AudioURL,.Title,.Metadata.Tags, etc.
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 Entityor401 Unauthorizederrors 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.
- SQLite (default): No configuration needed. Database is stored in
api.dbin 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.
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.
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.
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.
- Set
SECRET_TOKENto a strong random string to protect all/suno/*endpoints. - The
/v1/chat/completionsendpoint is not protected bySECRET_TOKEN. Place it behind a reverse proxy with your own authentication if you need to restrict access. - Never expose
SESSION_IDorCOOKIEvalues in logs or public repositories. These values give full access to your Suno account.
MIT