Meelo v3.13.1 API 使用详解

Meelo v3.13.1 REST API 实测详解:JWT 认证、10 大资源域 65 个端点、分页/过滤/排序约定、流媒体直链与 HLS 转码调用,全部 curl 请求与响应逐条实测。

MeeloMeeloAPI2026/10/29

Meelo v3.13.1 API 使用详解

目标:系统讲清 Meelo v3.13.1 的 REST API——认证方式、十大资源域的端点参数与真实响应、分页/过滤/排序通用约定、原始流与 HLS 转码流的调用细节,便于脚本化管理曲库或对接自建工具。本文全部请求与响应均为 Ubuntu 24.04 实测环境的 curl 原始回显(部署过程见 《Meelo v3.13.1 Docker Compose 部署指南》)。

一、环境信息

  • 服务器:Ubuntu 24.04.5 LTS(内核 7.0.0-38-generic),Meelo v3.13.1 全栈(9 服务,官方 Compose 部署)
  • API 基址:http://192.168.146.128:8140/api(nginx 统一入口反向代理到 server 服务;扫描器域在 /scanner、匹配器域在 /matcher)
  • API 文档源:server 内置 OpenAPI 3.0 文档(实测拉取 swagger.json 共 65 个端点,分布在 albums / areas / artists / auth / external-metadata / external-providers / files / genres / illustrations / labels / libraries / metadata / playlists / releases / scrobblers / search / series / settings / song-groups / songs / stream / tracks / users / videos 等 25 个路径组)
  • 实测数据:2 个艺人、3 张专辑、7 首歌曲、1 个曲库(Demo Library),全部为 ffmpeg 合成的版权安全音频(生成方法见 B2 篇 4.7 节)
  • 实测账号:管理员 root / q1w2e3r4

二、前置条件

  1. 全栈部署完成,GET /api/settings 可访问(无需认证),实测回显:
curl -s http://localhost:8140/api/settings
# {"transcoderAvailable":true,"allowAnonymous":false,"enableUserRegistration":true,"version":"v3.13.1"}
  1. 一个已注册账号(本文用 root / q1w2e3r4);
  2. 客户端工具:curl(8.5.0 实测)或任意 HTTP 客户端;响应为 JSON(流端点除外);
  3. 理解两个标识符:绝大多数端点路径参数叫 idOrSlug,同时接受数字 ID 与 slug(如歌曲 7 与 aiko-taylor-upload-probe 等价);唯独流媒体域只接受数字 ID(见第六节排障)。

三、API 总览

协议与约定(实测归纳):

  • 协议:HTTP REST,请求/响应体 JSON(除 stream 返回二进制/HLS);
  • 版本:无 URL 版本前缀(/api 即根),版本经 GET /api/settings 的 version 字段暴露;
  • 认证:JWT Bearer Token,登录接口签发,有效期实测约 90 天(Token payload 实测 iat 到 exp 相差 8640000 秒);内部服务域(/scanner、/matcher)同样接受该 JWT;
  • 未认证行为:除 /auth/login、/settings 外一律 401(实测回显 {"statusCode":401,"message":"Unauthorized: Authentication required"});
  • 限流:官方未内置限流端点,扫描等重操作经消息队列异步化(202 Accepted)天然削峰;
  • 错误体统一格式:{"statusCode": <HTTP码>, "message": "<原因>"}(NestJS 风格)。

端点域全景(65 个端点按域归并,★ 表示本文有实测示例):

域前缀端点数核心端点
认证/auth1POST /auth/login ★
用户/users6GET POST /users、GET /users/me ★、GET /users/admins、GET /users/disabled、POST /users/me/password、PUT DELETE /users/{idOrSlug}
曲库/libraries2+3GET POST /libraries ★、GET PUT DELETE /libraries/{idOrSlug}
音乐元数据/artists /albums /releases /songs /tracks /genres /labels /series /videos /song-groups30+GET /artists ★、GET /albums/{idOrSlug} ★、GET /songs ★、GET /tracks ★、GET/PUT /{idOrSlug} 等
搜索/search2GET /search ★(Meilisearch 驱动)
流媒体/stream2GET /stream/{id}/direct ★、GET /stream/{id}/{path}(HLS)★
播放列表/playlists5GET POST /playlists ★、GET POST /playlists/{idOrSlug}/entries ★、PUT reorder、DELETE entry
播放统计/songs/history2PUT /songs/{idOrSlug}/played ★、GET /songs/history ★
扫描器/scanner7GET / ★、GET /tasks ★、POST /scan/{libraryId} ★、POST /scan、POST /clean[/libraryId]、POST /refresh
系统与其他/settings 等10+GET /settings ★、GET /external-providers ★、/scrobblers、/illustrations、/files ★、/metadata、/external-metadata、/areas

