Pinepods 0.9.0 API 使用详解
详解 Pinepods 0.9.0 REST API 认证、参数约定与错误语义,按系统信息/用户管理/密钥管理/播客订阅/剧集五域给出 curl 实测请求与响应回显。
Pinepods 0.9.0 API 使用详解
目标:讲清 Pinepods 0.9.0 REST API 的认证方式、参数约定与错误语义,按系统信息、用户管理、密钥管理、播客订阅、剧集五个域逐端点给出真实请求示例与实测响应回显,覆盖用户与密钥管理的完整命令面。本文所有请求与响应均为 0.9.0 真实部署环境的 curl 实测记录。
一、环境信息
| 项目 | 值 | 说明 |
|---|---|---|
| 服务端版本 | 0.9.0 | get_pinepods_version 实测回显 |
| API 基地址 | http://<服务器IP>:8040(与 Web 同源同端口) | 实测部署映射到宿主 8140,下文以 8140 为准 |
| OpenAPI 文档 | /openapi.json,共 232 个端点 | 部署实例实测拉取计数 |
| 协议 | REST + JSON(请求体 application/json) | 实测 |
| 测试账号 | admin(管理员,user_id=2)/ demo(普通用户,user_id=3),密码 q1w2e3r4 | 实测环境 |
二、前置条件
- 已完成部署与初始化向导(部署见《Pinepods Docker Compose 部署指南》),并已登录 Web 界面。
- 已创建 API 密钥:登录 Web 界面 → 用户设置 → API 密钥页创建,密钥为 64 位随机串。下文示例统一用
<你的API密钥>占位,替换为你自己的密钥。 - 客户端工具:curl 即可(本文全部实测用 curl),任何支持自定义请求头的 HTTP 工具均适用。
三、API 总览
Pinepods 的 API 与 Web 界面同源同端口(8040),无需额外开启,密钥创建即可用。三个结构特征(实测自 openapi.json):
- 路径分域:
/api/data/*是数据与操作域(占绝大多数端点),/api/auth/*是 OIDC 认证回调域,另有免认证的/api/health健康检查。 - 认证方式:单一方式——请求头
Api-Key: <密钥>,除 health 外所有端点都要求该头(openapi.json 的 security 声明统一为api_key)。 - 参数位置:用户 ID 一类资源定位参数部分走路径(如
/api/data/return_pods/{user_id}),部分走查询串(如/api/data/get_stats?user_id=),创建/修改类走 JSON 请求体;写请求前先查 openapi.json 的 schema 是最稳妥的做法。
限流约定:官方文档与 OpenAPI 定义均未声明限流策略,实测连续调用无节流;但备份、批量下载类重操作仍建议控制并发,避免占满带宽。
四、认证与首次调用
第一组实测:免认证端点与认证端点的分界。/api/health 无需密钥:
$ curl -s http://127.0.0.1:8140/api/health
{"status":"healthy","database":true,"redis":true,"timestamp":"2026-10-30T00:13:35.849074586Z"}
带错误密钥访问受保护端点,返回 401 与结构化错误体:
$ curl -s -w '\nHTTP %{http_code}\n' http://127.0.0.1:8140/api/data/get_stats?user_id=2 -H "Api-Key: WRONGKEY123"
{"error":"Authentication failed","message":"Authentication error: Your API key is either invalid or does not have correct permission","status_code":401}
HTTP 401
换正确密钥即通过(get_stats 需查询参数 user_id):
$ curl -s http://127.0.0.1:8140/api/data/get_stats?user_id=2 -H "Api-Key: <你的API密钥>"
{"EpisodesDownloaded":0,"EpisodesSaved":0,"GpodderUrl":"http://localhost:8042","Pod_Sync_Type":"gpodder","PodcastsAdded":2,"PodcastsPlayed":0,"TimeListened":0,"UserCreated":"2026-10-29T17:40:53.975731"}
首个调用闭环验证完成:认证头正确、user_id=2 对应管理员账号、返回体为驼峰命名的统计 JSON。
五、各 API 域详解
5.1 系统信息域
GET /api/data/get_pinepods_version —— 无参数,返回服务端版本:
$ curl -s http://127.0.0.1:8140/api/data/get_pinepods_version -H "Api-Key: <你的API密钥>"
{"data":"0.9.0"}
用途:部署后核对版本、升级后确认新版本生效。
5.2 用户管理域
本域是管理员日常命令面的核心:查号、建号、查状态。
GET /api/data/get_user_info?user_id=2 —— 列出全部用户(管理动作,任何已认证密钥可查):
$ curl -s "http://127.0.0.1:8140/api/data/get_user_info?user_id=2" -H "Api-Key: <你的API密钥>"
[{"userid":1,"fullname":"Background Tasks","username":"background_tasks","email":"inactive","isadmin":0},{"userid":2,"fullname":"Pinepods Admin","username":"admin","email":"admin@pinepods.local","isadmin":1},{"userid":3,"fullname":"Demo User","username":"demo","email":"demo@pinepods.local","isadmin":0}]
解读:userid=1 是系统内置的后台任务账号(常驻,不要删),userid=2 为管理员,userid=3 为实测创建的普通用户。
GET /api/data/get_user?username=admin —— 按用户名反查数字 ID:
$ curl -s "http://127.0.0.1:8140/api/data/get_user?username=admin" -H "Api-Key: <你的API密钥>"
{"status":"success","retrieved_id":2}
POST /api/data/add_user —— 创建用户。请求体四个必填字段 fullname/username/email/hash_pw(密码原值,服务端负责散列)。先看一个缺字段的反面实测:
$ curl -s -w '\nHTTP %{http_code}\n' -X POST http://127.0.0.1:8140/api/data/add_user -H "Api-Key: <你的API密钥>" -H "Content-Type: application/json" -d '{"username":"demo","password":"q1w2e3r4","fullname":"Demo User","email":"demo@pinepods.local","is_admin":false}'
Failed to deserialize the JSON body into the target type: missing field `hash_pw` at line 1 column 111
HTTP 422
字段名必须按 schema 写 hash_pw(写 password 会 422)。正确实测:
$ curl -s -w '\nHTTP %{http_code}\n' -X POST http://127.0.0.1:8140/api/data/add_user -H "Api-Key: <你的API密钥>" -H "Content-Type: application/json" -d '{"username":"demo","fullname":"Demo User","email":"demo@pinepods.local","hash_pw":"q1w2e3r4"}'
{"detail":"Success","user_id":3}
HTTP 200
返回的 user_id=3 即新用户 ID,后续以它操作该用户。改密/改名的同族端点:PUT /api/data/set_password/{user_id}、PUT /api/data/set_fullname/{user_id}。
GET /api/data/check_mfa_enabled/{user_id} 与 GET /api/data/first_login_done/{user_id} —— 用户安全与引导状态查询:
$ curl -s http://127.0.0.1:8140/api/data/check_mfa_enabled/2 -H "Api-Key: <你的API密钥>"
{"mfa_enabled":false}
$ curl -s http://127.0.0.1:8140/api/data/first_login_done/2 -H "Api-Key: <你的API密钥>"
{"FirstLogin":false}
MFA 管理命令面:GET /api/data/generate_mfa_secret/{user_id} 生成密钥 → 用户扫码绑定 → POST /api/data/save_mfa_secret 保存 → DELETE /api/data/delete_mfa 解绑。
5.3 密钥管理域
GET /api/data/get_api_info/{user_id} —— 列出用户名下的 API 密钥(只显尾四位):
$ curl -s http://127.0.0.1:8140/api/data/get_api_info/2 -H "Api-Key: <你的API密钥>"
{"api_info":[{"apikeyid":1,"created":"2026-10-29 17:40:30.448188","lastfourdigits":"Rnid","podcastids":[],"userid":1,"username":"background_tasks"},{"apikeyid":2,"created":"2026-10-29 17:40:53.975731","lastfourdigits":"Myvs","podcastids":[],"userid":2,"username":"admin"}]}
POST /api/data/create_api_key —— 创建密钥,必填 user_id 与 rssonly。rssonly=true 生成仅能拉 RSS 的受限密钥。先看越权的反面实测——用管理员的密钥替其他用户建普通密钥会被 403 拒绝:
$ curl -s -w '\nHTTP %{http_code}\n' -X POST http://127.0.0.1:8140/api/data/create_api_key -H "Api-Key: <你的API密钥>" -H "Content-Type: application/json" -d '{"user_id":3,"rssonly":false}'
{"error":"Authorization failed","message":"Authorization error: Your API key is either invalid or does not have correct permission","status_code":403}
HTTP 403
为自己创建 RSS-only 密钥的实测(rssonly=true,响应体即完整新密钥,仅此一次可见):
$ curl -s -X POST http://127.0.0.1:8140/api/data/create_api_key -H "Api-Key: <你的API密钥>" -H "Content-Type: application/json" -d '{"user_id":2,"rssonly":true}'
{"rss_key":"UeRnxeUeJq6Xpbx7L7DnIOl6VYETGQ3L0riAmaTF42uR6KcPMduMzWrMTXEtiGXC"}
密钥管理命令面:创建用 create_api_key,吊销用 DELETE /api/data/delete_api_key;rssonly 配合可选的 podcast_ids 数组可以把密钥限定到指定播客,适合喂给第三方聚合器。
5.4 播客订阅域
GET /api/data/check_podcast?user_id=&podcast_name=&podcast_url= —— 订阅前查重(三个查询参数都必填,URL 值需编码):
$ curl -s "http://127.0.0.1:8140/api/data/check_podcast?user_id=2&podcast_name=The%20Daily&podcast_url=https%3A%2F%2Ffeeds.simplecast.com%2F54nAGcIl" -H "Api-Key: <你的API密钥>"
{"exists":false}
POST /api/data/add_custom_podcast —— 按 RSS 添加订阅,请求体必填 feed_url 与 user_id(可选 feed_cutoff 限制拉取天数)。实测响应是解析后的完整订阅记录(节选):
$ curl -s -X POST http://127.0.0.1:8140/api/data/add_custom_podcast -H "Api-Key: <你的API密钥>" -H "Content-Type: application/json" -d '{"feed_url":"https://feeds.simplecast.com/54nAGcIl","user_id":2}'
{"data":{"artworkurl":"https://image.simplecastcdn.com/images/7f2f4c05-9c2f-4deb-82b7-b538062bc22d/73549bf1-94b3-40ff-8aeb-b4054848ec1b/3000x3000/the-daily-album-art-original.jpg?aid=rss_feed","author":"The New York Times",...,"episodecount":3021,"explicit":false,"feedcutoffdays":30,"feedurl":"https://feeds.simplecast.com/54nAGcIl",...,"podcastid":3,"podcastname":"The Daily","userid":2,...}}
HTTP 200
(输出节选:服务端已自动解析封面、作者与 3021 集剧集清单,podcastid=3 为服务端分配的订阅 ID。)添加后再查重即 {"exists":true}。
坏地址的实测错误——feed 不可达时返回 400 并带原因:
$ curl -s -w '\nHTTP %{http_code}\n' -X POST http://127.0.0.1:8140/api/data/add_custom_podcast -H "Api-Key: <你的API密钥>" -H "Content-Type: application/json" -d '{"feed_url":"https://feeds.fireside.fm/decode/rss","user_id":2}'
{"error":"Bad request","message":"Bad request: Feed request failed: HTTP 404 Not Found","status_code":400}
HTTP 400
GET /api/data/return_pods/{user_id} —— 列出订阅(注意 user_id 是路径参数,实测写查询串会返回空):
$ curl -s http://127.0.0.1:8140/api/data/return_pods/2 -H "Api-Key: <你的API密钥>"
{"pods":[{"podcastid":1,"podcastname":"Pinepods News Podcast","artworkurl":"https://news.pinepods.online/assets/pinepods-logo.jpeg",...,"episodecount":21,...,"feedurl":"https://news.pinepods.online/feed.xml","author":"Collin Pendleton",...},{"podcastid":2,"podcastname":"声东击西",...},{"podcastid":3,"podcastname":"The Daily",...}]}
(输出节选:每条含订阅 ID、封面、集数、作者与 RSS 地址,中文播客名原样返回。)同族管理端点:PUT /api/data/podcast/toggle_favorite(收藏)、POST /api/data/podcast/set_playback_speed(倍速)、POST /api/data/download_all_podcast(整季下载)。
5.5 剧集域
GET /api/data/return_episodes/{user_id} —— 返回用户订阅下全部剧集(数据量大,展示节选):
$ curl -s http://127.0.0.1:8140/api/data/return_episodes/2 -H "Api-Key: <你的API密钥>"
{"episodes":[{"podcastid":2,"podcastname":"声东击西","episodetitle":"#408 李晶:互联网增长的红利、代价与一个人的十年自我重建丨十周年特别节目","episodepubdate":"2026-10-22T10:45:00","episodedescription":"\n <p>本期节目是声东击西「十周年」特别系列的第四期。...","...":"..."}]}
(输出节选:剧集描述为 RSS 原始 HTML,前端负责渲染;生产消费此接口时按 episodepubdate 排序、以剧集标题去重即可。)剧集域其余常用端点:GET /api/data/saved_episode_list/{user_id}(收藏剧集)、POST /api/data/bulk_mark_episodes_completed(批量标记已听)、GET /api/data/download_status/{user_id}(下载进度)。
六、参数与错误通用约定
- user_id 语义:接口里的
user_id指「以哪个用户的身份/数据操作」,管理员的密钥操作别人的数据会触发 403(实测见 5.3);先get_user反查 ID 再调用是最稳的顺序。 - 参数位置两态:同是用户定位,
return_pods/{user_id}走路径、get_stats走查询串——放错位置不会报错而是返回空体(实测return_pods?user_id=2返回空),排查时先核对 openapi.json 的参数定义。 - 错误四态(全部实测):
- 401 认证失败:密钥错误或缺失,错误体
{"error":"Authentication failed",...,"status_code":401}; - 403 授权失败:密钥有效但无权操作目标资源(如替他人建密钥);
- 422 反序列化失败:JSON 字段名/类型不符合 schema,服务端直接返回 Actix 原始提示
missing field \hash_pw``; - 400 业务拒绝:如 feed 拉取失败,错误体带上游原因(
Feed request failed: HTTP 404 Not Found)。
- 401 认证失败:密钥错误或缺失,错误体
- 响应风格:查询多为单一 JSON 对象或
{"data":...}/{"pods":...}/{"episodes":...}包裹数组;写操作成功返回{"detail":"Success",...}并带新建资源 ID;完整 schema 一律以部署实例的/openapi.json为准(232 个端点,可导入任何 OpenAPI 工具浏览调试)。
七、常见问题
Q1:Api-Key 头带了仍返回 401。
逐项核对:头名是 Api-Key(带连字符、大小写不敏感但拼写必须一致);密钥是否完整复制(64 位无空格);该密钥是否已被吊销(get_api_info 只能看到尾四位,对不上就重新创建)。
Q2:调用返回空体没有报错。
大概率是参数放错位置(路径参数写成了查询串,实测 5.4 有复现路径)。对照 openapi.json 该端点的 parameters 定义确认 in: path 还是 in: query。
Q3:想给其他用户建密钥/改密码返回 403。
这是服务端授权边界:普通密钥只能操作自己 user_id 的资源。正确做法是让目标用户自己生成密钥,或用目标用户身份在 Web 界面操作;管理员建号用 add_user(不受此限)。
Q4:add_user 返回 422 提示 missing field。
按 schema 核对四个必填字段名:fullname、username、email、hash_pw。密码字段名是 hash_pw(值传密码原文,服务端负责散列),写 password 必 422(实测见 5.2)。
Q5:添加播客一直 400。
先在浏览器直接打开该 RSS 地址确认可达;再确认 feed_url 是 RSS/Atom 源而不是节目主页。官方搜索源(SEARCH_API_URL 指向的服务)不可达不影响本接口,手动 RSS 添加不依赖搜索服务。
八、参考链接
- 部署实例自带 OpenAPI 文档:http://<服务器IP>:8040/openapi.json
- 官方 API 文档页:https://docs.pinepods.online
- GitHub 仓库:https://github.com/madeofpendletonwool/Pinepods
- 本专栏配套文档:《Pinepods 介绍与选型指南》、《Pinepods Docker Compose 部署指南》、《Pinepods 0.9.0 配置参数详解》