# Music API 接入文档

版本：v1 · 更新：2026-10-20 · 基础地址：**https://music.op.wiki**

[在线阅读](https://music.op.wiki/docs/api/) · [OpenAPI JSON](openapi.json) · [Python 下载脚本](https://music.op.wiki/docs/api/download_song.py)

应用分流已于 2026-10-20 00:04:35（UTC+8）发布：页面、API、歌词、封面及生成中音频保持 `https://music.op.wiki`；仅 completed 普通音频和 ALAC packet 使用 `https://music.zr.wiki`（Cloudflare）。`music.op.wiki` 的入口方案为中国大陆走 AxisNow、海外走 Cloudflare，DNS 配置尚待迁移侧完成；应用配置保持不变，本次应用发布未修改 DNS/CDN。同源配置仍受支持；始终使用 API 实际返回的完整 URL，不自行换域名。

本接口提供音乐搜索、音频准备与下载、歌词、推荐和歌单解析。最常用的下载流程是：

**搜索取得歌曲 ID → 创建播放任务 → 等待 completed → 下载 audio_url。**

## 1. 下载一首歌

### 1.1 搜索并确认歌曲

下面示例使用 Apple Music；可替换平台及搜索词。

```bash
curl --fail-with-body --silent --show-error --get \
  'https://music.op.wiki/api/v1/search' \
  --data-urlencode 'platform=applemusic' \
  --data-urlencode 'q=晴天 周杰伦' \
  --data-urlencode 'limit=5'
```

返回数组，每项包含 `id`、`song`、`singer`、`cover` 和 `platform`。核对歌名、歌手及版本，把选中项的 `id` 作为下一步的 `selected_id`。不要把播放任务 ID 当作平台歌曲 ID。

### 1.2 创建任务

将下面两处占位内容替换为上一步确认的歌曲 ID 和歌名：

```bash
curl --fail-with-body --silent --show-error \
  'https://music.op.wiki/api/v1/playbacks' \
  -H 'Content-Type: application/json' \
  --data '{
    "platform": "applemusic",
    "query": "替换为歌曲名",
    "selected_id": "替换为搜索结果的id",
    "quality": "hires",
    "include_cover": false
  }'
```

普通下载**省略 `streaming_mode`**。指定了 `selected_id`，`query` 仍然必须填写非空字符串。

响应结构示意：

```json
{
  "playback": {
    "id": "applemusic--示例歌曲ID--hires",
    "status": "queued",
    "quality": "hires"
  },
  "status_url": "/api/v1/playbacks/applemusic--示例歌曲ID--hires",
  "audio_url": "/api/v1/playbacks/applemusic--示例歌曲ID--hires/audio?rev=example-native-revision",
  "cover_url": "/api/v1/playbacks/applemusic--示例歌曲ID--hires/cover",
  "lyrics_url": "/api/v1/playbacks/applemusic--示例歌曲ID--hires/lyrics"
}
```

这里省略了 `playback` 的其他字段。实际返回的 ID 和 `rev` 由服务器生成，直接使用响应中的 URL，不要照抄示例值。

- `200`：已经完成，可进入下载步骤。
- `202`：任务已接受或正在复用尚未完成的任务，需要继续查询。
- 状态以 `playback.status` 为准。命中缓存可能直接得到 `completed`。

### 1.3 等待 completed

以 API 基础地址解析响应的 `status_url`；状态请求始终留在 API 域。例如：

```bash
curl --fail-with-body --silent --show-error \
  'https://music.op.wiki/api/v1/playbacks/替换为实际任务ID'
```

查询接口直接返回任务对象，读取顶层 `status`，没有 `playback` 包装。

| status | 含义 | 下载客户端的处理 |
| --- | --- | --- |
| queued | 已排队 | 等待后再查询 |
| processing | 准备音频中 | 等待后再查询 |
| streaming | 部分流式内容已可用，整首尚未完成 | 普通文件下载继续等待 |
| completed | 音频准备完成 | 下载 audio_url |
| failed | 准备失败 | 停止轮询，显示 error |

建议每 **1–2 秒**查询一次，并设置总等待时限，例如 10 分钟。这是客户端建议，不是服务端承诺的准备时长。不要为轮询重复 POST，也不要并行发送相同状态查询。

相同状态 URL 出现多次是正常轮询，**不代表重复下载了歌曲**。`completed` 后首次读取文件仍可能需要恢复缓存或生成兼容音频，下载请求的读取超时不宜过短。

### 1.4 保存文件

仅在 `completed` 后，使用**最新状态响应**中的 `audio_url`。同源配置返回相对路径，须以 `https://music.op.wiki` 解析；双 CDN 配置返回 `https://music.zr.wiki` 的绝对媒体 URL。queued/processing/streaming 阶段的媒体地址仍为 API 域相对路径。不要沿用较早状态的旧地址，也不要自行替换域名或丢弃查询参数：

```bash
# 将最新 completed 响应的 audio_url 原样填入，保留查询参数。
AUDIO_PATH='替换为返回的audio_url'
AUDIO_URL="$(python3 -c 'import sys; from urllib.parse import urljoin; print(urljoin("https://music.op.wiki/", sys.argv[1]))' "$AUDIO_PATH")"
curl --fail --show-error --location \
  --connect-timeout 15 --max-time 900 \
  --remote-name --remote-header-name \
  "$AUDIO_URL"
```

这个响应是音频字节，不是 JSON。保存格式应以这次文件响应的 `Content-Type` 和 `Content-Disposition` 为准。

特别是 Apple Music 的 Lossless/Hi-Res：任务中的 `format` 可能是 `m4a`，而 `audio_url` 实际返回兼容的无损 `audio/flac`。**不要硬改成 .mp3，也不要使用任务的 byte_size 校验兼容文件长度。**应使用文件响应自身的 `Content-Length`（若有）。

`audio_url` 返回无损兼容文件，不提供“保证原始 ALAC 容器字节不变”的下载接口。`source_url` 是原平台来源链接，不是本服务下载地址。

### 1.5 用脚本自动完成

下载本页附带的 [download_song.py](https://music.op.wiki/docs/api/download_song.py)，仅依赖 Python 3 标准库：

```bash
# 用上一步的真实歌曲 ID 替换占位内容。
python3 download_song.py \
  --platform applemusic \
  --query '歌曲名 歌手' \
  --id '搜索结果中的id' \
  --quality hires
```

也可以只传某首歌的公开平台链接，由服务端解析：

```bash
python3 download_song.py \
  --platform applemusic \
  --query '替换为完整的Apple Music单曲链接' \
  --quality lossless
```

不传 `--id` 而只给搜索词时，平台会自行解析结果，不能保证就是你想要的版本；需要精确下载时先搜索并传 ID。省略 `--output` 会根据文件响应命名；可用 `--output ./指定文件名.flac` 自行指定正确扩展名。脚本不会覆盖已有文件，下载失败会清理自己创建的临时文件。

多首兼容 FLAC 的服务端文件名可能同为 `browser-lossless.flac`；下载第二首时请用 `--output` 指定不同文件名，或保存到不同目录。

`--timeout` 控制任务准备总时限，默认 600 秒；`--read-timeout` 控制音频响应的单次网络等待，默认 120 秒，首次生成兼容文件较慢时可调大，例如 `--read-timeout 900`。运行 `python3 download_song.py --help` 查看选项。脚本会串行查询状态、等待整首完成、流式写入文件，保留所选音质；不会一次把整首音频读入内存。

脚本默认以 `https://music.op.wiki` 解析相对地址，并已信任其返回的 `https://music.zr.wiki` 完成媒体地址；两种配置均无需修改下载器或额外传入 `--media-base-url`，脚本不会改写返回的 URL。API 轮询只访问 `--base-url`。自建服务若使用其他媒体域，显式传入 `--base-url https://api.example.com --media-base-url https://media.example.com`。所有请求不会携带 Cookie、Authorization 或浏览器会话。脚本使用明确的 `MusicAPI-Downloader` User-Agent；不需要伪装浏览器，也不要转发个人登录信息。

## 2. 通用约定

| 项目 | 约定 |
| --- | --- |
| 基础地址 | `https://music.op.wiki` |
| 路径前缀 | `/api/v1`；健康检查为 `/health` |
| 请求 / 响应 | JSON 使用 UTF-8；音频和封面接口返回二进制 |
| 认证 | 当前部署不要求 API Key 或调用者登录 |
| 请求头 | JSON POST 需要 `Content-Type: application/json` |
| 浏览器头 | 不需要复制 Cookie、Referer、sec-ch-ua 或伪装 User-Agent |
| URL | 同时支持相对路径与绝对 URL，使用 `new URL(task.audio_url, base)` 或 Python `urljoin(base, task["audio_url"])`；保留全部查询参数 |
| 完成媒体 | 双 CDN 配置下，仅 completed 的 `/audio` 与 `/audio/stream` 使用 `https://music.zr.wiki`；未配置媒体域时保持同源，始终以 API 返回的 URL 为准 |
| 动态请求 | queued/processing/streaming 的媒体地址、页面、API、状态、歌词和封面保持 `https://music.op.wiki` |
| 时间 / 大小 | API 的 duration 为秒，byte_size 为字节，bitrate 为 bit/s |
| 空值 | 可选元数据可能为 null 或空字符串；调用方应允许将来新增字段 |

媒体使用匿名 GET/HEAD/Range，不需要 Cookie 或 Authorization。独立媒体域的跨域访问须允许浏览器读取 `Content-Disposition`、`Content-Length`、`Content-Range`、`Accept-Ranges`、`Content-Encoding` 和 `X-Audio-Stream-Id`；命令行 curl/Python 不受浏览器 CORS 限制。状态、搜索、推荐和歌词始终请求 API 基础地址；媒体域不是新的 API 入口。

同一平台、歌曲、音质通常复用同一任务；ID 不是用户专属，也不是鉴权凭据。应把它当成不透明字符串保存，不依赖其内部拼接规则。不同音质分别创建任务，播放/下载时提交当前所需音质。

当前没有取消任务、批量下载、账户管理或服务器端私人歌单管理接口。关闭连接不等于取消服务端准备任务。任务不是永久下载凭证；若状态返回 404，可以用原始平台、歌曲 ID、query 和音质重新创建。

## 3. 平台与音质

### 平台

使用 `GET /api/v1/platforms` 获取实时列表和别名。当前规范 ID：

| 平台 | platform |
| --- | --- |
| Apple Music | applemusic |
| Bilibili | bilibili |
| 酷狗音乐 | kugou |
| 网易云音乐 | netease |
| 汽水音乐 | soda |
| QQ 音乐 | tencent |
| YouTube Music | youtubemusic |

平台出现在列表中不表示所有曲目、地区和音质始终可用；平台能力及上游状态可能不同。推荐接口目前仅支持 Apple Music；歌单/专辑解析目前支持 Apple Music、网易云。

### 音质

| quality | 建议用途 |
| --- | --- |
| standard | 标准音质 |
| high | 高品质；默认值 |
| lossless | 无损音质 |
| hires | 高解析无损音质 |

外部接入建议使用上表小写规范值。`quality` 表示请求档位，并不保证源曲目拥有对应采样率或编码。Apple Music 的 Lossless 优先选择最高 48 kHz 以内的无损变体，Hi-Res 优先选择更高采样率；其他平台按自身能力处理。实际文件格式以下载响应为准，不把 quality 当作扩展名。

## 4. 接口速查

| 方法 | 路径 | 用途 |
| --- | --- | --- |
| GET | /health | 服务健康检查 |
| GET | /api/v1/platforms | 平台列表 |
| GET | /api/v1/search | 搜索歌曲 |
| POST | /api/v1/playbacks | 创建或复用播放任务 |
| GET | /api/v1/playbacks/{id} | 查询任务状态 |
| GET | /api/v1/playbacks/{id}/audio | 普通播放 / 文件下载 |
| GET | /api/v1/playbacks/{id}/audio/stream | 专用 WASM ALAC 分包流 |
| GET | /api/v1/playbacks/{id}/cover | 封面 |
| GET | /api/v1/lyrics | 通过平台歌曲 ID 获取歌词 |
| GET | /api/v1/playbacks/{id}/lyrics | 通过任务获取歌词 |
| GET | /api/v1/playbacks/{id}/loudness | 响度测量 |
| GET | /api/v1/recommendations | 推荐歌曲 |
| GET | /api/v1/recommendations/home | 推荐首页分区 |
| GET / POST | /api/v1/recommendations/continue | 队列续播候选 |
| GET | /api/v1/collections/resolve | 解析公开歌单 / 专辑 |
| POST | /api/v1/collections/match | 跨平台歌曲匹配 |

机器可读参数与数据模型见 [OpenAPI JSON](openapi.json)，可导入支持 OpenAPI 3.0 的接口工具。以下为接入时常用说明。

## 5. 搜索、任务和下载接口

### GET /health

返回：

```json
{"status":"ok","service":"music-api"}
```

它确认服务可响应，不代表所有上游平台的下载能力均已通过检查。

### GET /api/v1/platforms

返回数组：`[{ "id": "applemusic", "label": "Apple Music", "aliases": ["am", "..."] }]`。示例中的省略号仅用于简写；实际 aliases 是完整别名数组。

### GET /api/v1/search

| 参数 | 必填 | 默认 / 限制 | 说明 |
| --- | --- | --- | --- |
| platform | 是 | 平台 ID 或别名 | 搜索平台 |
| q | 是 | 建议提供非空关键词 | 歌名、歌手等搜索内容 |
| limit | 否 | 10；按 1–20 裁剪 | 结果数量上限 |

返回 `SearchItem[]`：

```json
[
  {
    "platform": "applemusic",
    "id": "示例歌曲ID",
    "song": "示例歌名",
    "singer": "示例歌手",
    "cover": "https://example.com/cover.jpg"
  }
]
```

`cover` 可能为空字符串。搜索不创建播放任务。

### POST /api/v1/playbacks

| JSON 字段 | 必填 | 默认 | 说明 |
| --- | --- | --- | --- |
| platform | 是 | 无 | 平台 ID / 别名 |
| query | 是 | 无 | 非空歌曲名、链接或平台 ID |
| selected_id | 否 | null | 精确指定搜索结果 ID，优先于 query 解析 |
| quality | 否 | high | 建议 standard / high / lossless / hires |
| include_cover | 否 | true | 是否请求准备封面；只下载音频建议 false |
| streaming_mode | 否 | null | 仅专业播放器使用 wasm_alac；普通下载省略 |

返回 `{playback, status_url, audio_url, cover_url, lyrics_url}`。字段 URL 即使在 queued 状态也会出现，**URL 存在不代表文件已就绪**。同任务复用时，已有任务的封面准备方式等设置不一定被新的请求覆盖。

应用只识别 `wasm_alac` 作为特殊传输模式，其他值按未指定处理。不要通过未知 mode 请求其他输出格式。

### GET /api/v1/playbacks/{id}

返回 `PlaybackTask`，核心字段如下。所有可空字段在早期状态可能没有值。

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| id / status | string | 不透明任务 ID / 五种任务状态 |
| platform / query / quality | string | 规范平台、原始查询和请求音质 |
| track_id / selected_id | string 或 null | 解析后的平台歌曲 ID；未手动传 selected_id 时也可能填入解析结果 |
| include_cover | boolean | 任务的封面准备设置 |
| title / artist / album | string 或 null | 歌名 / 歌手 / 专辑 |
| source_url | string 或 null | 平台来源链接，不是音频下载 URL |
| format / content_type | string 或 null | 任务源文件元信息，可能与兼容 audio 响应不同 |
| byte_size / bitrate / duration | number 或 null | 源文件字节数、bit/s、秒 |
| decrypt_elapsed_seconds | number 或 null | 解密耗时，秒；非所有路径都有 |
| error | string 或 null | failed 时的失败信息 |
| streaming_mode | string 或 null | 协商的传输模式 |
| streaming_codec / streaming_protocol | string 或 null | ALAC 时为 alac / splayer-alac-v2 |
| media_revision | string | 不透明的本次媒体生成标识；不是内容哈希，也不表示永久不可变 |
| audio_url | string | 标准音频地址；completed 时可为配置媒体域的绝对 URL，其他状态或同源配置返回 API 域相对路径 |
| cover_url / lyrics_url | string | API 域相对路径 |
| streaming_url | string 或 null | 专用分包地址，不是常规音乐文件；completed 时可为配置媒体域的绝对 URL，生成中或同源配置返回相对路径 |

任务在多个客户端间共享：即使本次创建没有请求 WASM，后续状态查询也可能带回另一客户端协商的 `wasm_alac` 字段。普通下载客户端仍按 **completed → audio_url** 处理，不自动改用 streaming_url。

### GET /api/v1/playbacks/{id}/audio

- `completed`：返回可供常规播放器使用的音频。首次访问可能仍需恢复远端缓存或准备无损兼容文件。
- `streaming` 且是原生 AAC 路径：可以返回持续增长的 `audio/mp4`，供支持它的播放器使用；普通下载仍建议等待 completed。
- `streaming` 且是 WASM ALAC 路径：该端点返回 `409`，等待 completed；分包播放器使用下一节的专用地址。
- queued、processing、failed 等未完成状态：`409`。未知任务：`404`。

常见成功响应：

```http
HTTP/1.1 200 OK
Content-Type: audio/flac
Content-Disposition: inline; filename="browser-lossless.flac"
Content-Length: 12345678
Accept-Ranges: bytes
Cache-Control: public, max-age=86400, no-transform
```

上面长度仅为示例。接口使用 `inline`；浏览器直接打开可能播放音频，而非弹出“另存为”。下载程序应主动保存响应体。

完成的本地文件支持单段 Range：

```http
Range: bytes=0-1048575
Range: bytes=1048576-
Range: bytes=-4096
```

每次只使用其中一种。`206` 响应通过 `Content-Range` 表示返回范围。多段、越界或无效范围可能返回 `416`；仍在增长的流不支持随机 Range。远端缓存路径是否返回 206 取决于上游响应，不能无条件假定续传成功。

需要续传时，可用 `curl --continue-at - --output 已有部分文件 "$AUDIO_URL"`，并确认服务器接受范围请求；不要把返回的 200 完整文件直接拼接到旧内容后。完成成功媒体使用 `Cache-Control: public, max-age=86400, no-transform`，可按正常浏览器/CDN 缓存语义读取，不要额外发送 `Cache-Control: no-store`。生成中音频使用 `no-store, no-transform`；动态 API、未就绪及错误响应使用 `no-store`。

任务 ID（包括旧客户端的 `v=任务ID` 参数）不是内容哈希。任务的 `media_revision` 表示本次媒体生成实例，返回 URL 中的 `rev` 进一步区分普通音频与 ALAC packet 表示。`rev` 在同一次生成从 streaming 变为 completed 时保持不变，重新生成后可能改变。它仍不是内容哈希，不能作为无限缓存依据。保留完整 URL；旧 `rev` 与当前版本不符时，源站返回 `409` 与 `no-store`，重新读取任务状态获得当前地址。旧客户端不传 `rev` 仍兼容，但没有此版本保护。

续传时还须确认 `X-Audio-Stream-Id` 与旧文件一致，并检查 `206`、`Content-Range`、总长度及无内容编码；身份不符时重新下载，不拼接两次生成的文件。缓存中的历史完成版本可能在 24 小时内继续由 CDN 提供，因此应结合版本 URL 和文件身份校验，不仅依赖任务 ID。

### GET /api/v1/playbacks/{id}/audio/stream

高级接口，仅用于明确支持本项目协议的解码器：

- 创建时请求 `streaming_mode: "wasm_alac"`，但最终以服务器返回的 transport 字段为准；不保证每个平台/文件都提供它。
- 返回 `application/x-splayer-alac`，协议标记 `SPLALAC1`，版本 2，包含 ALAC cookie 和分包长度、时长、数据。
- 可在 streaming 时持续接收并解码；completed 后可读取完整分包文件。
- 未协商该模式返回 `406`；尚无可用流返回 `409`。
- 这不是 MP3、FLAC、普通 M4A 或标准 HLS。不要将它直接交给普通 `<audio>`，也不要把保存结果改名为这些格式。
- 完整分包流的 Range 与普通音频文件的 Range 是两种不同表示，不能拼接混用。
- 分包流在生成中使用 API 域；双 CDN 配置下，completed 后的完整分包 URL 使用媒体域，同源配置仍使用 API 域。保留全部参数，缓存及身份校验要求与普通完成音频相同，不能因换域而跳过 `rev`、`X-Audio-Stream-Id` 或 Range 校验。

下载普通歌曲文件无需调用此接口。

### GET /api/v1/playbacks/{id}/cover

返回图片字节。任务未完成返回 `409`，没有封面或文件不可用返回 `404`。`include_cover: true` 也不能保证每首歌必有封面；搜索结果中的 `cover` URL 可作为展示来源。

## 6. 歌词和响度

### GET /api/v1/lyrics

| 参数 | 必填 | 说明 |
| --- | --- | --- |
| platform | 是 | 平台 ID |
| track_id | 是 | 平台歌曲 ID，非空 |
| title | 否 | 歌名；提供后有助于元数据匹配与补充来源 |
| artist / album | 否 | 歌手 / 专辑 |
| duration | 否 | 时长，非负整数，单位秒 |

无需先创建音频任务，适合与音频准备并行请求。

### GET /api/v1/playbacks/{id}/lyrics

使用已有任务的歌曲信息取歌词，无需重复传入标题、歌手等字段。两种歌词接口都返回：

```json
{
  "platform": "applemusic",
  "track_id": "示例歌曲ID",
  "available": true,
  "plain": "[00:00.00]示例歌词",
  "translation": ""
}
```

文本通常为逐行 LRC，不承诺逐字时间或翻译一定存在。找不到歌词通常是 `200` 配合 `available: false`、空文本，不能只用 HTTP 状态判断是否有歌词。

### GET /api/v1/playbacks/{id}/loudness

返回响度测量状态：

```json
{"state":"pending","version":1}
```

就绪示例：

```json
{
  "state": "ready",
  "version": 1,
  "integrated_lufs": -18.5,
  "true_peak_dbfs": -2.0,
  "gain_db": 1.0
}
```

`state` 为 pending / ready / unavailable；非 ready 时数值字段省略。`integrated_lufs` 单位 LUFS，`true_peak_dbfs` 虽保留这个字段名，实际测量单位为 dBTP，`gain_db` 为建议增益 dB。

接口只分析服务端本地已存在的完整音频，不为测量而额外下载源文件；pending 可稍后查询，unavailable 时客户端保持原音量即可。此接口不是下载歌曲的必需步骤。

## 7. 推荐接口

推荐目前只支持 `applemusic`。它使用服务端共享推荐源，**不是 API 调用者自己的个人账号推荐**，不要求调用者提交 Apple Music Cookie，也没有收听历史回传接口。

### GET /api/v1/recommendations

返回 `SearchItem[]`。

| 参数 | 默认 | 说明 |
| --- | --- | --- |
| platform | applemusic | 当前仅支持此平台 |
| limit | 20 | 1–50，超出范围会裁剪 |
| refresh | false | 请求刷新推荐来源 |
| exclude | 空字符串 | 逗号分隔的已见歌曲 ID，最多保留 500 个有效 ID |

`exclude` 仅保留 1–20 位、非全零的数字 ID，去重后忽略无效项。`refresh=true` 不保证上游立刻变化或所有结果全新；建议持续携带已见 ID，结果数量可能少于 limit。

### GET /api/v1/recommendations/home

参数与上节相同，返回：

```json
{
  "source": "applemusic-personal",
  "tags": [],
  "playlists": [],
  "tracks": []
}
```

- `tracks`：`SearchItem[]`。
- `tags`：每项含 `id, label, subtitle, playlist_ids[]`。
- `playlists`：每项含 `id, resource_type, name, curator, description, cover, url, recommendation_id`。
- `limit` 控制歌曲数量，不是卡片数量。
- 歌单/专辑卡片通过 `collections/resolve` 解析其 URL，再逐首创建任务。点击卡片、解析卡片本身不会自动下载整份歌单。

### GET / POST /api/v1/recommendations/continue

根据当前歌曲取得队列续播候选；队列较长时优先使用 POST：

```http
POST /api/v1/recommendations/continue
Content-Type: application/json
```

```json
{
  "platform": "applemusic",
  "seed_id": "替换为当前歌曲的数字ID",
  "exclude": ["已在队列中的数字ID", "已经播放的数字ID"],
  "limit": 10
}
```

上面 ID 为占位值，需换成实际数字 ID。`seed_id` 必填且必须是 1–20 位非全零数字 ID；`platform` 默认 applemusic，`limit` 默认 10、裁剪到 1–50，`exclude` 默认空数组、最多保留 10000 个有效 ID。种子歌曲会自动排除。

GET 使用同名查询参数，`exclude` 改为逗号分隔字符串。返回 `{ "source": "...", "tracks": [...] }`，source 可能为 applemusic-related 或 applemusic-personal。候选可能混合相关推荐和共享推荐补充，不能把 source 当成逐首推荐理由。

推荐接口返回 `Cache-Control: no-store`。它们不创建音频任务、不修改客户端队列；调用方自己决定如何入队和播放。

## 8. 歌单与跨平台匹配

### GET /api/v1/collections/resolve

| 参数 | 必填 | 说明 |
| --- | --- | --- |
| platform | 是 | 目前支持 applemusic、netease |
| url | 是 | 公开歌单/专辑链接或平台支持的资源 ID |
| limit | 否 | 1–5000；不传则完整解析，超过 5000 首明确失败 |

返回 `{platform,id,title,source_url,items: SearchItem[]}`。显式 limit 用于预览；想导入完整歌单时不要无意设置较小 limit。

Apple Music 支持歌单 URL、`pl.*` ID、专辑 URL 和 `album:<数字ID>`。带 `?i=歌曲ID` 的单曲链接不应作为歌单解析。网易云支持歌单/专辑 URL，以及 `playlist:<ID>`、`album:<ID>`。

该接口只返回歌曲元数据，不创建下载任务，也不把歌单保存到服务端。页面“我的歌单”和播放队列属于网页本地数据。

### POST /api/v1/collections/match

```json
{
  "source_platform": "netease",
  "target_platform": "applemusic",
  "items": [
    {
      "id": "源平台歌曲ID",
      "song": "示例歌名",
      "singer": "示例歌手",
      "cover": ""
    }
  ]
}
```

单次 1–200 首；`cover` 可省略，默认空字符串。超过 200 首时客户端分批提交并保持原顺序。

返回字段：`source_platform,target_platform,matched_count,review_count,missing_count,items`。每项为：

```json
{
  "source": {"id":"源平台歌曲ID","song":"示例歌名","singer":"示例歌手","cover":""},
  "status": "review",
  "confidence": 82,
  "candidates": []
}
```

此处为字段结构示意。`status` 为 matched / review / missing，confidence 为 0–100，candidates 为目标平台 `SearchItem[]`。

review 建议让用户确认候选；missing 也可能来自搜索失败或超时，不等于已证明曲目不存在。同平台匹配直接保留输入并返回 matched/100，并不重新验证每个 ID。接口不创建任务、不下载音频、不修改歌单；选定候选后使用其目标平台 ID 创建任务。

## 9. 错误与重试

业务错误通常返回：

```json
{"detail":"错误说明"}
```

但缺少必填字段、JSON 类型错误、错误 Content-Type 等可能由框架直接返回文本，网关错误也可能不是 JSON。客户端先判断 HTTP 状态和 Content-Type，再尝试读取 detail；JSON 解析失败时保留简短文本错误。

| HTTP 状态 | 常见含义 / 建议 |
| --- | --- |
| 200 | 成功；文件接口为二进制，其他接口通常为 JSON |
| 202 | 创建任务已接受，继续查状态 |
| 206 | 文件 Range 请求成功，按 Content-Range 处理 |
| 400 | 参数、平台能力或业务条件不满足，阅读 detail |
| 404 | 任务或文件不存在；封面缺失也属于此类 |
| 406 | 请求专用 ALAC 流，但任务未协商该模式 |
| 409 | 文件/流尚未就绪，先检查任务状态 |
| 416 | Range 无效、越界或不支持多段 |
| 415 / 422 | 可能的请求类型或 JSON 字段校验错误 |
| 500 / 502 / 503 | 服务或上游暂时异常，适度退避重试 |

`HTTP 200 + status: failed` 是任务失败，不是音频准备成功。显示 task.error，停止轮询；用户选择重试时可以重新 POST 原请求。同 ID 正在准备时不要通过不断 POST 来“催进度”。

建议对短暂网络/5xx 错误退避重试，例如 2、4、8 秒；避免高并发重复请求同一歌曲。当前文档不承诺固定的并发配额、每秒请求数或下载完成时限。

## 10. 常见问题

**为什么拿到链接仍下载不了？**  
任务创建时就返回 URL；等待 completed，再访问 audio_url。409 通常表示尚未准备好。

**Hi-Res 只能下载到 FLAC，是降音质了吗？**  
ALAC 到 FLAC 是无损兼容表示转换，不能仅凭扩展名判断降音质。源曲目实际可用规格决定内容；接口不承诺每首歌都有高采样率版本。

**能否直接使用 streaming_url 下载？**  
可取得专用分包数据，但它不是常规音乐文件。普通下载使用 audio_url。

**需要把浏览器复制出来的整串请求头都带上吗？**  
不需要。正常 GET 无需浏览器伪装头，JSON POST 带 Content-Type 即可。

**是否需要调用者提供自己的 Apple Music 账号或会员信息？**  
当前接入不需要这些凭据；调用服务公开接口即可。上游可用性由服务端配置及平台状态决定。

**能不能按 API 一键下载整个歌单？**  
没有批量下载接口。先解析歌单，再逐首执行下载流程；客户端负责进度、失败记录和并发控制。

**状态请求出现了三次，是下载了三遍吗？**  
不是。状态响应是小型 JSON，音频位于另一条路径。直到任务可用或完成前都可能需要轮询。

