# Suno 通用约定与任务查询
> Suno 系列的公共约定：认证、版本、异步流程、任务查询与结果结构
> 所有生成、编辑、工具接口都是异步任务
> 统一从提交响应的 `data[0].task_id` 取任务号
`GET /v1/music/tasks/{task_id}`
## 认证
- `Authorization` (string / 必填) — 所有接口均使用 Bearer Token 进行认证。 所有 Suno 接口共用。 获取 API Key：[访问控制台](https://api.oairoute.com/keys) 创建。 请求时在请求头中添加： ```http Authorization: Bearer YOUR_OAIRoute_KEY ```
## 查询参数
- `task_id` (string / 必填) — 提交任务时返回的任务 ID。
- `language` (string / 可选) — 失败信息的返回语言，例如 `zh`。**只影响错误文案的本地化**，不改变歌曲或歌词的语言。
> **Note:** 
> 当前没有取消已提交 Suno 任务的端点。任务一旦提交就会执行完，客户端的中断只会停止本地等待。
## 版本与模型选择
公共模型有三个版本：
| 版本 | 特点 |
|---|---|
| `v6` | 标准 |
| `v6-wild` | 风格更发散 |
| `v6-mini` | 更快、更便宜 |
主生成接口必须二选一：传 `version`，或传创建模型任务返回的 `custom_model_id`。两者互斥，同时发送会报错。
编辑类操作省略 `version` 时回落为 `v6`。
> **Note:** 
> `custom: true` 表示自定义歌词模式，`custom_model_id` 表示已训练的自定义模型。两者含义完全不同，前者不会自动切到自定义模型的价格档。
## 通用 V6 生成选项
这些字段只在具体端点明确列出时才发送。**不支持某字段的操作应完全省略它**，不要给所有操作统一塞一个默认值。
- `variety` (string / 可选) — 控制风格变化幅度。取值 `off`、`normal`、`high`、`extra`、`max`。 `variety` 与 Max 计费互相独立，选 `variety: "max"` 不会自动开启 Max 计费。
- `max_mode` (boolean / 可选) — Max 模式，默认 `false`。支持时按普通售价 2 倍计费。
- `audio_format` (string / 可选) — 输出音频格式。取值 `mp3`、`m4a`、`wav`。
- `duration` (integer / 可选) — 目标生成时长，10 到 360 秒。仅「生成」类操作支持，实际成品不保证一致。
## 文本长度与权重限制
| 内容 | 字段（按端点区分） | 限制 |
|---|---|---|
| 灵感描述 | `prompt` / `gpt_description` | 不超过 3000 字符 |
| 歌词 | `prompt`（自定义模式） | 不超过 5000 字符 |
| 风格 | `style` / `tags` | 不超过 1000 字符 |
| 标题 | `title` | 不超过 80 字符 |
| 权重 | `style_weight` / `weirdness` / `audio_weight` | 0 到 1，数值 `0` 有效 |
字符数按 Unicode 字符计算。
权重字段名按端点不同：操作接口的新请求用 `weirdness`，`weirdness_constraint` 作兼容别名；主生成仍用 `weirdness_constraint`。
## 引用源音轨
编辑类操作需要指向一段已生成的音频。
- `task_id` (string / 必填) — 产出该源音轨的任务 ID。
- `audio_index` (integer / 可选) — 源任务响应中 `data.result.music[]` 的原始位置，**从 1 起**，默认 `1`。
必须用 `task_id` 加 `audio_index` 引用，不能换成 `audio_id`、`music_id` 或 `audio_url`。源任务无法解析或索引越界时返回 400。
> **Note:** 
> 返回几首就展示几首，不要写死为 2。即使前端调整了展示顺序，后续操作仍要按**原始索引**引用，否则会改错曲目。
## 提交响应
```json
{
  "code": 200,
  "data": [
    {
      "status": "submitted",
      "task_id": "task_01K8SGYNNNVBQTXNR4MM964S7K"
    }
  ]
}
```
`submitted` 仅代表已受理，不代表已有音频。
## 查询响应字段
- `code` (integer) — 响应状态码。
- `data.id` (string) — 本平台任务 ID。
- `data.status` (string) — 任务状态。取值 `pending`、`processing`、`completed`、`failed`、`unknown`。 完成与否以这个字段判定，不要看 `progress`。
- `data.progress` (number) — 进度，通常 0 到 100。变化不保证线性。
- `data.created` (number) — 创建时间，Unix 秒。
- `data.estimated_time` (number) — 预计耗时，单位秒。仅供参考，不是倒计时承诺。
- `data.cost` (number) — 任务金额，美元。
- `data.credits_cost` (number) — 消耗积分。直接使用返回值展示，不要自己换算。
- `data.result` (object) — 完成后结果。结构随操作类型变化。
- `data.error` (object) — 失败信息。
## result.music[] 元素字段
- `audio_id` (string) — 音频标识。
- `status` (string) — 示例中为 `complete`。
- `title` (string) — 曲目标题。
- `lyrics` (string) — 歌词文本，含段落标记。
- `tags` (string) — 风格标签，原始形式。
- `display_tags` (string) — 展示用风格标签。
- `negative_tags` (string) — 排除风格。
- `style_weight` (number) — 风格权重。
- `weirdness_constraint` (number) — 怪异度权重。
- `audio_weight` (number) — 音频权重。
- `duration` (number) — 实际时长，允许小数。
- `audio_url` (string) — 音频地址。
- `image_url` (string) — 封面图。
- `image_large_url` (string) — 大图封面。
## 结果处理要点
**`duration` 是成品实际时长**，可能与请求的目标时长不同。
**权重返回 `0` 是合法值**，不能被 JS 的 falsy 判断吞掉。
**`audio_url` 不保证带 `.mp3` 后缀**，按返回的实际 URL 播放或下载，不要自己加扩展名。
**`status` 为 `unknown` 时**保留任务 ID 并允许手动刷新，**不要自动新建任务**，否则会重复扣费。
## 轮询与重试
建议首次等待约 3 秒，之后每 5 到 10 秒查一次，直到 `completed` 或 `failed`。
- 页面刷新后可用同一任务 ID 继续查询
- 网络超时不要自动重发收费的 POST
- `AbortController` 只能中断浏览器端请求与轮询，不会取消服务端任务
## 失败响应
```json
{
  "code": 200,
  "data": {
    "id": "task_01K8SGYNNNVBQTXNR4MM964S7K",
    "status": "failed",
    "progress": 0,
    "cost": 0,
    "credits_cost": 0,
    "error": {
      "message": "歌词包含不允许的内容",
      "type": "task_failed",
      "code": "task_failed",
      "param": ""
    }
  }
}
```
## 错误码
错误响应的格式、状态码与重试建议见[错误码与限流](../../../support/errors.html)，常见报错的排查步骤见[常见问题](../../../support/faq.html)。
## 使用示例
提交一个任务并拿到 `task_id`：
> **Note:** 
> 提交成功只表示任务已受理。真正的产物要拿返回的 `task_id` 去任务查询接口轮询，`status` 变成 `completed` 之后才有结果。
**通用约定与任务查询{Conventions and polling}::cURL**
```
curl --request GET \
  --url 'https://api.oairoute.com/v1/music/tasks/task_01K8SGYNNNVBQTXNR4MM964S7K?language=zh' \
  --header 'Authorization: Bearer YOUR_OAIRoute_KEY'
```
**通用约定与任务查询{Conventions and polling}::Python**
```
import time, requests
def wait_music(task_id, interval=6, timeout=300):
    deadline = time.time() + timeout
    while time.time() < deadline:
        r = requests.get(
            f"https://api.oairoute.com/v1/music/tasks/{task_id}",
            headers={"Authorization": "Bearer YOUR_OAIRoute_KEY"},
            timeout=30,
        )
        d = r.json()["data"]
        if d["status"] == "completed":
            return d["result"]["music"]
        if d["status"] == "failed":
            raise RuntimeError(d.get("error", {}).get("message"))
        time.sleep(interval)
    raise TimeoutError(task_id)
```
**通用约定与任务查询{Conventions and polling}::JavaScript**
```
const url = "https://api.oairoute.com/v1/music/tasks/task_01K8SGYNNNVBQTXNR4MM964S7K?language=zh";
const options = {
  method: "GET",
  headers: {
    "Authorization": "Bearer YOUR_OAIRoute_KEY",
  },
};
fetch(url, options)
  .then((res) => res.json())
  .then((json) => console.log(json))
  .catch((err) => console.error(err));
```
**200 通用约定与任务查询{Conventions and polling}**
```
{
  "code": 200,
  "data": [
    { "status": "submitted", "task_id": "task_01K8SGYNNNVBQTXNR4MM964S7K" }
  ]
}
```