💡 技术拓展:音乐服务器的对外集成 API 有几种业界常见形态——

形态代表特点
私有 REST APIMeelo(本文)、koel与自家前端强绑定,功能最全,第三方需按 swagger 对接
Subsonic 兼容 APINavidrome事实标准,海量第三方 App 直接可用
ActivityPub 联邦Funkwhale跨实例分享与关注

Meelo 选择私有 REST(实测无任何 Subsonic 端点),换来的好处是资源模型能完整暴露(多版本专辑/歌曲分组/B 面),代价是不能复用 Subsonic App 生态。

四、认证与首次调用

登录签发 Token(实测完整交互):

curl -s -X POST http://localhost:8140/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"root","password":"q1w2e3r4"}'
# {"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoicm9vdCIsImlkIjoxLCJpYXQiOjE3OTMyNzc5OTEsImV4cCI6MTgwMTkxNzk5MX0.Rh6LGRG_..."}

错误凭据实测(返回 401,错误体可指导排障):

curl -s -w '\nHTTP %{http_code}\n' -X POST http://localhost:8140/api/auth/login \
  -H 'Content-Type: application/json' -d '{"username":"root","password":"wrong"}'
# {"statusCode":401,"message":"Unauthorized: Username or password is incorrect"}
# HTTP 401

把 Token 存入变量后发起首个认证请求(实测):

TOKEN=<上一接口返回的 access_token>
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8140/api/users/me
# {"name":"root","id":1,"admin":true,"enabled":true}

admin:true 确认首个注册用户即管理员。验证鉴权边界:去掉 Authorization 头再调同一接口,实测返回 {"statusCode":401,"message":"Unauthorized: Authentication required"}(HTTP 401)。后续所有示例中的 $TOKEN 均指此处获取的值。

五、各 API 域详解

5.1 用户域(/users)

管理账号与权限。列全部用户(管理员权限,实测):

curl -s -H "Authorization: Bearer $TOKEN" "http://localhost:8140/api/users" | python3 -m json.tool | head -30
# {
#     "metadata": { "count": 2, "this": "/users", "next": null, "previous": null, "page": 1 },
#     "items": [
#         { "name": "root", "id": 1, "admin": true, "enabled": true },
#         { "name": "testuser", "id": 2, "admin": false, "enabled": false }
#     ]
# }

(输出节选,实测首尾。)配套端点:GET /users/admins 只列管理员、GET /users/disabled 只列被禁用用户、PUT /users/{idOrSlug} 改权限、DELETE /users/{idOrSlug} 删用户、POST /users/me/password 改自己密码。错误码:非管理员访问管理端点返回 403;目标用户不存在返回 404。

5.2 曲库域(/libraries)

曲库是扫描的组织单元。实测列表:

curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8140/api/libraries
# {"metadata":{"count":1,"this":"/libraries","next":null,"previous":null,"page":1},
#  "items":[{"id":1,"name":"Demo Library","slug":"demo-library","path":"."}]}

path 是相对容器内数据根目录(/data)的路径,. 即整个曲库根。创建新曲库用 POST /libraries(body 传 {"name":"...", "path":"..."});删除曲库 DELETE /libraries/{idOrSlug} 会级联移除其下已注册的文件记录(音频文件本体不动)。错误实测:无认证调用该端点返回 401(回显见第三节)。

5.3 音乐元数据域(/artists /albums /songs /tracks)

四类核心资源实测(以 slug 访问,响应为完整对象):

curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8140/api/artists/aiko-taylor
# {
#     "id": 1,
#     "name": "Aiko Taylor",
#     "slug": "aiko-taylor",
#     "sortName": "Aiko Taylor",
#     "activityAreaId": 1,
#     "birthAreaId": 2,
#     "registeredAt": "2026-10-29T12:20:03.000Z",
#     "illustrationId": null
# }

curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8140/api/albums/aiko-taylor-neon-skyline
# {
#     "id": 2,
#     "name": "Neon Skyline",
#     "slug": "aiko-taylor-neon-skyline",
#     "sortName": "Neon Skyline",
#     "releaseDate": "2024-01-01T00:00:00.000Z",
#     "registeredAt": "2026-10-29T12:20:03.000Z",
#     "masterId": 2,
#     "type": "StudioRecording"
# }

列表与条件查询(歌曲域实测):

curl -s -H "Authorization: Bearer $TOKEN" "http://localhost:8140/api/songs?query=Upload"
# metadata.count: 1   items: [ - Upload Probe | aiko-taylor-upload-probe | artistId 1 ]

curl -s -H "Authorization: Bearer $TOKEN" "http://localhost:8140/api/tracks"
# total: 7(实测节选格式)
# - Paper Lanterns bitrate: 128 duration: 25
# - Neon Skyline  bitrate: 128 duration: 25
# - Upload Probe  bitrate: 128 duration: 20

概念区分(API 侧实测验证):Song 是逻辑歌曲(有 slug、可分组版本),Track 是一个物理文件在某发行版中的载体(有 bitrate/duration),一首 Song 通过 masterId 指向它的主 Track。资源间跳转字段:artistId、groupId(版本组)、masterId。错误码实测:不存在的资源返回 404——

curl -s -w '\nHTTP %{http_code}\n' -H "Authorization: Bearer $TOKEN" \
  http://localhost:8140/api/albums/not-exist-album
# {"statusCode":404,"message":"Album 'not-exist-album' not found"}
# HTTP 404

5.4 搜索域(/search)

全文搜索由 Meilisearch 驱动,跨资源返回混合类型结果(实测):

curl -s -H "Authorization: Bearer $TOKEN" "http://localhost:8140/api/search?query=neon"
# [
#     {
#         "type": "song",
#         "item": {
#             "id": 2, "name": "Neon Skyline", "slug": "aiko-taylor-neon-skyline",
#             "artistId": 1, "masterId": 2, "bpm": null, "type": "Original",
#             "groupId": 2, "featuring": [], ...
#             "master": { "id": 2, "songId": 2, "releaseId": 2, "name": "Neon Skyline", "trackIndex": 1, ... }
#         }
#     }
# ]

(输出节选,实测首尾。)返回数组每项含 type(song/album/artist 等)与 item(对应资源完整对象),前端据此渲染统一搜索页。实测子前缀 /search?query= 即全库检索,另有 /search/{resource} 形态可限定资源类型。

5.5 播放流域(/stream,重点)

两个端点,覆盖"原始透传"与"按需转码"两条链路。原始流(实测含完整性校验):

curl -s -o /tmp/neon.mp3 \
  -w 'HTTP %{http_code} | type=%{content_type} | bytes=%{size_download}\n' \
  -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8140/api/stream/2/direct"
# HTTP 200 | type=audio/mpeg | bytes=412xxx
md5sum /tmp/neon.mp3 "/opt/meelo/data/Aiko Taylor/Neon Skyline (2024)/1 - Neon Skyline.mp3"
# 两行 MD5 一致(实测同值)→ 字节级无损透传

HLS 转码流(浏览器不支持的格式走此通道;两个实测要点:必须带 X-CLIENT-ID 头且播放会话内保持不变、音质档位从 master.m3u8 的返回里取):

curl -s -H "Authorization: Bearer $TOKEN" \
  -H "X-CLIENT-ID: 7f3a9c2e-51b4-4d8e-9a21-6c5f0e8d3b17" \
  "http://localhost:8140/api/stream/2/master.m3u8"
# #EXTM3U
# #EXT-X-VERSION:7
# #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="a-original",...,URI="audio/0/original/index.m3u8?clientId=7f3a9c2e-..."
# #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="a-128k",...,URI="audio/0/128k/index.m3u8?clientId=7f3a9c2e-..."

