# 完整歌曲合成
> 把续写产生的多个分段拼成一首完整歌曲，异步任务
> **只接受续写（extend）的产物**，普通生成整首会被拒
> 拼接本身不收费，续写各段按各自价格单独计费
`POST /v1/music/generations/concat`
## 认证
- `Authorization` (string / 必填) — 所有接口均使用 Bearer Token 进行认证。 获取 API Key：[访问控制台](https://api.oairoute.com/keys) 创建。 请求时在请求头中添加： ```http Authorization: Bearer YOUR_OAIRoute_KEY ```
## 请求参数
- `model` (string / 可选) — 音频模型，默认 `suno`，当前只支持 `suno`。
- `task_id` (string / 必填) — 源任务的 task_id，**必须是 extend 或 extendMusic 续写的产物**。 缺失、不是续写结果、或源无法解析时，提交阶段直接返回 400。
- `audio_index` (integer / 可选) — 指定引用源任务结果 `data.result.music[]` 中的第几首，默认 `1`。一次生成一般返回两首。
- `audio_format` (string / 可选) — 输出音频格式，取值 `mp3`、`m4a`、`wav`。
## 前置条件
这条限制最容易踩：**拼接只对续写的分段有效**。
| 源任务类型 | 能否拼接 |
|---|---|
| extend 续写产物 | 可以 |
| extendMusic 续写产物 | 可以 |
| 一次性生成的整首歌 | **不行**，返回 400 |
想做整首歌，正确流程是先生成一段，再续写若干段，最后拼接。
## 响应
- `code` (integer) — 响应状态码，成功为 200。
- `data` (array) — 提交成功后为单元素数组。
- `data[].status` (string) — 任务状态，当前提交成功只会返回 `submitted`。
- `data[].task_id` (string) — 新任务的唯一标识，用于后续轮询。
## 计费说明
拼接操作本身**不收费**。但生成续写片段时调用的 extend 接口，按各自的价格单独计费。
所以整首歌的总成本 = 首次生成 + 各次续写，拼接这一步是免费的。
## 轮询
用 `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` 给出原因，并自动退回预扣额度。
## 完整流程
```python
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"])
```
> **Note:** 
> 续写时的 `continue_at` 建议设在源音频结束前 1 到 2 秒，让两段有重叠，拼接处更自然。设成正好等于结尾，接缝容易听出来。
## 错误码
错误响应的格式、状态码与重试建议见[错误码与限流](../../../support/errors.html)，常见报错的排查步骤见[常见问题](../../../support/faq.html)。
## 使用示例
提交一个任务并拿到 `task_id`：
> **Note:** 
> 提交成功只表示任务已受理。真正的产物要拿返回的 `task_id` 去任务查询接口轮询，`status` 变成 `completed` 之后才有结果。
**完整歌曲合成{Full song concat}::cURL**
```
curl --request POST \
  --url 'https://api.oairoute.com/v1/music/generations/concat' \
  --header 'Authorization: Bearer YOUR_OAIRoute_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "model": "suno",
    "task_id": "task_01K8SGYNNNVBQTXNR4MM964S7K",
    "audio_index": 1,
    "audio_format": "wav"
  }'
```
**完整歌曲合成{Full song concat}::Python**
```
import requests
res = requests.post(
    "https://api.oairoute.com/v1/music/generations/concat",
    headers={
        "Authorization": "Bearer YOUR_OAIRoute_KEY",
        "Content-Type": "application/json",
    },
    json={
        "model": "suno",
        "task_id": "task_01K8SGYNNNVBQTXNR4MM964S7K",
        "audio_index": 1,
        "audio_format": "wav",
    },
)
print(res.json()["data"][0]["task_id"])
```
**完整歌曲合成{Full song concat}::JavaScript**
```
const url = "https://api.oairoute.com/v1/music/generations/concat";
const options = {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_OAIRoute_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "model": "suno",
    "task_id": "task_01K8SGYNNNVBQTXNR4MM964S7K",
    "audio_index": 1,
    "audio_format": "wav"
  }),
};
fetch(url, options)
  .then((res) => res.json())
  .then((json) => console.log(json))
  .catch((err) => console.error(err));
```
**200 完整歌曲合成{Full song concat}**
```
{
  "code": 200,
  "data": [
    { "status": "submitted", "task_id": "task_01K8D11M0HP1GJWBJYZMKX1XS1" }
  ]
}
```
