영감 모드 생성 API
인터페이스 설명
영감 모드 생성은 한 문장 설명으로 완성된 곡을 만드는 데 적합합니다. 시스템이 설명을 바탕으로 가사, 멜로디, 편곡, 보컬을 자동으로 생성합니다.
엔드포인트: POST /api/music/create
과금: 유료, 현재 기본 1회 0.6위안. 보통 1회 호출에 2곡이 반환되며, 실제 청구 금액은 현재 백오피스 가격 설정을 따릅니다.
Max Mode / Variety: 이 엔드포인트는 선택 파라미터 max_mode와 variety(0-4)를 지원합니다.
max_mode를 켜면 이번 호출은 2배 가격으로 과금되며(0.6 → 1.2위안/회), variety는 추가 과금이 없습니다.
두 값의 의미와 사용법은 새 API 개요의 "고급 파라미터" 절을 참고하세요.
권장 모델: suno-v6
대기 시간과 타임아웃(중요): 이 엔드포인트는 동기 방식이며, 응답이 돌아온 시점에야 주문이 접수되어 생성에 넘겨집니다. 실측 기준 대부분의 요청은 1~3초에 반환되고, 약 10%는 30초를 넘기며, 최장 약 230초가 걸립니다. 클라이언트 타임아웃을 300초 이상으로 설정하세요. 응답이 없다고 해서 생성되지 않은 것이 아니며, 타임아웃 직후 재시도하면 주문이 중복되고 요금이 두 번 청구됩니다. 연결이 끊겼다면 잠시 후
GET /api/music/songs로 확인할 수 있습니다. 자세한 내용은 새 API 개요의 "대기 시간과 타임아웃 설정"을 참고하세요.
요청 파라미터
Headers
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| Authorization | string | 예 | Bearer YOUR_API_KEY |
| Content-Type | string | 예 | application/json |
Body 파라미터
| 파라미터 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
| description | string | 예 | - | 곡 설명. 다국어 지원. 최대 3000자(초과 시 400을 반환하고 요금이 부과되지 않음) |
| model | string | 아니오 | suno-v6 | 대고객 모델명: suno-v6, suno-v6-wild, suno-v6-mini |
| instrumental | boolean | 아니오 | false | 순수 연주곡 여부 |
| wait_completion | boolean | 아니오 | false | 생성이 끝날 때까지 기다린 뒤 반환할지 여부 |
| references | array | 아니오 | - | 참고 자료 목록. 참고 곡 / 기존 음색 / 참고 이미지를 지정할 수 있습니다. 곡은 최대 5곡, 음색은 1개, 이미지는 1장. 아래 "참고 자료 references(선택)" 참고 |
요청 예시
curl -X POST https://www.suno-api.io/api/music/create \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "suno-v6",
"description": "여름 바다를 주제로 한 경쾌한 중국어 팝",
"instrumental": false,
"wait_completion": false
}'
참고 자료 references(선택)
references는 이번 생성에 "참고 자료"를 붙이는 배열입니다. 생략할 수 있으며(생략하면 기존과 완전히 동일하게 동작), 한 종류만 붙이거나 임의로 조합할 수 있습니다.
요소 타입
| type | 필수 필드 | 선택 필드 | 최대 개수 | 설명 |
|---|---|---|---|---|
song |
song_id |
style_prompt, lyrics, duration_s |
5 | 참고 곡. song_id는 곡 조회 및 기록 API가 반환하는 값을 사용하며, 곡은 이미 완료 상태여야 합니다 |
voice |
voice_id |
- | 1 | 기존 음색(보컬). voice_id는 보이스 클론 및 지정 음색 생성 API에서 가져옵니다 |
image |
base64 |
- | 1 | 참고 이미지. data:image/jpeg;base64,... 형식의 base64를 전송합니다 |
규칙
- 참고 곡의 순서가 곧 의미의 순서입니다: 표현하고 싶은 순서대로 전달하세요(예: "첫 곡의 멜로디, 둘째 곡의 보컬").
style_prompt/lyrics/duration_s는 모두 생략할 수 있고, 플랫폼이 곡 정보에서 채웁니다. 보낸 값이 있으면 그 값이 우선합니다.- 순수 연주곡은
lyrics에""또는"[Instrumental]"을 보내면 됩니다. - 참고 이미지는
jpeg/png/webp를 지원하며 한 장당 10MB 이하이고, base64 형식으로 요청 본문에 함께 담아 보냅니다. - 참고 곡을 여러 개 붙일 때는
accept_mode=async(위의 "접수 영수증 모드")를 함께 쓰는 것을 권장합니다. 서버가 참고 자료를 먼저 준비해야 하므로 동기 대기가 길어질 수 있습니다.
예시: 참고 곡 2곡 붙이기
curl -X POST https://www.suno-api.io/api/music/create \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "suno-v6",
"description": "이 영감들을 한 곡으로 합쳐 주세요",
"references": [
{ "type": "song", "song_id": "43eb711d-81f8-4e72-a68e-b2498bac88f9" },
{ "type": "song", "song_id": "a90438d2-35fc-4443-88f4-56531e6e5d24" }
]
}'
예시: 참고 곡 + 기존 음색
{
"model": "suno-v6",
"description": "이 음색으로 이 영감을 노래하게 해 주세요",
"references": [
{ "type": "song", "song_id": "43eb711d-81f8-4e72-a68e-b2498bac88f9" },
{ "type": "voice", "voice_id": "5813cbe1-36f1-414a-a9a8-4d399dfeb292" }
]
}
예시: 참고 곡 + 참고 이미지
{
"model": "suno-v6",
"description": "이 이미지의 분위기를 참고해 주세요",
"references": [
{ "type": "song", "song_id": "43eb711d-81f8-4e72-a68e-b2498bac88f9" },
{ "type": "image", "base64": "data:image/jpeg;base64,/9j/4AAQ..." }
]
}
파라미터 오류
다음 경우 HTTP 400을 반환하며, 과금 전에 차단되므로 비용이 발생하지 않습니다:
| 상황 | 오류 메시지 |
|---|---|
| 참고 곡이 5곡을 초과 | references supports at most 5 song references |
| 음색이 1개를 초과 | references supports at most 1 voice reference |
| 참고 이미지가 1장을 초과 | references supports at most 1 image reference |
type 값이 잘못됨 |
references[0].type must be one of song, voice, image |
곡에 song_id가 없음 |
references[0].song_id is required |
| 곡이 아직 완료되지 않음 | references[0].song_id must be a real completed clip |
| 같은 곡을 중복 지정 | references[0].song_id is duplicated |
references[0]의 숫자는 배열에서 해당 요소의 인덱스이며, 어떤 항목에서 오류가 났는지 찾기 쉽게 해 줍니다.
응답 결과
곡 배열을 반환하며 보통 2건의 작업 또는 곡을 포함합니다.
[
{
"song_id": "clip_id",
"song_title": "여름 바닷바람",
"status": "pending",
"description": "여름 바다를 주제로 한 경쾌한 중국어 팝",
"audio_url": "",
"cover_url": "",
"video_url": "",
"model_version": "suno-v6",
"generation_type": "generate",
"created_at": "2026-05-21T10:00:00Z"
}
]
접수 영수증 모드(선택, 긴 연결을 유지하기 어려운 클라이언트에 권장)
기본적으로 이 엔드포인트는 동기 방식입니다. 응답이 돌아와야 주문이 접수된 것이며, 실측상 최장 약 230초까지 기다릴 수 있습니다. 클라이언트가 긴 연결에 적합하지 않다면 접수 영수증 모드를 켤 수 있습니다. 둘 중 하나만 하면 됩니다:
- 요청 본문에
"accept_mode": "async"추가 - 요청 헤더에
X-Accept-Mode: async추가
켜면 이 엔드포인트는 즉시 HTTP 202를 반환하며, 이는 "접수되어 생성 대기열에 들어갔다"는 의미일 뿐이고 생성은 백그라운드에서 계속됩니다:
{
"success": true,
"data": {
"request_id": "ac_20260920092714315816800d76fv3nq",
"status": "queued",
"accepted_at": 1789896434,
"pre_consumed_quota": 300000,
"tasks": [
{ "index": 0, "status": "queued" },
{ "index": 1, "status": "queued" }
]
}
}
접수 단계에서는
song_id가 반환되지 않습니다. 이 시점에는 실제 곡 ID가 없고,request_id와tasks[].index모두 곡 ID가 아닙니다.song_id로 저장하지 마세요. 실제song_id는 아래 진행 조회에 나타납니다.
접수 진행 조회
엔드포인트: GET /api/music/requests/{request_id}(무료, 같은 API Key로 인증)
curl "https://www.suno-api.io/api/music/requests/ac_20260920092714315816800d76fv3nq" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"success": true,
"data": {
"request_id": "ac_20260920092714315816800d76fv3nq",
"status": "processing",
"tasks": [
{ "index": 0, "status": "processing", "song_id": "f3fa09f5-3d55-4953-826c-0d23f043ec57" },
{ "index": 1, "status": "processing", "song_id": "5fcafe3e-3fae-4d64-8f66-c01bc027b0c3" }
]
}
}
status 값과 의미:
| status | 의미 |
|---|---|
queued |
접수됨. 아직 특정 곡에 연결되지 않음(tasks가 빈 배열일 수 있음) |
processing |
생성에 제출됨 |
partial |
두 곡 중 하나가 완료됨 |
completed |
두 곡 모두 완료되어 tasks[].song_id를 읽을 수 있음 |
failed |
제출 실패. 요금은 전액 환불되었고 error_message에 사유가 표시됨 |
각 작업에는 자체 tasks[].status도 있으며 값은 곡 조회 API와 같습니다:
pending(제출됨, 생성 중), processing, completed, failed. 작업 항목에는 title,
duration, 이미 등록된 audio_url이 함께 올 수 있으며, 없으면 아직 생성되지 않은 것입니다.
실제 song_id를 받은 뒤에는 곡 조회 API나 다운로드 엔드포인트로 이어가면 됩니다.
Python 예시
import time
import requests
BASE = "https://www.suno-api.io"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json"}
accepted = requests.post(
f"{BASE}/api/music/create",
headers={**HEADERS, "Idempotency-Key": "your-own-unique-key"},
json={"description": "경쾌한 팝 한 곡", "model": "suno-v6", "accept_mode": "async"},
timeout=300,
).json()["data"]
request_id = accepted["request_id"]
while True:
time.sleep(5)
progress = requests.get(
f"{BASE}/api/music/requests/{request_id}", headers=HEADERS, timeout=60
).json()["data"]
if progress["status"] in ("completed", "failed"):
print(progress)
break
호환성과 주의 사항
accept_mode를 붙이지 않으면 동작은 기존과 완전히 같습니다: 동기로 기다리고200과 곡 배열을 반환합니다. 기존 연동 측은 코드를 바꿀 필요가 없습니다.- 이 스위치는 현재
POST /api/music/create와POST /api/music/create/custom에서만 동작하며, 다른 생성 계열 엔드포인트는 아직 지원하지 않습니다. - 함께
Idempotency-Key(직접 정하는 고유 문자열, 128자 이하)를 보내는 것을 권장합니다. 같은 키의 재전송은 한 번만 접수되고 재생 시 같은 영수증이 반환되며, 요금이 중복 청구되지 않습니다. 비동기 모드에서는 특히 중요합니다. accept_mode=async일 때wait_completion은 무시됩니다(영수증이 즉시 반환되고 곡 완성을 기다리지 않음).- 요금은 접수 시점에 선차감되며, 생성 실패 시 전액이 자동으로 환불됩니다.
- 텍스트 필드 길이 상한:
description은 최대 3000자(유니코드 문자 기준, 한자 1자는 1로 계산)입니다. 초과하면 곧바로 HTTP400이 반환되고 생성에 들어가지 않으며 요금도 부과되지 않습니다. 오류 메시지에는 실측 글자 수가 함께 옵니다. 예:description must not exceed 3000 characters; received 4120. 이 엔드포인트에는 가사/스타일 필드가 없습니다. 그 필드가 필요한 경우 프로 모드 생성 API의 "텍스트 필드 길이 상한"을 참고하세요. 공통 규칙은 새 API 개요의 "텍스트 필드 상한"에 있습니다. references를 붙이지 않으면 동작, 가격, 응답이 기존과 완전히 같습니다: 기존 연동 측은 코드를 바꿀 필요가 없습니다.- 참고 자료를 붙일 때(특히 참고 곡이 여러 개일 때)는 동기의 긴 대기를 피하려고
accept_mode=async접수 영수증 모드로 전환하는 것을 권장합니다. 참고 자료 준비 시간은 자료 수와 출처에 따라 달라집니다.
다음 단계
응답 시점에 곡이 아직 완료되지 않았다면 곡 조회 API로 최종 결과를 조회하세요.
Node/Electron 연동 사례
Electron 메인 프로세스, Node.js 서버, 데스크톱 클라이언트에서 일반 생성을 연동할 때는 아래 흐름을 권장합니다:
POST /api/music/create를 호출해 생성 작업을 만듭니다.- 응답에서
song_id또는id를 읽습니다. - 2초마다
POST /api/music/query를 호출해 상태를 조회합니다. - 조회 결과에
audio_url이 나타나면 생성이 완료된 것으로 봅니다.
옛 경로인 POST /api/generate나 GET /api/clip?id=...는 더 이상 사용하지 마세요. 현재 권장하는 공개 연동 경로가 아닙니다.
const http = require('http');
const https = require('https');
const BASE_URL = 'https://www.suno-api.io';
const MODEL = 'suno-v6';
const POLL_INTERVAL_MS = 2000;
const MAX_POLL_ATTEMPTS = 60;
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function requestJson(urlString, apiKey, body) {
const url = new URL(urlString);
const transport = url.protocol === 'https:' ? https : http;
const payload = JSON.stringify(body);
return new Promise((resolve, reject) => {
const req = transport.request(
{
method: 'POST',
hostname: url.hostname,
port: url.port || undefined,
path: `${url.pathname}${url.search}`,
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload),
},
},
(res) => {
let raw = '';
res.setEncoding('utf8');
res.on('data', (chunk) => {
raw += chunk;
});
res.on('end', () => {
const data = raw ? JSON.parse(raw) : {};
if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
reject(new Error(`Suno API request failed: HTTP ${res.statusCode} ${raw}`));
return;
}
resolve(data);
});
}
);
req.on('error', reject);
req.write(payload);
req.end();
});
}
function getPayload(response) {
return response && response.data !== undefined ? response.data : response;
}
function toSongList(payload) {
if (Array.isArray(payload)) return payload;
if (Array.isArray(payload?.songs)) return payload.songs;
if (Array.isArray(payload?.clips)) return payload.clips;
return payload ? [payload] : [];
}
function collectSongIds(createResponse) {
return toSongList(getPayload(createResponse))
.map((song) => song.song_id || song.id)
.filter(Boolean);
}
async function generateSunoSong({ apiKey, description, instrumental = false }) {
const createResponse = await requestJson(`${BASE_URL}/api/music/create`, apiKey, {
description,
model: MODEL,
instrumental,
wait_completion: false,
});
const songIds = collectSongIds(createResponse);
if (songIds.length === 0) {
throw new Error(`Suno API did not return song ids: ${JSON.stringify(createResponse)}`);
}
for (let attempt = 0; attempt < MAX_POLL_ATTEMPTS; attempt += 1) {
await sleep(POLL_INTERVAL_MS);
const queryResponse = await requestJson(`${BASE_URL}/api/music/query`, apiKey, {
song_ids: songIds,
model: MODEL,
});
const songs = toSongList(getPayload(queryResponse));
const completed = songs.find((song) => song.audio_url);
if (completed) {
return {
audioUrl: completed.audio_url,
title: completed.song_title || completed.title || 'Suno Song',
duration: completed.duration,
};
}
const allFailed = songs.length > 0 && songs.every((song) =>
['failed', 'error', 'rejected'].includes(String(song.status || '').toLowerCase())
);
if (allFailed) {
throw new Error(`Suno generation failed: ${JSON.stringify(songs)}`);
}
}
throw new Error('Suno generation timed out after 120 seconds. Please query again later.');
}
// Usage:
// generateSunoSong({
// apiKey: process.env.SUNO_API_KEY,
// description: 'A bright pop song about the ocean in summer',
// }).then(console.log).catch(console.error);
실제 프로젝트에서는 API Key를 서버 환경 변수, 데스크톱 클라이언트 설정 또는 안전한 저장소에 두고, 프런트엔드 코드에 넣거나 저장소에 커밋하지 마세요.