업로드 후 커버 API
인터페이스 설명
업로드 후 커버는 오디오 파일이나 오디오 URL을 제출하고, 그 소스 오디오를 바탕으로 새 커버 버전을 생성합니다.
엔드포인트: POST /api/music/upload-cover
과금: 유료, 현재 기본 1회 0.6위안. 이 엔드포인트는 "업로드 후 커버"이며, 이미지 커버 업로드가 아닙니다. 실제 청구 금액은 현재 백오피스 가격 설정을 따릅니다.
Max Mode / Variety: 이 엔드포인트는 선택 파라미터 max_mode와 variety(0-4)를 지원합니다.
max_mode를 켜면 이번 호출은 2배 가격으로 과금되며(0.6 → 1.2위안/회), variety는 추가 과금이 없습니다.
두 값의 의미와 사용법은 새 API 개요의 "고급 파라미터" 절을 참고하세요.
대기 시간과 타임아웃(중요): 이 엔드포인트는 동기 방식이며, 응답이 돌아온 시점에야 주문이 접수되어 생성에 넘겨집니다. 실측 기준 대부분의 요청은 1~3초에 반환되고, 약 10%는 30초를 넘기며, 최장 약 230초가 걸립니다. 클라이언트 타임아웃을 300초 이상으로 설정하세요. 응답이 없다고 해서 생성되지 않은 것이 아니며, 타임아웃 직후 재시도하면 주문이 중복되고 요금이 두 번 청구됩니다. 연결이 끊겼다면 잠시 후
GET /api/music/songs로 확인할 수 있습니다. 자세한 내용은 새 API 개요의 "대기 시간과 타임아웃 설정"을 참고하세요.
오디오가 클 때는 "업로드"와 "커버"를 2단계로 나누는 것을 권장합니다
이 엔드포인트는 오디오 전체를 요청 본문에 넣어야 합니다. 오디오가 클수록 본문도 커지고, 네트워크가 흔들리거나 타임아웃이 나면 전송 중간에 끊겨 서버가
unexpected end of JSON input(HTTP 400, 과금 없음)을 반환합니다.오디오가 약 8MB를 넘거나 네트워크가 불안정하거나 같은 소스 소재를 재사용한다면 2단계 방식으로 바꾸세요.
POST /api/music/upload-source로 소스 오디오를 먼저 업로드 — 무료, 언제든 재시도 가능 — 하고clip_id를 받습니다.POST /api/music/generate-from-source로 그clip_id를 사용해 커버를 시작 — 과금되는 단계의 요청 본문은 약 250바이트뿐입니다.자세한 내용은 소스 오디오 업로드 API, 소스 clip 기반 커버 API, 그리고 이 페이지 끝의 "대용량 파일과 요청 잘림"을 참고하세요.
요청 파라미터
Headers
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| Authorization | string | 예 | Bearer YOUR_API_KEY |
| Content-Type | string | 예 | application/json |
Body 파라미터
| 파라미터 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
| model | string | 아니오 | suno-v6 | 대고객 모델명: suno-v6, suno-v6-wild 또는 suno-v6-mini |
| file_url | string | 아니오 | - | 소스 오디오 URL |
| file_base64 | string | 아니오 | - | Base64 오디오 |
| file_name | string | 아니오 | - | 파일 이름 |
| prompt | string | 아니오 | - | 새 가사 또는 편곡 방향 |
| style | string | 아니오 | - | 스타일 태그 |
| title | string | 아니오 | - | 제목 |
| negativeTags | string | 아니오 | - | 네거티브 스타일 |
| instrumental | boolean | 아니오 | false | 연주곡 여부 |
| waitAudio | boolean | 아니오 | false | 오디오 완료를 기다릴지 여부 |
| cover_start_s | number | 아니오 | - | 커버 구간 시작 초 |
| cover_end_s | number | 아니오 | - | 커버 구간 종료 초 |
| audio_weight | number | 아니오 | - | 참조 오디오 의존도, 범위 0.0-1.0. 참조 오디오를 지정한 경우에만 보낼 수 있습니다 |
cover_start_s와 cover_end_s의 단위는 초이며 소수도 넣을 수 있습니다. 시작 시각은 종료 시각보다 작아야 하고 소스 오디오 길이를 넘을 수 없습니다. 두 필드를 모두 생략하면 오디오 전체가 대상입니다. audio_weight는 Suno의 metadata.control_sliders.audio_weight에 매핑되어 생성 결과가 참조 오디오에 얼마나 기대는지를 제어합니다. 값은 0.0-1.0이고, 요청에 유효한 file_url 또는 file_base64 참조 오디오가 있을 때만 실을 수 있습니다. 0은 유효한 값이며, 필드를 생략하면 "지정하지 않음"으로 Suno 기본 동작을 따릅니다. instrumental과 waitAudio는 반드시 JSON 불리언을 넘기고 문자열은 넘기지 마세요.
업로드 엔드포인트는 기본적으로 비동기로 반환합니다. 응답의 clips[].status가 pending일 때 audio_url이 비어 있는 것은 정상이며 업로드나 커버 실패를 뜻하지 않습니다. 반환된 실제 song_id를 저장하고, 곡 조회 엔드포인트로 완료가 될 때까지 폴링한 뒤 오디오 주소를 읽으세요.
요청 예시
curl -X POST https://www.suno-api.io/api/music/upload-cover \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "suno-v6",
"file_url": "https://example.com/source.mp3",
"file_name": "source.mp3",
"prompt": "새 가사 또는 편곡 방향",
"style": "Mandopop, acoustic guitar",
"title": "커버 버전",
"negativeTags": "noise, low quality",
"audio_weight": 0.5,
"instrumental": false,
"waitAudio": false,
"cover_start_s": 0,
"cover_end_s": 60
}'
응답 결과
{
"code": 200,
"message": "success",
"data": {
"upload": {
"clip_id": "uploaded_clip_id",
"status": "complete"
},
"clips": [
{
"song_id": "new_clip_id",
"song_title": "커버 버전",
"status": "pending"
}
]
}
}
대용량 파일과 요청 잘림
권장: 업로드와 커버를 2단계로 나누기
한 번에 처리하는 엔드포인트(이 엔드포인트)는 업로드와 생성을 한 단계로 합칩니다. 즉 큰 body가 유일한 단계에 나타나므로, 중간에 끊기면 생성됐는지도, 과금됐는지도 판단하기 어렵습니다.
| 1단계 업로드 | 2단계 커버 | |
|---|---|---|
| 엔드포인트 | POST /api/music/upload-source |
POST /api/music/generate-from-source |
| 과금 | 무료(업로드만, 생성 없음) | 1회 0.6위안. Max Mode를 켜면 1회 1.2위안 |
| 요청 본문 | 오디오 자체(base64 / multipart) 또는 file_url로 링크만 |
약 250바이트(sourceClipId 하나만 전달) |
| 반환 | clip_id(소스 오디오의 clip ID) |
data.clips[].song_id(생성 결과) |
| 실패하면 | 그대로 재시도. 과금 없음, 생성 작업도 생기지 않음 | 클라이언트 타임아웃을 300초 이상으로. 타임아웃 시 먼저 /api/music/songs로 확인하고 바로 재시도하지 않기 |
| 소재 재사용 | 같은 clip_id를 이어쓰기 / 구간 교체 / 분리에도 사용 |
매번 오디오를 다시 올려야 함 |
결론: 오디오가 약 8MB를 넘거나 네트워크가 불안정하거나 같은 소재를 여러 번 쓴다면 → 2단계 방식. 오디오가 작고 네트워크가 안정적이라면 → 이 엔드포인트가 더 간편합니다.
400 unexpected end of JSON input이란
의미는 서버가 받은 JSON이 불완전하다는 것입니다(파라미터 오류도, 권한이나 잔액 문제도 아닙니다). 흔한 원인을 확률 순으로 정리하면 다음과 같습니다.
- 큰 body가 전송 중 잘렸다: 접속 계층은 "클라이언트가 보내겠다고 선언한 양"과 "실제로 도착한 양"을 함께 기록합니다. 실측 사례에서는 클라이언트 선언이 14.17MB인데 실제 도착은 11.8~12.4MB였고 세 번의 요청이 모두 약 1.6~1.7초에서 멈췄습니다. 부족한 바이트 수가 매번 달랐다는 점에서 고정 상한이 아니라 전송 중단임을 알 수 있습니다(서버는 요청 본문 크기를 제한하지 않습니다).
Content-Length와 실제 전송 바이트 수가 불일치: Node.js에서 문자열의.length(UTF-16 문자 수)를 길이로 쓰면 중국어 등 멀티바이트 내용에서 틀립니다.- body를 Buffer가 아니라 문자열로 쓴 경우 실제 바이트 수가 선언과 맞지 않습니다.
세 경우 모두 과금 전에 발생하므로 비용은 생기지 않습니다.
클라이언트 자체 점검 목록
-
Content-Length는Buffer.byteLength(json, 'utf8')(또는Buffer.from(json).length)로 구한다.json.length는 쓰지 않는다. - 요청 본문은 Buffer로 쓴다(문자열로 쓰지 않는다).
- socket / 요청 타임아웃은 300초 이상.
- 오디오가 약 8MB를 넘으면:
file_url로 바꾸거나(본문이 수백 바이트),upload-source의 multipart 업로드를 쓰거나, 2단계 방식으로 간다. - multipart 요청에서는
Content-Type을 직접 설정하지 않는다(boundary는 클라이언트가 붙이게 한다). - "실제 전송한 바이트 수"를 출력해
Content-Length와 비교한다. - 2단계 방식에서는 1단계가
clip_id를 반환하고status가 완료 상태인지 확인한 뒤 2단계를 보낸다.
자주 나오는 오류 대조표
| 오류 | 의미 | 대응 |
|---|---|---|
unexpected end of JSON input |
요청 본문이 불완전(잘림) | 위 자체 점검을 수행하거나 2단계 방식으로 전환 |
invalid character '-' in numeric literal |
multipart body를 JSON 엔드포인트로 보냈다 | JSON 엔드포인트에서 multipart를 쓰지 않는다. Content-Type을 직접 설정하지 않는다 |
one of file_path, file_base64, file_url, or multipart file is required |
오디오가 첨부되지 않았다 | file_base64 / file_url / multipart file을 추가 |
model is required |
요청 본문이 빈 정상 JSON(예: {}) |
최소한 model을 넣는다 |
Invalid request: Invalid request, ... |
본문 파싱 실패의 래퍼 메시지 | 뒤쪽의 구체적 이유를 확인(대개 위 항목 중 하나) |
관련 엔드포인트: 소스 오디오 업로드 API(1단계), 소스 clip 기반 커버 API(2단계).