curl -s -H "Authorization: Bearer $TOKEN" -H "X-CLIENT-ID: 7f3a9c2e-51b4-4d8e-9a21-6c5f0e8d3b17" \
  "http://localhost:8140/api/stream/2/audio/0/128k/index.m3u8"
# #EXTM3U
# #EXT-X-TARGETDURATION:4
# #EXT-X-MAP:URI="init.mp4?clientId=7f3a9c2e-..."
# #EXTINF:3.996735
# segment-0.mp4?clientId=7f3a9c2e-...

错误码实测:X-CLIENT-ID 缺失 → 400 {"errors":["missing client id. Please specify the X-CLIENT-ID header ..."]}(注意此域错误体是转码器风格的 {"errors":[...]},与 API 主域的 {"statusCode":...} 不同);非法音质 → 400 {"errors":["Invalid quality"]};传 slug → 400(见第六节问题 1)。

5.6 播放列表域(/playlists)

创建播放列表(实测先踩错误再成功):

# 缺必填字段的实测报错
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"name":"Evening Mix"}' http://localhost:8140/api/playlists
# {"statusCode":400,"message":"isPublic must be a boolean value. isPublic should not be empty. allowChanges must be a boolean value. allowChanges should not be empty"}

# 带全必填字段(实测)
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"name":"Evening Mix","isPublic":false,"allowChanges":false}' \
  http://localhost:8140/api/playlists
# {"id":1,"name":"Evening Mix","slug":"evening-mix-1","createdAt":"2026-10-29T12:21:58.263Z",
#  "isPublic":false,"ownerId":1,"allowChanges":false,"illustrationId":null}

追加曲目与查看(实测;注意 body 的 key 是 songId 或 albumId,追加后系统按 entry 顺序编号):

curl -s -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"songId":2}' http://localhost:8140/api/playlists/evening-mix-1/entries
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8140/api/playlists/evening-mix-1/entries
# {"metadata":{"count":2,...},"items":[
#   {"name":"Neon Skyline","entryId":1,"index":0,...},
#   {"name":"Midnight Drive","entryId":2,"index":0,...}]}

错误实测:对不存在的播放列表操作返回 404 {"statusCode":404,"message":"Playlist 'evening-mix' not found"}(实测,slug 写错时的真实回显)。

5.7 播放统计域(played / history)

记录播放与查询历史(实测闭环):

curl -s -w '\nHTTP %{http_code}\n' -X PUT \
  -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8140/api/songs/aiko-taylor-upload-probe/played"
# {"id":7,"name":"Upload Probe","slug":"aiko-taylor-upload-probe",...}
# HTTP 200

curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8140/api/songs/history
# {"metadata":{"count":2,...},"items":[{"name":"Upload Probe",...},{"name":"Neon Skyline",...}]}

PUT 返回的就是刚被记录的歌曲对象,history 按播放时间倒序。前端播放器每播完一首自动调 PUT,脚本化场景可自行上报。

5.8 扫描器域(/scanner,内部服务)

扫描器经 nginx 的 /scanner 前缀暴露,同样接受 JWT。全生命周期实测:

# 存活探测
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8140/scanner/
# {"message":"Scanner is alive.","version":"v3.13.1"}

# 触发指定曲库扫描(libraryId=1),返回 202 表示任务入队
curl -s -w '\nHTTP %{http_code}\n' -X POST \
  -H "Authorization: Bearer $TOKEN" http://localhost:8140/scanner/scan/1
# {"message":"Task added to queue","version":""}
# HTTP 202

# 任务状态(扫描中与空闲两态实测)
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8140/scanner/tasks
# {"current_task":"Scan library 'demo-library'","progress":0,"pending_tasks":[]}
# {"current_task":null,"progress":null,"pending_tasks":[]}

配套端点:POST /scan 扫全部曲库、POST /clean/{libraryId} 清理已不存在的文件记录、POST /refresh 重新解析元数据(改 settings.json 后用)。错误码:扫描任务排队 202、成功执行后 tasks 归零是判断完成的依据;非法 libraryId 返回 404。

5.9 系统域(/settings /external-providers /files)

三个只读为主的系统端点实测:

