# 下载音频文件
> 按任务 ID 下载生成好的音频，支持多格式，异步任务
> 一次请求可以同时拿 mp3、m4a、wav
> 多格式只收一次费用
`POST /v1/music/generations/download`
## 认证
- `Authorization` (string / 必填) — 所有接口均使用 Bearer Token 进行认证。 获取 API Key：[访问控制台](https://api.oairoute.com/keys) 创建。 请求时在请求头中添加： ```http Authorization: Bearer YOUR_OAIRoute_KEY ```
## 请求参数
- `model` (string / 可选) — 模型名称，省略时按 `suno` 处理。
- `task_id` (string / 必填) — 生成源歌曲时返回的任务 ID。 源任务须属于当前账户、已完成、且含可下载音轨。音乐生成、延长、翻唱、分轨等任务都可以下载；**只返回文本的任务（歌词、BPM 等）不可下载**。
- `audio_index` (integer / 可选) — 指定源任务结果 `data.result.music[]` 中的第几首，默认 `1`，从 1 开始计数。越界会报错。
- `formats` (array / 可选) — 要下载的格式数组，至少一项。取值 `mp3`、`m4a`、`wav`。 可多选，大小写不敏感，重复值自动去重，结果顺序与请求顺序一致。与 `format` 二选一。
- `format` (string / 可选) — 只下载单一格式时代替 `formats` 使用，例如 `"mp3"`。 `formats` 与 `format` 均缺失或列表为空时返回 400。
## 提交响应
- `code` (integer) — 状态码，成功为 200。
- `data[].status` (string) — 初始值固定为 `submitted`。
- `data[].task_id` (string) — **新建的下载任务 ID**，与请求中的源歌曲 task_id 不同。轮询时用这个。
> **Note:** 
> 这一步容易搞混：提交下载后返回的是一个**新任务 ID**。拿原来的 task_id 去查，永远查不到下载结果。
## 查询下载任务
用 `GET /v1/music/tasks/{下载任务ID}`。建议提交后立即查一次，非终态则每 2 秒轮询，最多 60 秒。
- `data.status` (string) — 任务状态，取值 `submitted`、`processing`、`completed`、`failed`。
- `data.progress` (integer) — 进度百分比。
- `data.created` (integer) — 创建时间，Unix 时间戳。
- `data.completed` (integer) — 完成时间，Unix 时间戳，仅完成时出现。
- `data.actual_time` (integer) — 实际耗时，单位秒。
- `data.cost` (number) — 费用。失败任务为 0。
- `data.credits_cost` (number) — 积分消耗。
- `data.result` (object) — 结果对象，**仅 `completed` 状态存在**，`processing` 时没有这个字段。
- `data.result.music_id` (string) — 歌曲唯一标识。
- `data.result.files` (array) — 下载文件列表，新代码应读取这个字段。
- `data.result.files[].format` (string) — 文件格式。
- `data.result.files[].url` (string) — 文件下载地址。**可能有有效期，需尽快保存**。
- `data.result.wavUrl` (string) — 兼容旧 WAV 接口的字段，请求 WAV 时仍可能返回。不建议新代码使用。
- `data.error.message` (string) — 失败原因，可展示给用户并提供重试。
## 计费规则
| 情况 | 计费 |
|---|---|
| 一次请求选多个格式 | **只收一次费用** |
| 再次提交同一首歌，即使格式相同 | 产生新费用 |
| 换另一种格式下载 | 需新建任务，也产生新费用 |
| 下载失败 | 自动退款，`cost` 为 0 |
所以想拿多个格式，一次请求全选上最划算。分两次下载同一首歌会被收两遍钱。
## 错误处理
HTTP 400 属于提交期参数错误，**不创建任务、不扣费**。常见对照：
| 错误信息 | 含义 |
|---|---|
| `formats is required` / `must contain at least one` | 未提供下载格式 |
| `unsupported format` | 包含 mp3、m4a、wav 之外的格式 |
| `task_id is required` / `invalid task_id format` | 源任务 ID 缺失或格式错误 |
| `source task not found` | 源任务不存在或不属于当前账户 |
| `audio_index N out of range` | 歌曲序号超出源任务音轨数量 |
| `track #N has no music_id` | 源任务未完成或该歌曲无音频 |
| HTTP | 说明 |
|---|---|
| 401 | 身份验证失败，检查 API 密钥 |
| 402 | 账户余额不足，需充值 |
| 403 | 模型价格未配置，错误码 `model_price_not_configured`，需联系平台支持 |
| 429 | 请求过于频繁，稍后重试 |
| 500 | 服务器内部错误，稍后重试 |
> **Note:** 
> 旧接口 `POST /v1/music/generations/wav` 已弃用，暂仍兼容，等价于新接口传 `formats: ["wav"]`。新代码请用本接口。
## 错误码
错误响应的格式、状态码与重试建议见[错误码与限流](../../../support/errors.html)，常见报错的排查步骤见[常见问题](../../../support/faq.html)。
## 使用示例
提交一个任务并拿到 `task_id`：
> **Note:** 
> 提交成功只表示任务已受理。真正的产物要拿返回的 `task_id` 去任务查询接口轮询，`status` 变成 `completed` 之后才有结果。
**下载音频文件{Download audio file}::cURL**
```
curl --request POST \
  --url 'https://api.oairoute.com/v1/music/generations/download' \
  --header 'Authorization: Bearer YOUR_OAIRoute_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "model": "suno",
    "task_id": "task_01K8SGYNNNVBQTXNR4MM964S7K",
    "audio_index": 1,
    "formats": ["mp3", "wav"]
  }'
```
**下载音频文件{Download audio file}::Python**
```
import time, requests
BASE = "https://api.oairoute.com"
HEAD = {"Authorization": "Bearer YOUR_OAIRoute_KEY",
        "Content-Type": "application/json"}
# 1. 提交下载
r = requests.post(f"{BASE}/v1/music/generations/download", headers=HEAD, json={
    "model": "suno",
    "task_id": "task_01K8SGYNNNVBQTXNR4MM964S7K",
    "audio_index": 1,
    "formats": ["mp3", "wav"],
})
dl_id = r.json()["data"][0]["task_id"]     # 注意：这是新的下载任务 ID
# 2. 轮询，建议 2 秒一次，最多 60 秒
for _ in range(30):
    d = requests.get(f"{BASE}/v1/music/tasks/{dl_id}",
                     headers=HEAD).json()["data"]
    if d["status"] == "completed":
        for f in d["result"]["files"]:
            print(f["format"], f["url"])    # 尽快转存
        break
    if d["status"] == "failed":
        raise RuntimeError(d.get("error", {}).get("message"))
    time.sleep(2)
```
**下载音频文件{Download audio file}::JavaScript**
```
const url = "https://api.oairoute.com/v1/music/generations/download";
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,
    "formats": [
      "mp3",
      "wav"
    ]
  }),
};
fetch(url, options)
  .then((res) => res.json())
  .then((json) => console.log(json))
  .catch((err) => console.error(err));
```
**200 下载音频文件{Download audio file}**
```
{
  "code": 200,
  "data": [
    { "status": "submitted", "task_id": "task_01K8D66M0HP1GJWBJYZMKX1XS1" }
  ]
}
```
