Audiobookshelf 2.37.2 常用 API 与运维实战
Audiobookshelf 2.37.2 REST API 实战手册:login/库/条目/播放会话/HLS/进度同步实测回显,播客 RSS 预检-建档-跟更与单集播放路径参数陷阱,RSS 只抓检查时点后新单集语义,备份恢复、API Key 与用户管理自动化,附 3 案例排障实录(2026-10-27 实测)。
Audiobookshelf 2.37.2 常用 API 与运维实战
目标:把 Audiobookshelf 2.37.2 的 REST API 按真实使用路径串起来——认证、库与条目、播放会话与进度同步、播客订阅与单集下载、备份、API Key 与用户管理——全部以 2.37.2 运行实例的实测 curl 回显为准,并沉淀三个实测排障案例(播客单集播放的路径参数陷阱、RSS 只抓「检查时点之后」发布的新单集、新建对象默认未激活)。读完全篇可以独立完成「用脚本驱动 Audiobookshelf」的自动化集成。部署与配置细节见《Audiobookshelf 2.37.2 Docker Compose 部署实战》《Audiobookshelf 2.37.2 配置参数详解》。
一、环境信息
| 项目 | 值 |
|---|---|
| 实测日期 | 2026-10-27 |
| 服务版本 | 2.37.2(官方最新稳定版),已初始化运行实例 |
| 已有数据 | 有声书库「有声书图书馆」(2 本条目)、播客库「播客订阅」(订阅本地 RSS 源,1 集已下载)、root 账号 |
| 实测账号 | root / q1w2e3r4 |
| 测试条目 | 有声书《三体(演示)》(2 个音频文件、2 章节、总时长 120 秒,实测条目 id f9b0678e-0eac-4d84-9b39-e9b3f387eeab) |
| API 文档 | 官方 API 参考 https://api.audiobookshelf.org |
| 服务地址 | http://127.0.0.1:8137(注意:默认基础路径 /audiobookshelf,API 路径根路径与基础路径均可达,实测一致) |
二、API 总览与认证
2.1 认证方式
两种凭据,同一 Authorization: Bearer 头:
| 凭据 | 获取方式 | 适用 |
|---|---|---|
| 用户 JWT | POST /login,取响应 user.token | 交互/短脚本;有效期 1 小时(ACCESS_TOKEN_EXPIRY 默认值),可刷新 |
| API Key | POST /api/api-keys 创建(root/admin) | 长期自动化集成;需显式激活(见第八节与排障案例 3) |
登录实测:
curl -s -X POST http://127.0.0.1:8137/login \
-H 'Content-Type: application/json' \
-d '{"username":"root","password":"q1w2e3r4"}'
实测回显(截取):
{"user":{"id":"da78502d-603d-4a89-835e-48c2bd409761","username":"root","type":"root","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....","mediaProgress":[],...}}
后续示例统一用 $TOKEN 指代登录令牌。注意登录端点有默认限速(认证限速默认 40 次/窗口,见配置参数篇),脚本暴力重试会触发 429。
2.2 服务探针
curl -s http://127.0.0.1:8137/ping # -> {"success":true}
curl -s http://127.0.0.1:8137/status # -> {"app":"audiobookshelf","serverVersion":"2.37.2","isInit":true,...}
/status 的 isInit 可用于部署脚本判断是否需要初始化(POST /init)。
三、库与条目
3.1 列出库
curl -s http://127.0.0.1:8137/api/libraries -H "Authorization: Bearer $TOKEN"
实测回显(截取):
{"libraries":[{"id":"d14e4bba-d24e-4e41-8761-ffb76851b4c1","name":"有声书图书馆","folders":[{"id":"b7f418a0-0a78-4126-980a-f70e65071f73","fullPath":"/audiobooks",...}],"displayOrder":...
3.2 创建库与触发扫描
curl -s -X POST http://127.0.0.1:8137/api/libraries \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"有声书图书馆","icon":"audiobook","mediaType":"book","provider":"audible","folders":[{"fullPath":"/audiobooks"}]}' \
-w '\nHTTP %{http_code}\n'
实测回显:HTTP 200,响应体含新库 id(mediaType 支持 book 与 podcast)。创建库时首轮扫描自动执行;追加扫描用:
curl -s -X POST "http://127.0.0.1:8137/api/libraries/d14e4bba-d24e-4e41-8761-ffb76851b4c1/scan" \
-H "Authorization: Bearer $TOKEN" -w '\nHTTP %{http_code}\n'
实测回显:
HTTP 200
扫描是异步的——接口立即返回,扫描进度经实时通道推送、结果通过条目查询核对(服务日志中可见 Watcher/扫描相关条目)。
3.3 条目列表与详情
curl -s "http://127.0.0.1:8137/api/libraries/d14e4bba-d24e-4e41-8761-ffb76851b4c1/items" \
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool | head -8
实测回显(截取):
{
"results": [
{
"id": "f9b0678e-0eac-4d84-9b39-e9b3f387eeab",
"media": {
"metadata": {
"title": "三体(演示)",
"authorName": "刘慈欣",
...
条目详情(audioFiles/chapters 结构是播放与会话接口的输入源):
curl -s "http://127.0.0.1:8137/api/items/f9b0678e-0eac-4d84-9b39-e9b3f387eeab" \
-H "Authorization: Bearer $TOKEN" > /tmp/item.json
python3 -c "
import json
d=json.load(open('/tmp/item.json'))
m=d['media']
print('media keys:', list(m.keys()))
print('chapters:', [(c['id'], c['title'], c['start'], c['end']) for c in m['chapters']])
af=m['audioFiles'][0]
print('audioFile0:', {k: af[k] for k in ('index','duration','mimeType','codec','channels','bitRate')})
"
实测回显:
media keys: ['id', 'libraryItemId', 'metadata', 'coverPath', 'tags', 'audioFiles', 'chapters', 'ebookFile']
chapters: [(0, '第01章', 0, 40), (1, '第02章', 40, 80)]
audioFile0: {'index': 1, 'duration': 40, 'mimeType': 'audio/mpeg', 'codec': 'mp3', 'channels': 1, 'bitRate': 64000}
四、播放会话与进度同步
这是 App/第三方客户端接入的核心链路,实测四步:建会话 → 取流 → 同步进度 → 关会话。
4.1 创建播放会话
curl -s -X POST "http://127.0.0.1:8137/api/items/f9b0678e-0eac-4d84-9b39-e9b3f387eeab/play" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"deviceInfo":{"deviceId":"doc-demo","client":"DocClient"}}' > /tmp/play.json
python3 -c "
import json
d=json.load(open('/tmp/play.json'))
print('session id:', d['id'])
print('playMethod:', d['playMethod'], '| duration:', d['duration'])
t=d['audioTracks'][0]
print('track0:', {k: t[k] for k in ('index','startOffset','duration','mimeType','contentUrl')})
"
实测回显:
session id: f44bbb93-b445-4ac8-9b80-cd855f74393d
playMethod: transcode | duration: 120
track0: {'index': 1, 'startOffset': 0, 'duration': 120, 'mimeType': 'application/vnd.apple.mpegurl', 'contentUrl': '/hls/f44bbb93-b445-4ac8-9b80-cd855f74393d/output.m3u8'}
会话对象字段很全(chapters、displayTitle、deviceInfo、serverVersion 等约 25 个键),客户端展示所需信息一次拿齐。
4.2 拉流播放
HLS 播放列表与分片(带上同一令牌):
curl -s -o /dev/null -w 'm3u8: %{http_code}\n' \
"http://127.0.0.1:8137/hls/f44bbb93-b445-4ac8-9b80-cd855f74393d/output.m3u8" -H "Authorization: Bearer $TOKEN"
curl -s -o /dev/null -w 'segment: %{http_code}\n' \
"http://127.0.0.1:8137/hls/f44bbb93-b445-4ac8-9b80-cd855f74393d/output-0.ts" -H "Authorization: Bearer $TOKEN"
实测回显:
m3u8: 200
segment: 206
分片返回 206(范围请求),播放器按 m3u8 索引顺序拉取即可。
4.3 同步进度并关闭会话
每 10 秒左右上报一次播放位置:
curl -s -X POST "http://127.0.0.1:8137/api/session/f44bbb93-b445-4ac8-9b80-cd855f74393d/sync" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"currentTime":42}' -w '\nsync HTTP %{http_code}\n'
实测回显:sync HTTP 200。收听结束时关闭会话并结算收听时长:
curl -s -X POST "http://127.0.0.1:8137/api/session/f44bbb93-b445-4ac8-9b80-cd855f74393d/close" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"currentTime":42,"timeListened":10}' -w '\nclose HTTP %{http_code}\n'
实测回显:close HTTP 200。会话关闭后服务端立即清理转码临时目录(/metadata/streams/<会话id>),此时再请求该会话的 m3u8 会得到 404(服务端日志:ERROR: [HlsRouter] Stream "..." does not exist)——这是正常生命周期,不是故障。
4.4 读取进度(多端接力)
curl -s "http://127.0.0.1:8137/api/me/progress/f9b0678e-0eac-4d84-9b39-e9b3f387eeab" \
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool
实测回显:
{
"id": "d1009a82-3086-4d7b-acd3-ef2adb219f57",
"userId": "da78502d-603d-4a89-835e-48c2bd409761",
"libraryItemId": "f9b0678e-0eac-4d84-9b39-e9b3f387eeab",
"episodeId": null,
"mediaItemType": "book",
"duration": 120,
"progress": 0.35,
"currentTime": 42,
"isFinished": false,
...
}
progress=0.35(42/120 秒)与关会话时上报的位置一致——换任意客户端调本接口即可续播。
五、播客 API
5.1 订阅源预检
添加播客前先验证 RSS 可读(同时拿到结构化元数据用于建档):
curl -s -X POST http://127.0.0.1:8137/api/podcasts/feed \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"rssFeed":"http://podcast-source:8000/feed.xml"}' | head -c 260
实测回显(截取):
{"podcast":{"metadata":{"image":null,"categories":["Technology"],"feedUrl":"http://podcast-source:8000/feed.xml","description":"用于实测 Audiobookshelf 播客订阅功能的本地模拟 RSS 源,共两期。","descriptionPlain":"...","title":"自托管之声(演示)","language":"zh-cn",...
解析出的单集带 publishedAt(毫秒时间戳)、enclosure.url 等字段。注意请求体参数名是 rssFeed(不是 feedUrl)。
5.2 建立播客
用预检结果的元数据 POST /api/podcasts 建档(libraryId/folderId/path 指明落地位置,media.metadata.feedUrl 是订阅源),实测建立后返回新条目 id(本例 00f24b04-5258-4205-938f-ccc6619caa19)。随后查单集:
curl -s "http://127.0.0.1:8137/api/items/00f24b04-5258-4205-938f-ccc6619caa19" \
-H "Authorization: Bearer $TOKEN" | python3 -c "
import json,sys
d=json.load(sys.stdin)
print('episodes:', [(e['title'], e['id'][:13], bool(e.get('audioFile'))) for e in d['media'].get('episodes',[])])
"
实测回显(下载后):
episodes: [('第02期:媒体库命名规范', '8ab9a4f4-48be', True)]
5.3 手动检查新单集并下载
curl -s "http://127.0.0.1:8137/api/podcasts/00f24b04-5258-4205-938f-ccc6619caa19/checknew?limit=2" \
-H "Authorization: Bearer $TOKEN" | head -c 260
实测回显(截取):
{"episodes":[{"title":"第02期:媒体库命名规范","subtitle":"","description":"","descriptionPlain":"","pubDate":"Tue, 27 Oct 2026 23:51:55 +0800","episodeType":"","season":"","episode":"","author":"","duration":"70","durationSeconds":70,"explicit":"","publishedAt":1793116315000,"enclosure":{...
返回的是「将被下载」的新单集列表;服务端日志随之记录下载完成:
[2026-10-27 23:51:55.165] INFO: [PodcastManager] Found 1 new episodes for podcast "自托管之声(演示)" - starting download
[2026-10-27 23:51:55.318] INFO: [PodcastManager] Successfully downloaded podcast episode "第02期:媒体库命名规范"
下载产物落在播客目录的条目子目录(实测 /podcasts/自托管之声(演示)/第02期:媒体库命名规范.mp3,560942 字节),条目接口随之出现 audioFile(探测出的时长 70.02 秒)。
5.4 播放单集:路径参数是唯一正确姿势
播客单集播放的端点是 POST /api/items/:id/play/:episodeId——episodeId 必须放在 URL 路径里:
curl -s -X POST "http://127.0.0.1:8137/api/items/00f24b04-5258-4205-938f-ccc6619caa19/play/8ab9a4f4-48be-4216-be98-04004d046508" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"deviceInfo":{"deviceId":"doc-demo"}}' | python3 -c "
import json,sys
d=json.load(sys.stdin)
print('session:', d['id'], '| episodeId:', d.get('episodeId'))
"
curl -s -o /dev/null -w 'podcast HLS: %{http_code}\n' \
"http://127.0.0.1:8137/hls/<新会话id>/output.m3u8" -H "Authorization: Bearer $TOKEN"
实测回显:
session: 9030a416-3ea8-4b89-8556-9426273d4edd | episodeId: 8ab9a4f4-48be-4216-be98-04004d046508
podcast HLS: 200
会话对象里 episodeId 正确、HLS 播放列表 200——单集播放链路完整可用。进度同步/关闭会话与有声书共用第四节的接口。
六、备份与恢复
curl -s -X POST http://127.0.0.1:8137/api/backups -H "Authorization: Bearer $TOKEN" -w '\nHTTP %{http_code}\n'
实测回显(截取):
{"backups":[{"id":"2026-10-27T2356","key":"sqlite","backupDirPath":"/metadata/backups","datePretty":"Tue, Oct 27 2026 23:56","fullPath":"/metadata/backups/2026-10-27T2356.audiobookshelf","filename":"2026-10-27T2356.audiobookshelf","fileSize":17108,"createdAt":1793116577181,"serverVersion":"2.37.2"}]}
HTTP 200
curl -s http://127.0.0.1:8137/api/backups -H "Authorization: Bearer $TOKEN" | python3 -c "
import json,sys
d=json.load(sys.stdin)
print('备份份数:', len(d['backups']), '| 落盘目录:', d['backupLocation'], '| 环境变量覆盖:', d['backupPathEnvSet'])
"
实测回显:
备份份数: 1 | 落盘目录: /metadata/backups | 环境变量覆盖: False
运维要点:
- 备份是 zip 结构(扩展名
.audiobookshelf),内容含 SQLite 库与配置,落盘/metadata/backups/(可被BACKUP_PATH环境变量或backupPath设置改向)。 - 定时策略(
backupSchedule/backupsToKeep)是配置项不是接口,见配置参数篇第五节实测。 - 恢复方向:用备份文件覆盖
/config后重启实例;迁移 = 「备份 + 媒体目录 +/metadata」三件套整体搬家。
七、运维自动化常用端点
| 端点 | 方法 | 用途(均实测) |
|---|---|---|
/api/serverInfo | GET | 版本与运行信息(自动化里的版本核对) |
/api/libraries/:id/scan | POST | 触发库扫描(异步) |
/api/items/:id | GET/PATCH/DELETE | 条目详情/修改/删除 |
/api/podcasts/:id/checknew | GET | 手动检查新单集(?limit=N 限制下载数) |
/api/backups | GET/POST | 备份列表/立即备份 |
/api/settings | PATCH | 修改 serverSettings(实测改备份策略返回 200 + 新配置) |
/api/authorize | POST | 当前用户 + 全量 serverSettings(设置读取通道;GET /api/settings 在 2.37.2 不存在,实测 404) |
/api/users | GET/POST | 用户管理 |
/api/api-keys | GET/POST | API Key 管理 |
八、API Key 与用户管理
8.1 API Key
创建(注意 userId 必填、isActive 默认 false):
MYID=$(curl -s http://127.0.0.1:8137/api/me -H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")
curl -s -X POST http://127.0.0.1:8137/api/api-keys \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d "{\"name\":\"doc-demo-key2\",\"userId\":\"$MYID\",\"isActive\":true}" | python3 -c "
import json,sys
d=json.load(sys.stdin)
k=d['apiKey']
print('keyId:', k['id'], '| isActive:', k['isActive'])
open('/tmp/ak.txt','w').write(k['apiKey'])
"
实测回显:
keyId: 6b61a567-29b9-48ee-8243-d2bca8eeb3fc | isActive: True
用 Key 调用(与 JWT 同头不同值):
curl -s http://127.0.0.1:8137/api/libraries -H "Authorization: Bearer $(cat /tmp/ak.txt)" -w '\nHTTP %{http_code}\n' | tail -1
实测回显:HTTP 200。注意:Key 的 name 与 expiresIn 写死在签发的 JWT 里,创建后不可改(官方源码注释明示仅 isActive 与 userId 可更新)。
8.2 用户管理
创建 + 激活 + 验证登录(完整闭环):
curl -s -X POST http://127.0.0.1:8137/api/users \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"username":"family","password":"f@m1ly2026","type":"user",
"permissions":{"download":true,"update":false,"delete":false,"upload":false,
"accessAllLibraries":false,"selectedLibraries":["d14e4bba-d24e-4e41-8761-ffb76851b4c1"]}}' \
| python3 -c "import json,sys; u=json.load(sys.stdin)['user']; print('created:', u['username'], '| isActive:', u['isActive'])"
curl -s -X PATCH http://127.0.0.1:8137/api/users/4334b495-e583-4433-8331-436917ba389e \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"isActive":true}' -o /dev/null -w 'activate HTTP %{http_code}\n'
curl -s -o /dev/null -w 'login HTTP %{http_code}\n' -X POST http://127.0.0.1:8137/login \
-H 'Content-Type: application/json' -d '{"username":"family","password":"f@m1ly2026"}'
实测回显:
created: family | isActive: False
activate HTTP 200
login HTTP 200
权限对象字段全集(download/update/delete/upload/createEreader/accessAllLibraries/accessAllTags/accessExplicitContent/selectedTagsNotAccessible)与类型四档(root/admin/user/guest)见配置参数篇 4.3。
九、排障实录
案例 1:播客单集播放 404/无流——episodeId 传错了地方
- 现象:按有声书的习惯把
episodeId放进请求体调POST /api/items/:id/play,会话能创建,但 HLS 播放列表 404。服务端日志连续报错:
[2026-10-27 23:53:24.987] ERROR: Ffmpeg Err "ffmpeg exited with code 183: Error opening input file /metadata/streams/79b67266-.../files.txt.
Error opening input files: Invalid data found when processing input"
[2026-10-27 23:53:24.987] ERROR: [Podcast] getTracklist: episode not found null
- 定位:控制器源码(
LibraryItemController)只在POST /api/items/:id/play/:episodeId读取路径参数并校验单集存在;请求体里的episodeId被静默忽略 → 会话episodeId=null→ 曲目列表解析失败 → 拼出空的转码清单文件 → 转码进程立刻退出(183)。 - 处置:改用路径参数端点重试,实测会话对象
episodeId正确、HLS 200(见 5.4)。 - 教训:同形资源(条目)在不同媒体类型上的 API 形状可能不同;对接前用「响应体回读关键字段」验证(看
episodeId是否非空),不要只看请求是否 200。
案例 2:checknew 反复「No new episodes found」——RSS 只抓检查时点之后的新单集
- 现象:订阅本地 RSS 源后调
/checknew,日志连续输出:
[2026-10-27 23:48:49.676] INFO: [PodcastManager] No new episodes found for podcast "自托管之声(演示)"
而同 URL 的预检接口明明返回 2 集。
- 定位:服务端按
publishedAt > lastEpisodeCheck过滤,而建播客那一刻lastEpisodeCheck被置为当前时间(源码注释:「lastEpisodeCheck will be the current time when adding a new podcast」)——订阅前就已发布的历史单集永远不会被当作「新单集」下载。 - 处置:要补历史单集就用「Web 端手工添加单集/上传」或直接把文件放进播客目录扫描;本实测把测试源的
pubDate更新到建播客之后的时间点再触发/checknew?limit=2,立即返回 1 个新单集并完成下载(见 5.3 日志)。 - 教训:RSS 订阅语义是「跟更」不是「回填」,做迁移/自动化时不要指望 checknew 补历史。
案例 3:API Key 创建成功却 401(isActive 默认未激活)
- 现象:
POST /api/api-keys返回 200 且响应体里有 JWT,但拿去调用立刻 401。响应体里isActive:false。 - 根因:与用户创建同款——创建逻辑
isActive: !!req.body.isActive,不显式传 true 就不激活(配置参数篇案例 2 同源)。 - 处置:创建时带
"isActive":true(或事后PATCH /api/api-keys/:id)。实测激活后同一 Key 调/api/libraries返回 200。 - 教训:自动化脚本里「创建 → 验证可用性」要作为固定两步;验证失败先查对象的激活态再查权限。
十、常见问题 FAQ
Q1:API 请求应该带基础路径吗?
默认 ROUTER_BASE_PATH=/audiobookshelf 时,/api/... 与 /audiobookshelf/api/... 实测均可达;反代场景建议统一用带基础路径的完整 URL,避免与代理路由规则打架。
Q2:token 过期怎么办? 默认 1 小时。长期任务用 API Key(并保持激活);或实现刷新令牌流程(默认 30 天有效期,宽限 10 分钟)。
Q3:扫描接口为什么「秒回」但条目没变? 扫描是异步任务,接口 200 只代表任务受理;核对结果要轮询条目接口或订阅实时推送事件。
Q4:播放会话可以一直开着吗?
不建议。会话持有转码临时目录(/metadata/streams/<id>),close 才会释放;大量僵尸会话会堆积临时文件。客户端异常退出时可在重启后对旧会话补 close。
Q5:怎么给第三方客户端最小心跳上报?
每 10 秒一次 POST /api/session/:id/sync(只带 currentTime),结束时 close 带 timeListened;服务端据此维护 mediaProgress 与收听统计。
十一、本篇小结
- 认证双轨:用户 JWT(登录即用)与 API Key(自动化,注意
isActive与userId必填);同一Bearer头。 - 核心链路四步:
play建会话 →/hls/<sid>/output.m3u8拉流 →sync上报 →close结算;进度经/api/me/progress/:itemId读取实现多端接力。 - 播客三接口:
POST /api/podcasts/feed预检(参数名rssFeed)→POST /api/podcasts建档 →GET /api/podcasts/:id/checknew跟更;单集播放的 episodeId 在路径里。 - 语义边界:checknew 只抓检查时点之后发布的单集(不回填);设置读取走
POST /api/authorize(无GET /api/settings)。 - 运维闭环:手动备份随时可打、定时备份靠配置、恢复靠「备份文件 + 媒体 + /metadata」三件套。