完整歌曲合成
- 把续写产生的多个分段拼成一首完整歌曲,异步任务
- 只接受续写(extend)的产物,普通生成整首会被拒
- 拼接本身不收费,续写各段按各自价格单独计费
认证
All endpoints authenticate with a Bearer token.
Get an API key: open the console and create one.
Add it to the request header:
Authorization: Bearer YOUR_OAIRoute_KEY请求参数
音频模型,默认 suno,当前只支持 suno。
源任务的 task_id,必须是 extend 或 extendMusic 续写的产物。
缺失、不是续写结果、或源无法解析时,提交阶段直接返回 400。
指定引用源任务结果 data.result.music[] 中的第几首,默认 1。一次生成一般返回两首。
输出音频格式,取值 mp3、m4a、wav。
The audio model; defaults to suno, currently the only supported value.
The source task's task_id, which must be the output of an extend or extendMusic continuation.
When it's missing, isn't an extension result, or the source can't be resolved, submission returns a 400.
Which track in the source task's data.result.music[], default 1. A single generation usually returns two.
Output audio format: mp3, m4a, or wav.
前置条件
这条限制最容易踩:拼接只对续写的分段有效。
| 源任务类型 | 能否拼接 |
|---|---|
| extend 续写产物 | 可以 |
| extendMusic 续写产物 | 可以 |
| 一次性生成的整首歌 | 不行,返回 400 |
想做整首歌,正确流程是先生成一段,再续写若干段,最后拼接。
This is the easiest restriction to trip over: concatenation only works on extended segments.
| Source task type | Can concatenate? |
|---|---|
| extend continuation output | Yes |
| extendMusic continuation output | Yes |
| A whole song from one generation | No, returns a 400 |
To build a whole song, the correct flow is: generate one segment, extend it several times, then concatenate.
响应
响应状态码,成功为 200。
提交成功后为单元素数组。
任务状态,当前提交成功只会返回 submitted。
新任务的唯一标识,用于后续轮询。
The response status code; 200 on success.
A single-element array on successful submission.
The task status; a successful submission currently returns only submitted.
The new task's unique identifier, for later polling.
计费说明
拼接操作本身不收费。但生成续写片段时调用的 extend 接口,按各自的价格单独计费。
所以整首歌的总成本 = 首次生成 + 各次续写,拼接这一步是免费的。
The concatenation operation itself is free. But the extend calls used to produce each continuation segment are billed separately at their own prices.
So the full song's total cost = the first generation + each extension; the concatenation step is free.
轮询
用 GET /v1/music/tasks/{task_id},间隔 3 到 5 秒,典型耗时 30 到 120 秒。
status 取值 pending、processing、completed、failed,只有 completed 或 failed 是终态。progress 是 0 到 100 的整数,不保证按固定节点变化。
完成后从 data.result.music[].audio_url 取音频。失败时 data.error.message 给出原因,并自动退回预扣额度。
Use GET /v1/music/tasks/{task_id}, every 3 to 5 seconds; typical duration is 30 to 120 seconds.
status is pending, processing, completed, or failed; only completed or failed are terminal. progress is an integer from 0 to 100 that isn't guaranteed to change on fixed steps.
On completion, take the audio from data.result.music[].audio_url. On failure, data.error.message gives the reason and the held balance is refunded automatically.
完整流程
import time, requests
BASE = "https://api.oairoute.com"
HEAD = {"Authorization": "Bearer YOUR_OAIRoute_KEY",
"Content-Type": "application/json"}
def wait(task_id):
while True:
d = requests.get(f"{BASE}/v1/music/tasks/{task_id}",
headers=HEAD).json()["data"]
if d["status"] == "completed":
return d
if d["status"] == "failed":
raise RuntimeError(d.get("error"))
time.sleep(5)
# 1. 生成第一段
r = requests.post(f"{BASE}/v1/music/generations", headers=HEAD, json={
"model": "suno", "custom": True, "version": "v6",
"prompt": "[Verse]\n第一段歌词", "tags": "folk",
})
first = r.json()["data"][0]["task_id"]
base = wait(first)
# 2. 续写第二段
r = requests.post(f"{BASE}/v1/music/generations/extend", headers=HEAD, json={
"model": "suno", "task_id": first, "audio_index": 1,
"continue_at": base["result"]["music"][0]["duration"] - 2,
"custom": True, "prompt": "[Verse]\n第二段歌词", "tags": "folk",
})
second = r.json()["data"][0]["task_id"]
wait(second)
# 3. 拼接
r = requests.post(f"{BASE}/v1/music/generations/concat", headers=HEAD, json={
"model": "suno", "task_id": second, "audio_index": 1,
"audio_format": "wav",
})
print("拼接任务", r.json()["data"][0]["task_id"])续写时的 continue_at 建议设在源音频结束前 1 到 2 秒,让两段有重叠,拼接处更自然。设成正好等于结尾,接缝容易听出来。
import time, requests
BASE = "https://api.oairoute.com"
HEAD = {"Authorization": "Bearer YOUR_OAIRoute_KEY",
"Content-Type": "application/json"}
def wait(task_id):
while True:
d = requests.get(f"{BASE}/v1/music/tasks/{task_id}",
headers=HEAD).json()["data"]
if d["status"] == "completed":
return d
if d["status"] == "failed":
raise RuntimeError(d.get("error"))
time.sleep(5)
# 1. Generate the first segment
r = requests.post(f"{BASE}/v1/music/generations", headers=HEAD, json={
"model": "suno", "custom": True, "version": "v6",
"prompt": "[Verse]\nFirst verse lyrics", "tags": "folk",
})
first = r.json()["data"][0]["task_id"]
base = wait(first)
# 2. Extend a second segment
r = requests.post(f"{BASE}/v1/music/generations/extend", headers=HEAD, json={
"model": "suno", "task_id": first, "audio_index": 1,
"continue_at": base["result"]["music"][0]["duration"] - 2,
"custom": True, "prompt": "[Verse]\nSecond verse lyrics", "tags": "folk",
})
second = r.json()["data"][0]["task_id"]
wait(second)
# 3. Concatenate
r = requests.post(f"{BASE}/v1/music/generations/concat", headers=HEAD, json={
"model": "suno", "task_id": second, "audio_index": 1,
"audio_format": "wav",
})
print("concat task", r.json()["data"][0]["task_id"])Set continue_at during extension 1 to 2 seconds before the source audio ends, so the segments overlap and the join sounds natural. Setting it exactly at the end makes the seam audible.
错误码
For error response formats, status codes, and retry guidance see Error codes and rate limits; for troubleshooting steps on common errors see the FAQ.
使用示例
提交一个任务并拿到 task_id:
提交成功只表示任务已受理。真正的产物要拿返回的 task_id 去任务查询接口轮询,status 变成 completed 之后才有结果。
Submit a task and get a task_id:
A successful submission only means the task was accepted. To get the actual output, poll the task query endpoint with the returned task_id; results appear only after status becomes completed.