公开音乐 API
本文件全部接口为 GET。除音频接口外均返回 JSON,不要求管理员登录。全局设备/IP封禁和访问策略可能使任意 /api/* 返回 403/503;单接口下方只列主要分支,不重复全部通用错误。
1. 健康与目录
1.1 GET /api/health
无查询参数。
{"status":"ok","app":"Wristhyme","version":"2.1.1","access":"public"}
200 只表示该网关处理了健康请求,不验证上游搜索、插件、音频或完整播放。version 示例来自网关快照,不是当前 Android 版本。启用访问策略后健康接口仍可能被封禁/策略错误拦截。
1.2 GET /api/sources
无查询参数。200:
{
"sources":[
{"id":"qq","name":"QQ音乐","backend":"qq","plugin":""},
{"id":"netease","name":"网易云音乐","backend":"netease","plugin":""}
],
"catalog_revision":1790812800,
"default_plugin":"0123456789abcdef"
}
- 只返回启用目录项,且自定义别名的基础平台也必须启用。
- 关联
plugin禁用时返回空;关联插件只是推荐信息,不代表客户端必须或已经切换。 default_plugin是当前有效默认,空字符串表示没有有效默认。客户端使用稳定别名进行解析,避免保存一个随后变化的真实默认 ID。catalog_revision是策略投影的 Unix 秒时间戳,不是严格递增事务序号;同秒修改可能相同,不能作为唯一变更检测依据。- 基础七平台有特定展示顺序;不要要求响应永远恰好七条。
- 无启用项时可以正常返回
sources: [];客户端不得硬编码补回禁用项。
2. 通用搜索与歌单
2.1 GET /api/search
| 查询参数 | 类型 | 必需 | 默认/范围 |
|---|---|---|---|
source |
string | 是 | /api/sources 返回的 ID;可为自定义别名 |
q |
string | 是 | 空白折叠后1..100字符,不能以http:/https:开头 |
page |
integer字符串 | 否 | 网易云默认1,范围1..20;其他平台当前通用接口忽略此字段 |
refresh |
string | 否 | 仅 1 绕过搜索缓存;其他值按非刷新处理 |
网易云每次最多20首,其他普通平台最多60首;哔哩哔哩最多20个视频首分P结果。自定义 source 先映射到 backend,响应歌曲和顶层 source 保持真实平台。
普通平台 200:
{
"source":"qq",
"songs":[
{"key":"11111111111111111111111111111111","ref":"<SIGNED_REF>","name":"示例歌曲","artist":"示例歌手","album":"示例专辑","duration":"240","source":"qq","original":false,"cover":""}
],
"note":"",
"query":"示例歌曲",
"state":"ok"
}
网易云 200 在上述结构上增加:
{
"page":1,
"page_size":20,
"total":120,
"has_more":true,
"search_adapter":"netease-signals-221",
"ranking_scope":"current_page",
"ranking_note":"原版和热度缺乏已验证字段时为未知;版本仅依标题/别名线索,平台次序只作稳定兜底。",
"dedup":{
"duplicate_ids_removed":0,
"duplicate_reissues_collapsed":1,
"policy":"same-title-artist-version-duration-span-1000ms",
"scope":"current-page",
"raw_count":20,
"display_count":19
}
}
示例是扩展字段片段,完整响应仍包含 songs/source/query/state/note。total 可为 null;去重后歌曲数可以少于 page_size。分页依据上游总量,而非返回列表长度;第20页可能仍报告上游有更多,但本接口不能继续请求第21页。
空结果为200、songs:[]、state:empty,可能附平台限制提示。平台搜索错误不能视为空结果,网易云或哔哩哔哩显式错误分支为502、state:error、songs:[]、error。
主要错误:400参数不合法;403禁用;429繁忙;502上游失败。非空搜索缓存90秒。没有通用跨源聚合搜索参数,也没有可自定义 page_size 的公开合同。
2.2 GET /api/playlist_search
| 参数 | 类型 | 必需 | 默认/范围 |
|---|---|---|---|
source |
string | 否 | 默认 netease;可用目录平台/别名 |
q |
string | 是 | 空白折叠后1..100字符,不接受http:/https:开头 |
200:{playlists: Playlist[], source: string},最多20条。没有公开分页、刷新、排序或个性化参数;某个平台有适配器不等于它一定支持歌单搜索。空数组表示当前未读取到歌单。
主要错误:400参数无效;403禁用;429繁忙;502上游失败。
2.3 GET /api/playlist
两种输入方式:source + id,或者 link。非空 link 存在时,解析后的平台和ID覆盖 source/id。
| 参数 | 类型 | 必需 | 默认/范围 |
|---|---|---|---|
source |
string | source/id模式需要 | 启用平台/目录别名 |
id |
string | source/id模式需要 | [A-Za-z0-9_-]{1,180} |
link |
string | 否 | 下述受支持完整URL,不支持短链 |
page |
integer字符串 | 否 | 默认1,转换整数后钳制到1..100;非整数返回400 |
200:
{"songs":[],"source":"netease","id":"123456789","page":1,"has_more":false,"note":"该歌单无可读取曲目,可能为空、私密或平台受限"}
每页最多100首;不会连续抓取全歌单。has_more 优先取上游页数;没有页数标记时按本页是否达到100首估算,所以不是绝对准确的全量计数。
完整链接合同:
| 平台 | 允许主机 | ID提取形式 |
|---|---|---|
| 网易云 | music.163.com、y.music.163.com |
路径含playlist,query或fragment query中数字id;如 https://music.163.com/#/playlist?id=123456789 |
y.qq.com、i.y.qq.com |
/playlist/数字、/taoge/数字,或disstid/id数字查询参数 |
|
| 酷狗 | www.kugou.com、m.kugou.com |
/songlist/gcid_字母数字、/special/数字、/special/single/数字,或specialid数字参数 |
链接可用HTTP/HTTPS,但禁止用户名/密码及80/443以外端口;服务器只提取ID,不抓取任意用户链接、不展开跳转。整个链接必须 URL 编码,尤其是 #、? 和 &。
主要错误:400链接/ID/分页无效;403平台禁用;429繁忙;502上游失败。
3. 轻量列表
这组路径名带 watch,但也可供手机等客户端调用。固定每页10项,无自动后台预取;共用2并发槽和300秒缓存。
3.1 GET /api/watch/search
| 参数 | 必需 | 说明 |
|---|---|---|
source |
是 | 启用平台/目录别名 |
q |
是 | 与通用搜索相同的1..100字符约束 |
page |
否 | 默认1,必须1..100,越界返回400 |
refresh |
否 | 1 刷新请求页;首页刷新会清理相应搜索页缓存 |
200包含 songs: Song[]、source、page、page_size:10、has_more、query。
网易云还可包含 total/dedup/search_adapter/ranking_scope/ranking_note/note;使用独立10首的上游分页,不能混用通用20首分页的page偏移。网易云第20页强制has_more=false,第21..100页正常返回空和has_more=false。
哔哩哔哩从有界的最多20条快照切页,并非继续搜索后续平台页。其他平台依上游分页标记;若上游当前页与请求页不符,返回空页且has_more=false,防止无限重复加载。
主要错误:400参数无效;部分目录ValueError也被该模块包装为400;403禁用;429加载繁忙;502其他上游异常。因此轻量网易云失败的HTTP包装与通用搜索不完全一致。
3.2 GET /api/watch/recommend
| 参数 | 必需 | 说明 |
|---|---|---|
source |
是 | 只支持基础平台QQ/网易云,别名映射后同样适用 |
page |
否 | 默认1,范围1..100 |
refresh |
否 | 仅page=1且refresh=1重新获取推荐快照 |
200:{playlists: Playlist[], source, page, page_size:10, has_more, note}。
服务器读取最多100个去重的公开推荐歌单,客户端分页。不是登录账号个性化日推;没有账号、历史听歌、推荐模型参数。平台没有可读推荐时当前模块抛ValueError并返回400,不是必然200空数组。
主要错误:400不支持平台/页码/推荐不可读;403禁用;429繁忙;502其他上游失败。
3.3 GET /api/watch/playlist
| 参数 | 必需 | 说明 |
|---|---|---|
source |
是 | 只支持QQ/网易云 |
id |
是 | [A-Za-z0-9_-]{1,180} |
page |
否 | 默认1,范围1..100 |
refresh |
否 | 1刷新当前页,首页刷新同时清理同歌单其他页缓存 |
200:{songs: Song[], has_more, source, page, page_size:10}。没有 link输入合同,响应也不保证包含id/note。过滤基础平台不符的歌曲;发现分页标记错位时返回空列表。
主要错误:400参数无效;403禁用;429繁忙;502上游失败。
4. 热歌榜
GET /api/charts/hot
| 参数 | 必需 | 说明 |
|---|---|---|
source |
是 | qq或netease;音源别名经公共策略映射后可用 |
page |
否 | 默认1,1..100;不钳制,越界400 |
refresh |
否 | 默认0,只接受0/1;仅首页的1才清理该平台热榜缓存 |
200:
{"songs":[],"total":0,"has_more":false,"period":"2026-10-01","source":"qq","title":"QQ音乐热歌榜","chart_id":"26","page":1,"page_size":10,"note":"官方公开热歌榜 · 2026-10-01"}
此为结构示例;真实有效榜单首页通常有歌曲。QQ固定榜单26,网易云固定3778678,每页10首。period是平台更新标识/日期,不应自行当作完整精确时间戳;缺失时空字符串。
QQ按平台offset拉取并校验rank与页码;网易云最多读取1000个有效去重条目后本地切页。total 在QQ来自平台截断到0..1000,在网易云是已读有效行数。某页无数据不一定是错误;首页/应有数据的位置返回异常内容则502。
每平台独立加载锁,第二个并发请求可能429。缓存300秒;没有任意榜单ID、topid、page_size、登录个性化或媒体下载参数。禁用检查在慢请求前后都执行。
主要错误:400不支持平台/页码/refresh;403禁用;429榜单加载中;502平台内容异常/超时。
5. 恢复与原平台音频
5.1 GET /api/restore
查询参数 ref: string 必需,长度最多12000字符。200直接返回一个 Song,不包在song字段中。
400:引用为空/损坏/签名不匹配、来源无效或其平台已禁用。虽然文案可能写“签名过期”,ref没有内置TTL。签名密钥轮换会使旧ref失效;访问策略异常还可能被外层包装为其他错误。
5.2 GET /api/inspect
查询参数 key: string 必需。检查原平台,不检测后台默认插件音频。
200可能为:
{"valid":true,"size":"8.00 MB","bitrate":"267 kbps","format":"MP3","quality_note":"格式由上游地址标识;码率为估算值,不代表无损认证"}
上游早期失败可能只给valid=false,网关仍补format/quality_note;size和bitrate不保证存在。size是人类可读字符串,不是字节整数。format为FLAC/MP3/M4A/AAC/OGG/WAV/未知,根据地址后缀推断;url字段被移除。估算码率与文件扩展名不能证明无损或授权。
主要错误:410key不可用;429繁忙;502上游失败。valid=false可正常200。
5.3 GET /api/media
| 参数 | 必需 | 说明 |
|---|---|---|
key |
是 | 服务器已知歌曲 |
download |
否 | 仅1增加附件响应头;否则普通音频流 |
请求可带单一Range。成功为200/206二进制音频,常见MIME:audio/mpeg、audio/flac、audio/mp4、audio/aac、audio/ogg、audio/wav。实际类型结合字节验证,不能仅依据平台名或Content-Type。
可能透传 Content-Range、Accept-Ranges、ETag、Last-Modified、Content-Length,以真实上游存在为条件。没有保证一定提供长度、范围或校验标记。
download=1 的附件名由标题和歌手清理后组成,头格式包含ASCII后备名及UTF-8 filename*;不会创建服务器下载任务、写服务器音乐库,也不切换后台默认插件。
Range与续传:
- 只接受正则
bytes=\d*-\d*:典型bytes=0-、bytes=1024-2047、bytes=-1024;多段范围拒绝。 - 这是格式检查,不代表上游支持每种范围;非音频、续传资源变化也会失败。
- 请求了Range但响应为200时表示不能把body直接追加到旧文件,应按下载器策略从头处理。
- 上游416可能被包装为外层502 + upstream_status=416;本地格式检查416与上游不支持要区分。
- 仅Range被网关显式转发;不要依靠发送If-Range来保证服务端条件续传。客户端自行核对响应范围、ETag、长度和已保存资源身份。
主要错误:410key不可用;416本地Range不支持;429繁忙;502平台拒绝/非有效音频/资源变化;504连接失败或超时。
5.4 GET /api/playback_status
查询参数 key 必需。200示例:
{"state":"streaming","content_type":"audio/mpeg","source":"qq"}
其他状态为 {state:"unknown"} 或 {state:"error",error,source,upstream_status?}。状态有效观察窗口180秒,最多1200条进程内记录。
该状态只反映原平台 /api/media 部分取流分支,不代表客户端解码、已播放时长或完整下载。插件媒体不更新此表,部分媒体验证失败也不会写error,所以unknown/streaming不能作为最终播放验收。
主要错误:410key不可用;全局403/503。该接口不走普通六槽慢请求限流。
6. 歌词与快照
6.1 GET /api/lyric
查询参数 key 必需。200:
{"lyric":"[00:00.00]示例歌词","source":"netease","key":"11111111111111111111111111111111","lyric_adapter":"square-karaoke-1"}
上游请求format=lrc,读取最多128KiB并规范化换行/部分方括号逐字时间到增强LRC。适配不会创造缺失的逐字时间;相邻行首时间戳表示重复行,不应误当逐字标签。
此接口不是结构化逐字数组或翻译专用API;普通LRC、增强LRC和客户端支持的其他解码能力应区分。空字符串允许200,不能据此生成歌词。旧部署可能没有lyric_adapter字段;上游格式质量也不由适配标识保证。
主要错误:410key不可用;429繁忙;502上游失败。该路径调用原平台歌词,不自动跟随后台默认插件。
6.2 GET /api/snapshot
查询参数 key 必需。用于下载时取得曲目信息和歌词,不下载音频、不保存服务器文件。
200:
{"name":"示例歌曲","artist":"示例歌手","album":"示例专辑","duration":"240","source":"qq","key":"11111111111111111111111111111111","captured_at":1790812800000,"lyric":"","lyric_state":"unavailable","lyric_note":"未保存歌词:下载时平台未提供"}
captured_at为Unix毫秒。lyric_state为saved/unavailable;正常取得可接受歌词时saved,其余通过lyric_note说明超限、不可用、失败或超时。歌词抓取失败通常仍返回200及歌曲信息,不能把unavailable当作整个快照请求失败。
最多128KiB歌词,快照会检测多1字节的超限,并排除常见HTML/JSON错误内容。返回不包含ref、cover或search_signals;需要长期恢复引用时另存原Song.ref。
主要错误:410key不可用;429繁忙;外层502异常。快照的原平台歌词不保证等于插件播放来源歌词。
7. LX插件
支持平台与默认别名见 协议。第三方可能获得服务器IP及当前在线歌曲元数据;不会因为此接口取得设备本地整个音乐库。consent=1是接口请求合同,不由管理员会话代替。
7.1 GET /api/plugins
无查询参数。200:
{
"plugins":[
{"id":"0000000000000000","name":"跟随后台默认 · 示例插件","version":"1.0.0","sha256":"<SHA256>","imported":true,"status":"imported","detail":"已导入服务器,尚未运行检测","sources":{},"is_default":true,"target_plugin":"0123456789abcdef"},
{"id":"0123456789abcdef","name":"示例插件","version":"1.0.0","sha256":"<SHA256>","imported":true,"status":"imported","detail":"已导入服务器,尚未运行检测","sources":{}}
],
"runtime":"quickjs-isolated-process",
"notice":"启用后第三方将获得服务器IP和当前歌曲信息;不读取或上传设备本地音乐库。",
"default_plugin":"0123456789abcdef"
}
只显示当前启用项;默认有效时首项可能为别名,复制真实插件元数据并增加is_default/target_plugin。别名不满足“ID等于SHA256前16位”,客户端应明确识别。
状态包括imported/initialized/failed;checked_at可能存在,单位Unix秒;网关最后取流结果可能覆盖status/detail。sources来自插件初始化声明,未检测时可为空。声明样例:
{"wy":{"actions":["musicUrl","lyric"],"qualitys":["128k","320k","flac"]}}
注意字段拼写是qualitys,不是qualities。声明支持、初始化通过和每首歌实际可播放互不等价。
主要错误:503运行时/策略不可用,可能附plugins空数组。无需consent即可读目录,该接口不占插件操作双槽。
7.2 GET /api/plugin_probe
| 参数 | 必需 | 说明 |
|---|---|---|
plugin |
否 | 真实16位小写hex ID,或default/稳定别名;缺省走后台默认 |
consent |
是 | 必须为字符串1 |
200示例:{ok:true,state:"initialized",sources:{},detail:"初始化通过;不代表每首歌曲可播放"}。422示例:{ok:false,error:"ValueError",detail:"插件执行失败,可能是接口离线、授权限制或不兼容的 LX 扩展"}。
会清除网关该插件最后取流状态,再触发脚本初始化;不需要歌曲key,不验证某首音频。可产生第三方网络请求,不应后台自动连续运行。
主要错误:400缺consent/ID无效;403禁用;429频率/并发;422执行失败;503无默认;502运行时通信失败。
7.3 GET /api/plugin_resolve
| 参数 | 必需 | 说明 |
|---|---|---|
key |
是 | 服务器已知歌曲;仅五个LX映射平台 |
plugin |
否 | 同probe;推荐新受管客户端使用0000000000000000 |
consent |
是 | 1 |
quality |
否 | 默认128k,必须在该插件该平台的qualitys中;无统一固定音质枚举 |
200:
{"url":"/api/plugin_media?ticket=<TICKET>","plugin":"0123456789abcdef","state":"resolved","expires_in":3600}
url为本站相对取流路径,plugin为映射后的真实插件ID。服务器只将已缓存歌曲元数据交给插件;拒绝直接上传任意musicInfo或audioURL。返回resolved只表示获取并校验了地址,不是已拉到有效音频。
票据3600秒、512条有界缓存;无需一次性消费,可重复请求或Range,但可能因淘汰/重启提前失效。票据没有在当前实现中绑定客户端IP或设备ID,拥有者可尝试访问,应视为临时访问凭据,不传播/记录完整URL。平台/插件禁用会使已有票据取流403。
主要错误:400consent/ID/不支持平台;403禁用;410key不可用;422插件失败/不声明音质;429频率/并发;503无默认;502异常/非法地址。
7.4 GET /api/plugin_media
查询参数 ticket 必需。无需consent、plugin或key,实际平台和插件从票据读取;仍执行全局封禁和票据内音源/插件启用检查。
成功200/206音频,Range合同、内容验证与原平台媒体相似;可能透传Content-Range/Accept-Ranges/ETag/Last-Modified/Content-Length。该接口没有download=1附件命名合同,客户端下载应自行保存已验证的二进制数据。
主要错误:410票据过期/未知;403插件或音源禁用;416Range不合法;429插件双槽繁忙;502第三方拒绝/非有效音频/续传资源变化/连接异常。该路径不调用原平台playback_status状态表,但会影响plugins的最近取流status/detail。
7.5 GET /api/plugin_lyric
参数key/plugin/consent与resolve相同;quality也可传递给运行时,默认128k,但歌词动作没有musicUrl的同一音质声明检查。
200仅为 {lyric: string}。插件value为对象时取其lyric,为字符串时直接使用,其他类型变为空字符串。该网关分支不保证与 /api/lyric 一样包含source/key/lyric_adapter或相同规范化流程。
主要错误与resolve相同,除音乐URL及票据签发相关分支。客户端仍须有自己的大小和格式限制。
8. 非API发布文件
| GET路径 | 内容 |
|---|---|
/downloads/latest-phone.json |
手机更新feed |
/downloads/latest-watch.json |
手表更新feed |
/downloads/latest.json |
旧双模式feed;不能代替当前两端独立feed |
/downloads/Wristhyme-Phone-<version>.apk |
手机安装包 |
/downloads/Wristhyme-<version>.apk |
手表安装包 |
上述文件 + .sha256 |
SHA256文本 |
/downloads/*-source.zip |
对应源码包,以实际发行文件名为准 |
两端feed主要字段:versionCode integer、versionName string、applicationId string、minSdk integer、apkUrl string、sha256 string、notes string;新发布可附 signerSha256。这些路径由静态文件服务处理,错误未必为JSON。
2026-09-30发行记录中两端为2.2.2/222;实际更新仍读各自feed。手机包名 cn.tolike.wristhyme.phone,手表 cn.tolike.wristhyme。下载前核对包名/版本/平台兼容性,下载后核对SHA256与签名,不仅依靠健康接口version或网页文案。