Music Generation
The full list of models can be viewed on the Models page or retrieved dynamically via the API endpoint: https://openai.bothub.chat/v1/music/models
BotHub generates songs with Suno models. Generation takes 1–3 minutes, so it works asynchronously: the request returns a job with status: "in_progress" right away, and you poll for the result by its ID. Each generation returns two tracks and is billed once, when it completes. A failed generation is not billed.
How It Works
- Send a generation request —
POST /v1/music/generationsreturns immediately withstatus: "in_progress"and a jobid. - Poll for status — Periodically call
GET /v1/music/:musicIduntilstatusbecomes"completed"or"failed". - Download the tracks — On completion,
datacontains both tracks with links to MP3 files and covers.
Two Ways to Describe a Song
| Mode | What to send | What happens |
|---|---|---|
| By description | prompt only (plus instrumental if needed) | Suno writes the lyrics and picks the style itself. The written lyrics come back in data[].lyrics |
| Your own song | lyrics, style, title and any fine-tuning fields | Suno sings your lyrics exactly in the style you set |
prompt cannot be combined with lyrics, style, title or the fine-tuning fields: the request fails with MUSIC_PROMPT_WITH_CUSTOM_FIELDS. Otherwise Suno would sing your description word for word. To describe the style of your own song, use style.
An instrumental track in the second mode needs style or negative_tags: send instrumental: true and describe the music in style.
Endpoints
POST /v1/music/generations
Creates a new music generation. Returns immediately — generation happens asynchronously.
Base URL: https://openai.bothub.chat
Authentication: Authorization: Bearer YOUR_BOTHUB_API_KEY
Request Body
| Field | Type | Description |
|---|---|---|
model | string | Model ID, e.g. suno-v6 (see GET /v1/music/models) |
external_id | string | Your own job ID, up to 128 characters, unique within the account. A repeated request with the same value returns the existing job instead of starting a new one — see below |
prompt | string | Description of the song, up to 3000 characters. Suno writes the lyrics |
lyrics | string | Words to sing, up to 5000 characters. Mark sections like [Verse], [Chorus], [Bridge], [Outro] |
style | string | Genre, mood, tempo, instruments, vocals — up to 1000 characters |
title | string | Track title, up to 80 characters. Defaults to the first line of the lyrics |
instrumental | boolean | true — no vocals. Default false |
negative_tags | string | Styles to avoid, up to 1000 characters |
vocal_gender | string | male or female |
style_weight | number | How closely to follow style, from 0 to 1 |
weirdness_constraint | number | How experimental the result is, from 0 to 1 |
audio_weight | number | Weight of the audio guidance, from 0 to 1 |
duration | integer | Target length in seconds, from 10 to 360 |
variety | integer | Variety of the result, from 0 to 4 |
All fields except model are optional, but the request needs prompt, lyrics or instrumental: true.
GET /v1/music/{musicId}
Returns the current status and result of a music generation.
GET /v1/music/models
Returns the list of available music generation models.
A Song From Your Own Lyrics
- JS
- Python
- Go
- cURL
async function generateSong() {
const BASE_URL = 'https://openai.bothub.chat/v1';
const API_KEY = 'YOUR_BOTHUB_API_KEY';
// Step 1: Create the generation
const createRes = await fetch(`${BASE_URL}/music/generations`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify({
model: 'suno-v6',
lyrics: '[Verse]\nNeon light on the wet asphalt\nThe city never sleeps tonight\n\n[Chorus]\nNight city, lights until the dawn',
style: 'synthwave, 80s retro, 110 bpm, dreamy female vocals, analog synths',
title: 'Night City',
negative_tags: 'heavy metal, screaming',
vocal_gender: 'female',
duration: 180,
}),
});
const { id } = await createRes.json();
console.log('Generation created, id:', id);
// Step 2: Poll until completed
let music;
while (true) {
await new Promise((r) => setTimeout(r, 10000));
const statusRes = await fetch(`${BASE_URL}/music/${id}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
music = await statusRes.json();
console.log('Status:', music.status);
if (music.status === 'completed' || music.status === 'failed') break;
}
if (music.status === 'completed') {
music.data.forEach((track) => console.log(track.title, track.audio_url));
} else {
console.error('Generation failed:', music.error);
}
}
generateSong();
import time
import requests
BASE_URL = 'https://openai.bothub.chat/v1'
API_KEY = 'YOUR_BOTHUB_API_KEY'
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {API_KEY}',
}
# Step 1: Create the generation
response = requests.post(
f'{BASE_URL}/music/generations',
json={
'model': 'suno-v6',
'lyrics': '[Verse]\nNeon light on the wet asphalt\nThe city never sleeps tonight\n\n[Chorus]\nNight city, lights until the dawn',
'style': 'synthwave, 80s retro, 110 bpm, dreamy female vocals, analog synths',
'title': 'Night City',
'negative_tags': 'heavy metal, screaming',
'vocal_gender': 'female',
'duration': 180,
},
headers=headers,
)
music_id = response.json()['id']
print('Generation created, id:', music_id)
# Step 2: Poll until completed
while True:
time.sleep(10)
music = requests.get(f'{BASE_URL}/music/{music_id}', headers=headers).json()
print('Status:', music['status'])
if music['status'] in ('completed', 'failed'):
break
if music['status'] == 'completed':
for track in music['data']:
print(track['title'], track['audio_url'])
else:
print('Generation failed:', music['error'])
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
const (
baseURL = "https://openai.bothub.chat/v1"
apiKey = "YOUR_BOTHUB_API_KEY"
)
func do(method, url string, body any) map[string]any {
var r io.Reader
if body != nil {
b, _ := json.Marshal(body)
r = bytes.NewReader(b)
}
req, _ := http.NewRequest(method, url, r)
req.Header.Set("Authorization", "Bearer "+apiKey)
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 300 {
panic(fmt.Sprintf("%s\n%s", resp.Status, raw))
}
var out map[string]any
if err := json.Unmarshal(raw, &out); err != nil {
panic(err)
}
return out
}
func main() {
created := do("POST", baseURL+"/music/generations", map[string]any{
"model": "suno-v6",
"lyrics": "[Verse]\nNeon light on the wet asphalt\nThe city never sleeps tonight\n\n[Chorus]\nNight city, lights until the dawn",
"style": "synthwave, 80s retro, 110 bpm, dreamy female vocals, analog synths",
"title": "Night City",
"negative_tags": "heavy metal, screaming",
"vocal_gender": "female",
"duration": 180,
})
id := created["id"].(string)
fmt.Println("Generation created, id:", id)
for {
time.Sleep(10 * time.Second)
music := do("GET", baseURL+"/music/"+id, nil)
status := music["status"].(string)
fmt.Println("Status:", status)
if status == "completed" {
for _, t := range music["data"].([]any) {
track := t.(map[string]any)
fmt.Println(track["title"], track["audio_url"])
}
return
}
if status == "failed" {
panic(music["error"])
}
}
}
# Step 1: Create the generation
curl https://openai.bothub.chat/v1/music/generations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_BOTHUB_API_KEY" \
-d '{
"model": "suno-v6",
"lyrics": "[Verse]\nNeon light on the wet asphalt\nThe city never sleeps tonight\n\n[Chorus]\nNight city, lights until the dawn",
"style": "synthwave, 80s retro, 110 bpm, dreamy female vocals, analog synths",
"title": "Night City",
"negative_tags": "heavy metal, screaming",
"vocal_gender": "female",
"duration": 180
}'
# Step 2: Poll for status (replace MUSIC_ID with the id from step 1)
curl https://openai.bothub.chat/v1/music/MUSIC_ID \
-H "Authorization: Bearer YOUR_BOTHUB_API_KEY"
Response — Step 1 (creation):
{
"id": "5f0c2a9e-3b7d-4c1a-9e2f-8d6b4a1c7e30",
"external_id": null,
"object": "music.generation",
"model": "suno-v6",
"status": "in_progress",
"created_at": 1761559200,
"completed_at": null,
"data": [],
"usage": null,
"error": null
}
Response — Step 2 (completed):
{
"id": "5f0c2a9e-3b7d-4c1a-9e2f-8d6b4a1c7e30",
"external_id": null,
"object": "music.generation",
"model": "suno-v6",
"status": "completed",
"created_at": 1761559200,
"completed_at": 1761559318,
"data": [
{
"id": "c1d2e3f4-0000-4a5b-8c9d-0e1f2a3b4c5d",
"audio_url": "https://storage.bothub.chat/files/c1d2e3f4.mp3",
"image_url": "https://storage.bothub.chat/images/c1d2e3f4.jpeg",
"title": "Night City",
"tags": "synthwave, 80s retro, 110 bpm, dreamy female vocals, analog synths",
"lyrics": "[Verse]\nNeon light on the wet asphalt\n...",
"duration": 178
},
{
"id": "d2e3f4a5-0000-4b6c-9d0e-1f2a3b4c5d6e",
"audio_url": "https://storage.bothub.chat/files/d2e3f4a5.mp3",
"image_url": "https://storage.bothub.chat/images/d2e3f4a5.jpeg",
"title": "Night City",
"tags": "synthwave, 80s retro, 110 bpm, dreamy female vocals, analog synths",
"lyrics": "[Verse]\nNeon light on the wet asphalt\n...",
"duration": 182
}
],
"usage": {
"caps": 64530
},
"error": null
}
usage.caps is the amount charged for the generation.
A Song From a Description
Suno writes the lyrics itself; they come back in data[].lyrics.
curl https://openai.bothub.chat/v1/music/generations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_BOTHUB_API_KEY" \
-d '{
"model": "suno-v6",
"prompt": "A cheerful acoustic song about a road trip to the sea with friends"
}'
For background music without vocals, add "instrumental": true.
Your Own Job ID and Safe Retries
Pass your own ID, an order number for example, in external_id. It must be unique within your account and comes back in every response for this job.
external_id also makes retries safe. If the connection drops before you get the response, send the request again with the same external_id: BotHub does not start a second generation and returns the job created by the first request, with the same id. Nothing is charged twice. The other fields of the repeated request are not compared with the first one.
If the job with this external_id has failed, a repeated request starts the generation again and moves the external_id to the new job. A failed generation is not charged.
curl https://openai.bothub.chat/v1/music/generations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_BOTHUB_API_KEY" \
-d '{
"model": "suno-v6",
"external_id": "order-12345",
"prompt": "A cheerful acoustic song about a road trip to the sea with friends"
}'
List Available Models
curl https://openai.bothub.chat/v1/music/models \
-H "Authorization: Bearer YOUR_BOTHUB_API_KEY"
Example Response:
{
"object": "list",
"data": [
{ "id": "suno-v6", "object": "model", "created": 1758000000, "owned_by": "suno" }
]
}
Status Reference
| Status | Description |
|---|---|
in_progress | Generation is in progress |
completed | Tracks are ready — see data |
failed | Generation failed — see error. Nothing is charged |
Errors
Errors returned right away, with HTTP status 400, in the format {"status": "error", "error": {"code": "...", "message": "..."}}:
| Code | Reason |
|---|---|
VALIDATION_ERROR | A field has the wrong type or is out of range; error.data lists the fields |
MUSIC_PROMPT_WITH_CUSTOM_FIELDS | prompt was sent together with lyrics, style, title or fine-tuning fields |
MUSIC_PROMPT_REQUIRED | None of prompt, lyrics or instrumental: true was sent |
MUSIC_STYLE_REQUIRED | An instrumental track in the second mode needs style or negative_tags |
MUSIC_PROMPT_TOO_LONG | prompt is longer than 3000 characters |
MUSIC_LYRICS_TOO_LONG | lyrics is longer than 5000 characters |
MODEL_NOT_SUPPORTED_FOR_ENDPOINT | The model does not generate music |
MUSIC_ARTIST_NAME | The request names a real artist — describe the style instead |
error.code of a failed generation:
| Code | Reason |
|---|---|
MUSIC_MODERATION | The lyrics or description did not pass Suno moderation |
MUSIC_ARTIST_NAME | The request names a real artist |
MUSIC_PROVIDER_ERROR | Suno could not generate the track — try again |
Tips
- Polling interval: Poll every 10–15 seconds. Most songs are ready within 1–3 minutes.
- Song structure: Section tags in
lyrics([Verse],[Chorus],[Bridge]) help Suno build the song the way you wrote it. - Style without names: Describe genre, tempo, instruments and voice in
styleinstead of naming artists — names are rejected by moderation. - Two takes: Both tracks in
dataare full versions of the same song; pick the one you like. - Retries: Send
external_idso that a repeated request after a dropped connection does not start a second paid generation.