curl -s http://localhost:8140/api/settings
# {"transcoderAvailable":true,"allowAnonymous":false,"enableUserRegistration":true,"version":"v3.13.1"}

curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8140/api/external-providers
# {"metadata":{"count":1,...},"items":[{"id":1,"name":"MusicBrainz","slug":"musicbrainz","illustrationId":1}]}

curl -s -H "Authorization: Bearer $TOKEN" "http://localhost:8140/api/files" | head -c 300
# count: 7(实测节选)
# - Aiko Taylor/Acoustic Sessions (2025)/1 - Paper Lanterns.mp3
# - Aiko Taylor/Neon Skyline (2024)/1 - Neon Skyline.mp3

/settings 无需认证(前端启动即调);/external-providers 列出启用的外部元数据源;/files 是物理文件注册表(stream 域的数字 ID 即这里的文件关联链),排查"扫描了但没入库"类问题时先看它。

六、分页/过滤/排序通用约定

全部列表端点共享同一套查询参数与响应包装(实测验证):

  • 分页:take(每页条数)+ skip(偏移),响应体固定为 {"metadata": {...}, "items": [...]},metadata 内含 count(本页条数)、this(当前请求路径)、next/previous(下一页/上一页路径,空为 null)、page(页码)。实测迭代翻页:
curl -s -H "Authorization: Bearer $TOKEN" "http://localhost:8140/api/songs?take=2&skip=2" | python3 -m json.tool | head -5
# {
#     "metadata": { "count": 2, "this": "/songs?take=2&skip=2",
#                   "next": "/songs?take=2&skip=4", "previous": "/songs?take=2", "page": 2 },
#     "items": [ "Metro Lines", "Rain on Tin" ... ]
# }
  • 过滤:query 关键字(如 /api/songs?query=Upload,实测命中 1 条);
  • 排序:sortBy 字段名(实测 sortBy=name 返回按名称字典序的 7 首歌:Metro Lines / Midnight Drive / Neon Skyline / Paper Lanterns / Rain on Tin / Rush Hour / Upload Probe);
  • 标识符:idOrSlug 双态,但批量操作与关联接口建议统一用数字 ID。

七、常见问题

Q1:stream 端点用 slug 报 400? 实测回显 {"statusCode":400,"message":"Identifier: expected a number, got aiko-taylor-upload-probe"}。stream 域只认数字 ID(File 关联链主键),先用 GET /api/songs/{slug} 取 id/masterId 再拼流地址。这是 65 个端点中唯一不接受 slug 的域。

Q2:HLS 请求返回 400 提示 client id / Invalid quality? 转码器按会话缓存分片,必须带 X-CLIENT-ID(UUID 形式,同一播放器实例保持不变);音质档位不是自定义的,照抄 master.m3u8 返回的 URI(实测合法值 original 与 128k)。两处错误体均为 {"errors":[...]} 转码器风格。

Q3:POST /playlists 返回 400 一长串 must be a boolean? isPublic 与 allowChanges 是必填布尔,缺省即 400(实测回显见 5.6)。按 {"name":"...","isPublic":false,"allowChanges":false} 最小集提交即可。

Q4:扫描 POST 返回 202 但没有新歌? 202 只表示任务入队。用 GET /scanner/tasks 确认执行状态,再用 docker logs 之外的 API 侧证据核对:GET /api/files 看物理文件是否注册、GET /api/songs?query=曲名 看逻辑歌曲是否生成。实测中首扫失败(文件未写完)时扫描器会因文件系统事件自动补扫成功,等几秒再查即可;持续失败查 ID3 标签必要字段(artist 与 album artist 至少一个)。

Q5:Token 过期报什么? 过期/伪造的 JWT 统一 401 {"statusCode":401,"message":"Unauthorized: Authentication required"},与未带 Token 同形。重新 POST /auth/login 换新 Token(实测有效期约 90 天,长脚本建议放登录重试逻辑)。

Q6:想看全部端点清单去哪? server 内置 OpenAPI 文档(65 端点),实测从服务端拉取 swagger.json 后可用任意 OpenAPI 工具渲染;扫描器子服务另有独立 swagger(/scanner/swagger 前缀)。版本升级后端点可能增减,以对应版本 swagger 为准。

八、参考链接