Plainpad 1.2.0 API 使用详解
Plainpad 1.2.0 REST API 全端点逐条实测:21 条路由总览、Bearer 认证与会话吊销闭环、notes/users/settings/密码找回/根资源五域请求回显、filter/sort/page/fields 查询助手、官方文档三处偏差与六条常见问题(2026-11-02)。
Plainpad 1.2.0 API 使用详解
目标:把 Plainpad 1.2.0 的 REST API 全部端点逐个实测讲清——认证模型、五个资源域、查询助手与权限边界,并沉淀官方文档与实测不符的三处偏差。本文所有请求与回显均为 2026-11-02 在真实运行部署上的逐条实测。部署见 《Plainpad 1.2.0 Docker Compose 部署实战》 篇(本栏 B2),配置参数见 《Plainpad 1.2.0 配置参数详解》 篇(本栏 C1)。
一、环境信息
- 被测部署:Docker Compose 项目
plainpad-1.2.0-stack,应用服务 plainpad(官方镜像 alextselegidis/plainpad:latest,应用版本 1.2.0,Laravel 12.69.0 / PHP 8.5.12)+ mysql:8.0(2026-11-02php artisan about实查) - API 基址:
http://localhost:8139/api.php/v1(宿主 8139 → 应用服务 80) - 数据格式:JSON;认证:
Authorization: Bearer {TOKEN}请求头 - 实验账号:管理员
admin@example.org / 12345678(官方播种,id7a17c8fd-b84f-4f2c-8ba3-2a484c1a0e1e);普通用户root@example.org / q1w2e3r4(name "Test User",id54ec677a-5344-4402-9189-bcd2e6470a28) - 取证方式:curl 逐条实测;端点清单以应用路由表(
php artisan route:list)为权威依据
Plainpad 的服务端可编程面集中在 REST API:artisan 自定义命令只有 refresh 与 cleanup 两个(C1 篇 3.4 实测),笔记读写、用户管理、应用设置全部走 HTTP。
二、前置条件
- 部署按 B2 篇完成且服务健康:
curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://localhost:8139/
# HTTP 200
-
主机备有 curl(本文命令在 Ubuntu 26.04.1 LTS 实测)。
-
至少一个可用账号。管理员账号用于 users/settings/根资源域;notes 域任意账号皆可(各账号数据严格隔离,普通用户只见自己的笔记)。
三、API 总览
应用路由表是端点的权威来源(官方文档存在滞后,见本章末):
cd /opt/data/quzuo/tmp/plainpad/stack
docker compose exec plainpad php artisan route:list --path=v1
实测回显(21 条):
GET|HEAD v1 ............................. V1\ApplicationController@retrieve
POST v1 .............................. V1\ApplicationController@install
PUT v1 ............................... V1\ApplicationController@update
GET|HEAD v1/notes ................................. V1\NotesController@list
POST v1/notes ............................... V1\NotesController@create
GET|HEAD v1/notes/{id} ........................ V1\NotesController@retrieve
PUT v1/notes/{id} .......................... V1\NotesController@update
DELETE v1/notes/{id} .......................... V1\NotesController@delete
PATCH v1/notes/{id}/pinned ................... V1\NotesController@pinned
POST v1/refresh ...................... V1\ApplicationController@refresh
POST v1/sessions ......................... V1\SessionsController@create
DELETE v1/sessions/{token} ................. V1\SessionsController@delete
GET|HEAD v1/settings ....................... V1\SettingsController@retrieve
PUT v1/settings ......................... V1\SettingsController@update
GET|HEAD v1/users ................................. V1\UsersController@list
POST v1/users ............................... V1\UsersController@create
POST v1/users/recovery ............. V1\UsersController@recoverPassword
POST v1/users/reset-password ......... V1\UsersController@resetPassword
GET|HEAD v1/users/{id} ........................ V1\UsersController@retrieve
PUT v1/users/{id} .......................... V1\UsersController@update
DELETE v1/users/{id} .......................... V1\UsersController@delete
按资源域归纳(认证列以实测为准):
| 域 | 端点 | 方法 | 认证(实测) | 用途 |
|---|---|---|---|---|
| 应用 | /v1 | GET | 不需要(管理员才见 updates) | 应用信息与可用更新 |
| 应用 | /v1 | POST | 需要 | 安装(建库表+播种) |
| 应用 | /v1 | PUT | 需要 | 执行自动更新 |
| 应用 | /v1/refresh | POST | 需要 | 刷新安装(迁移+缓存) |
| 会话 | /v1/sessions | POST | 不需要 | 登录换 token |
| 会话 | /v1/sessions/{token} | DELETE | 需要 Bearer 头 | 吊销会话(登出) |
| 笔记 | /v1/notes | GET / POST | 需要 | 列表 / 新建 |
| 笔记 | /v1/notes/{id} | GET / PUT / DELETE | 需要 | 读取 / 更新 / 删除 |
| 笔记 | /v1/notes/{id}/pinned | PATCH | 需要 | 置顶开关 |
| 用户 | /v1/users | GET / POST | 需要(仅管理员) | 列表 / 新建 |
| 用户 | /v1/users/{id} | GET / PUT / DELETE | 需要(仅管理员) | 读取 / 更新 / 删除 |
| 用户 | /v1/users/recovery | POST | 不需要 | 找回密码(发邮件) |
| 用户 | /v1/users/reset-password | POST | 不需要 | 用令牌重置密码 |
| 设置 | /v1/settings | GET / PUT | 需要(仅管理员) | 应用级设置读写 |
⚠️ 注意:官方 API 文档(仓库 docs/api.md,2026-11-02 实查)与实现存在三处偏差——① 密码恢复端点文档写
POST /v1/users/recover,实际是POST /v1/users/recovery(按文档调用实测 405,见 5.5);② 文档称笔记更新用POST /v1/notes/{ID},实际只接受 PUT(POST 实测 405,见 7.1);③ 文档称吊销会话「Authorization: Not required」,实测不带 Bearer 头返回 401(见 7.4)。PATCH /v1/notes/{id}/pinned、POST /v1/refresh、POST /v1/users/reset-password三个已实现端点则未写进文档。以路由表与本文实测为准。
四、认证与首次调用
Plainpad 的 API 认证是「登录换 token + Bearer 头」模型:token 即会话凭证,有效期约 12 小时(创建响应中的 expires_at),除 sessions 创建与密码找回外所有端点都要带 Authorization: Bearer {TOKEN}。
4.1 创建会话(登录)
curl -s -X POST http://localhost:8139/api.php/v1/sessions \
-H 'Content-Type: application/json' \
-d '{"email":"root@example.org","password":"q1w2e3r4"}' \
-w '\nHTTP %{http_code}\n'
{"user_id":"54ec677a-5344-4402-9189-bcd2e6470a28","expires_at":"2026-11-02 12:50:06","token":"EGRzpno23b9V3yKCEcj9Lmydlvdkdzt0Fn3fNJzmOnZvYoAmnwXMs4ZIS1IS"}
HTTP 201
响应三字段:user_id(账号 UUID)、expires_at(过期时刻)、token(后续请求凭据)。这正是网页登录框背后发出的请求(B2 篇 4.5 实测同源)。本文后续命令使用管理员 token 实值:
export TOKEN=FE5Is0r75P5jZoalOwHKpWi0NlAsAJwNGRWsRqqFNcwtwD9kWCLQk5RDEPxS
(该 token 由 admin@example.org / 12345678 登录获得,过期后重新执行 4.1 换取。)
4.2 错误凭据
curl -s -X POST http://localhost:8139/api.php/v1/sessions \
-H 'Content-Type: application/json' \
-d '{"email":"root@example.org","password":"wrongpass"}' \
-w '\nHTTP %{http_code}\n'
{"message":"Invalid email or password."}
HTTP 401
4.3 首次带令牌调用
不带令牌访问受保护资源,实测统一 401:
curl -s http://localhost:8139/api.php/v1/notes -w '\nHTTP %{http_code}\n'
{
"message": "Unauthenticated"
}
HTTP 401
带上令牌即 200(笔记域按账号隔离,管理员此时无笔记):
curl -s http://localhost:8139/api.php/v1/notes -H "Authorization: Bearer $TOKEN" -w '\nHTTP %{http_code}\n'
# []
# HTTP 200
4.4 吊销会话(登出)
curl -s -X DELETE http://localhost:8139/api.php/v1/sessions/EGRzpno23b9V3yKCEcj9Lmydlvdkdzt0Fn3fNJzmOnZvYoAmnwXMs4ZIS1IS \
-H "Authorization: Bearer EGRzpno23b9V3yKCEcj9Lmydlvdkdzt0Fn3fNJzmOnZvYoAmnwXMs4ZIS1IS" \
-w '\nHTTP %{http_code}\n'
HTTP 200
吊销后立即用同一 token 再请求,实测已失效(闭环验证):
curl -s http://localhost:8139/api.php/v1/notes -H "Authorization: Bearer EGRzpno23b9V3yKCEcj9Lmydlvdkdzt0Fn3fNJzmOnZvYoAmnwXMs4ZIS1IS" -w '\nHTTP %{http_code}\n'
{
"message": "Unauthenticated"
}
HTTP 401
⚠️ 注意:DELETE 必须同时把待吊销 token 放在路径和 Bearer 头里(不带头的实测回显见第七章 7.4)。吊销后该 token 立即不可用,脚本化场景应在退出前主动调用。
五、各 API 域详解
5.1 Notes(笔记域,任意登录用户)
创建——payload 三字段 title/content/pinned,响应 201 返回完整笔记对象(实测三条,供后续演示):
curl -s -X POST http://localhost:8139/api.php/v1/notes \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"title":"API 测试笔记一","content":"用于演示过滤与排序的第一条内容","pinned":false}' \
-w '\nHTTP %{http_code}\n'
{"id":"38874e6b-bf4b-4246-a74d-a1d0b8085df5","user_id":"7a17c8fd-b84f-4f2c-8ba3-2a484c1a0e1e","title":"API \u6d4b\u8bd5\u7b14\u8bb0\u4e00","content":"\u7528\u4e8e\u6f14\u793a\u8fc7\u6ee4\u4e0e\u6392\u5e8f\u7684\u7b2c\u4e00\u6761\u5185\u5bb9","pinned":false,"updated_at":"2026-11-02T00:50:22.000000Z","created_at":"2026-11-02T00:50:22.000000Z"}
HTTP 201
(另两条演示数据:{"title":"购物清单","content":"牛奶 鸡蛋 面包","pinned":true} → id 1582cda4-ba69-4370-ad8e-6f88e9f0a0c6;{"title":"API 测试笔记二","content":"第二篇测试内容"} → id 01ee6648-c96c-4fe9-a4c6-aaaeea24ea8a,均 HTTP 201。中文在 JSON 响应中以 \uXXXX 转义回显,下同。)
列表:
curl -s http://localhost:8139/api.php/v1/notes -H "Authorization: Bearer $TOKEN" -w '\nHTTP %{http_code}\n'
[{"id":"38874e6b-bf4b-4246-a74d-a1d0b8085df5","user_id":"7a17c8fd-b84f-4f2c-8ba3-2a484c1a0e1e","content":"\u7528\u4e8e\u6f14\u793a\u8fc7\u6ee4\u4e0e\u6392\u5e8f\u7684\u7b2c\u4e00\u6761\u5185\u5bb9","title":"API \u6d4b\u8bd5\u7b14\u8bb0\u4e00","pinned":false,"created_at":"2026-11-02T00:50:22.000000Z","updated_at":"2026-11-02T00:50:22.000000Z"},{"id":"01ee6648-c96c-4fe9-a4c6-aaaeea24ea8a","user_id":"7a17c8fd-b84f-4f2c-8ba3-2a484c1a0e1e","content":"\u7b2c\u4e8c\u7bc7\u6d4b\u8bd5\u5185\u5bb9","title":"API \u6d4b\u8bd5\u7b14\u8bb0\u4e8c","pinned":false,"created_at":"2026-11-02T00:50:24.000000Z","updated_at":"2026-11-02T00:50:24.000000Z"},{"id":"1582cda4-ba69-4370-ad8e-6f88e9f0a0c6","user_id":"7a17c8fd-b84f-4f2c-8ba3-2a484c1a0e1e","content":"\u725b\u5976 \u9e21\u86cb \u9762\u5305","title":"\u8d2d\u7269\u6e05\u5355","pinned":true,"created_at":"2026-11-02T00:50:23.000000Z","updated_at":"2026-11-02T00:50:23.000000Z"}]
HTTP 200
⚠️ 注意:默认返回顺序既非时间也非置顶优先(上例 22s→24s→23s),脚本不要依赖默认序,显式加
sort助手(第六章)。
读取单条:
curl -s http://localhost:8139/api.php/v1/notes/01ee6648-c96c-4fe9-a4c6-aaaeea24ea8a -H "Authorization: Bearer $TOKEN" -w '\nHTTP %{http_code}\n'
{"id":"01ee6648-c96c-4fe9-a4c6-aaaeea24ea8a","user_id":"7a17c8fd-b84f-4f2c-8ba3-2a484c1a0e1e","created_at":"2026-11-02T00:50:24.000000Z","updated_at":"2026-11-02T00:50:24.000000Z","title":"API \u6d4b\u8bd5\u7b14\u8bb0\u4e8c","content":"\u7b2c\u4e8c\u7bc7\u6d4b\u8bd5\u5185\u5bb9","pinned":false}
HTTP 200
更新——正确方法是 PUT(官方文档写 POST,实测 POST 返回 405,见 7.1):
curl -s -X PUT http://localhost:8139/api.php/v1/notes/01ee6648-c96c-4fe9-a4c6-aaaeea24ea8a \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"title":"API 测试笔记二(已更新)","content":"更新后的内容","pinned":false}' \
-w '\nHTTP %{http_code}\n'
{"id":"01ee6648-c96c-4fe9-a4c6-aaaeea24ea8a","user_id":"7a17c8fd-b84f-4f2c-8ba3-2a484c1a0e1e","created_at":"2026-11-02T00:50:24.000000Z","updated_at":"2026-11-02T00:50:24.000000Z","title":"API \u6d4b\u8bd5\u7b14\u8bb0\u4e8c\uff08\u5df2\u66f4\u65b0\uff09","content":"\u66f4\u65b0\u540e\u7684\u5185\u5bb9","pinned":false}
HTTP 200
置顶开关——独立端点,payload 必填 pinned(不带载荷实测 422):
curl -s -X PATCH http://localhost:8139/api.php/v1/notes/38874e6b-bf4b-4246-a74d-a1d0b8085df5/pinned -H "Authorization: Bearer $TOKEN" -w '\nHTTP %{http_code}\n'
# {"message":"The pinned field is required.","errors":{"pinned":["The pinned field is required."]}}
# HTTP 422
curl -s -X PATCH http://localhost:8139/api.php/v1/notes/38874e6b-bf4b-4246-a74d-a1d0b8085df5/pinned \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"pinned":true}' -w '\nHTTP %{http_code}\n'
# HTTP 200(成功响应体为空)
生效验证(配合 fields 助手,置顶笔记排到了列表首位):
curl -s "http://localhost:8139/api.php/v1/notes?fields=id,title,pinned" -H "Authorization: Bearer $TOKEN"
[{"id":"38874e6b-bf4b-4246-a74d-a1d0b8085df5","title":"API \u6d4b\u8bd5\u7b14\u8bb0\u4e00","pinned":true},{"id":"01ee6648-c96c-4fe9-a4c6-aaaeea24ea8a","title":"API \u6d4b\u8bd5\u7b14\u8bb0\u4e8c\uff08\u5df2\u66f4\u65b0\uff09","pinned":false},{"id":"1582cda4-ba69-4370-ad8e-6f88e9f0a0c6","title":"\u8d2d\u7269\u6e05\u5355","pinned":true}]
删除:
curl -s -X DELETE http://localhost:8139/api.php/v1/notes/01ee6648-c96c-4fe9-a4c6-aaaeea24ea8a -H "Authorization: Bearer $TOKEN" -w '\nHTTP %{http_code}\n'
# HTTP 200(响应体为空)
错误路径——不存在与越权都是 404/401 组合:
curl -s http://localhost:8139/api.php/v1/notes/00000000-0000-0000-0000-000000000000 -H "Authorization: Bearer $TOKEN" -w '\nHTTP %{http_code}\n'
{"message":"Not Found"}
HTTP 404
(三条演示笔记实验后已全部 DELETE 清理,终态 GET /v1/notes 回显 []。)
5.2 Users(用户域,仅管理员)
列表:
curl -s http://localhost:8139/api.php/v1/users -H "Authorization: Bearer $TOKEN" -w '\nHTTP %{http_code}\n'
[{"id":"7a17c8fd-b84f-4f2c-8ba3-2a484c1a0e1e","name":"Admin","email":"admin@example.org","admin":true,"locale":"en-US","view":"compact","line":"full","sort":"modified","theme":"light","encrypt":false,"created_at":"2026-11-02T00:20:21.000000Z","updated_at":"2026-11-02T00:48:23.000000Z"},{"id":"54ec677a-5344-4402-9189-bcd2e6470a28","name":"Test User","email":"root@example.org","admin":false,"locale":"en-US","view":"compact","line":"full","sort":"modified","theme":"light","encrypt":false,"created_at":"2026-11-02T00:21:48.000000Z","updated_at":"2026-11-02T00:21:48.000000Z"}]
HTTP 200
创建(新用户偏好全量给出,password 只进不出——任何响应都不回显口令):
curl -s -X POST http://localhost:8139/api.php/v1/users \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"API Temp","email":"api-temp@example.org","password":"q1w2e3r4","locale":"zh-CN","view":"compact","line":"full","sort":"modified","theme":"light","admin":false}' \
-w '\nHTTP %{http_code}\n'
{"id":"20dd9c08-50c5-4c12-94ee-653396566774","name":"API Temp","email":"api-temp@example.org","locale":"zh-CN","view":"compact","line":"full","sort":"modified","theme":"light","encrypt":false,"admin":false,"updated_at":"2026-11-02T00:50:53.000000Z","created_at":"2026-11-02T00:50:53.000000Z"}
HTTP 201
更新(PUT,全量提交语义——漏字段即重置,详见 C1 篇 5.6):
curl -s -X PUT http://localhost:8139/api.php/v1/users/20dd9c08-50c5-4c12-94ee-653396566774 \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"API Temp Renamed","email":"api-temp@example.org","locale":"zh-CN","view":"compact","line":"full","sort":"modified","theme":"light","admin":false}' \
-w '\nHTTP %{http_code}\n'
{"id":"20dd9c08-50c5-4c12-94ee-653396566774","created_at":"2026-11-02T00:50:53.000000Z","updated_at":"2026-11-02T00:50:53.000000Z","name":"API Temp Renamed","email":"api-temp@example.org","locale":"zh-CN","view":"compact","line":"full","sort":"modified","theme":"light","encrypt":false,"admin":false}
HTTP 200
权限边界——用普通用户(Test User)token 访问用户列表,实测不是 403 而是 401:
curl -s http://localhost:8139/api.php/v1/users -H "Authorization: Bearer $ROOT_TOKEN" -w '\nHTTP %{http_code}\n'
{"message":"Unauthorized"}
HTTP 401
删除(临时账号实验后删除,200 空体):
curl -s -X DELETE http://localhost:8139/api.php/v1/users/20dd9c08-50c5-4c12-94ee-653396566774 -H "Authorization: Bearer $TOKEN" -w '\nHTTP %{http_code}\n'
# HTTP 200
($ROOT_TOKEN 指 4.1 所得普通用户 token;401 语义与越权处理见 7.3。)
5.3 Settings(应用级设置,仅管理员)
读写九个键(default_locale + mail_* 八项,逐项含义见 C1 篇 5.5)。实测 GET 回显与 PUT 成功(PUT 成功响应体为空):
curl -s http://localhost:8139/api.php/v1/settings -H "Authorization: Bearer $TOKEN"
# {"mail_from_address":"notes@quzuo.net","mail_port":"1025","mail_password":"","mail_from_name":"Plainpad","default_locale":"zh-CN","mail_driver":"smtp","mail_host":"10.89.58.1","mail_encryption":"","mail_username":""}
curl -s -X PUT http://localhost:8139/api.php/v1/settings \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"default_locale":"zh-CN","mail_driver":"smtp","mail_host":"10.89.58.1","mail_port":"1025","mail_username":"","mail_password":"","mail_encryption":"","mail_from_address":"notes@quzuo.net","mail_from_name":"Plainpad"}' \
-w '\nHTTP %{http_code}\n'
# HTTP 200
普通用户 PUT settings 实测同样 401 {"message":"Unauthorized"}(与 5.2 权限边界一致)。
5.4 Sessions 补充语义
token 背后是服务端会话记录(会话驱动 file,存应用 storage/ 内,C1 篇第一章 about 回显):重建应用即所有 token 失效,脚本需能处理「运行中突然 401」并重登。同一账号可并存多个 token(网页端与脚本各持一个互不影响),吊销只作用于指定 token。
5.5 密码找回两步(无需认证)
第一步 recovery(官方文档误写 recover,实测 405,见 7.2):
curl -s -X POST http://localhost:8139/api.php/v1/users/recovery \
-H 'Content-Type: application/json' -d '{"email":"root@example.org"}' \
-w '\nHTTP %{http_code}\n'
# HTTP 200(响应体为空)
200 只代表「已受理并尝试投递重置邮件」,不代表邮件送达。令牌以 bcrypt 哈希落 password_resets 表(实测查库):
docker compose exec mysql mysql -uroot -psecret -e "select email,token,created_at from plainpad.password_resets;"
email token created_at
root@example.org $2y$10$bxaLk8N41CtyLna5ljK83eVGdNqziT8nDqtLpySahI0MItsZURprm 2026-11-02 00:51:46
明文令牌只在邮件正文中,库里不可回查——没配好 SMTP(C1 篇 5.5)时找回流程实际走不通。
第二步 reset-password(携带邮件中的令牌;用无效令牌实测错误路径):
curl -s -X POST http://localhost:8139/api.php/v1/users/reset-password \
-H 'Content-Type: application/json' \
-d '{"email":"root@example.org","token":"badtoken","password":"q1w2e3r4"}' \
-w '\nHTTP %{http_code}\n'
{"message":"Invalid or expired reset token."}
HTTP 422
(实验产生的 password_resets 行已清理。)
5.6 根资源(应用信息与安装/更新)
GET /v1——免认证可调,但 updates 只对管理员显示(源码逻辑:非管理员直接返回空对象)。用管理员 token 实测:
curl -s http://localhost:8139/api.php/v1 -H "Authorization: Bearer $TOKEN" -w '\nHTTP %{http_code}\n'
# []
# HTTP 200
空数组即「当前无可用更新」(更新源 APP_REPOSITORY 见 C1 篇 5.4)。
POST /v1(install)——对已安装实例是受保护的空操作,实测 401:
curl -s -X POST http://localhost:8139/api.php/v1 -H "Authorization: Bearer $TOKEN" -w '\nHTTP %{http_code}\n'
# {"message":"Unauthorized"}
# HTTP 401
源码给出双层守卫(控制器实查):表已存在即拒绝,防止 migrate:fresh 清空生产库——该端点只服务于「空库首装」场景,日常部署用不到(首装由入口脚本自动完成,B2 篇 4.4)。
PUT /v1(update)——触发自动更新,无新版本时实测 404:
curl -s -X PUT http://localhost:8139/api.php/v1 -H "Authorization: Bearer $TOKEN" -w '\nHTTP %{http_code}\n'
# {"message":"Not Found"}
# HTTP 404
404 语义 = 更新源没有比当前更新的版本。即使返回可更新,Docker 形态也不要调用它——更新写入应用文件系统,重建即丢(B2 篇第五节),正确升级路径是拉新镜像 + php artisan refresh。
POST /v1/refresh——升级后的刷新(迁移+缓存),实测 200 空体:
curl -s -X POST http://localhost:8139/api.php/v1/refresh -H "Authorization: Bearer $TOKEN" -w '\nHTTP %{http_code}\n'
# HTTP 200
六、分页/过滤/排序/裁剪通用约定
GET 类列表端点(notes/users)支持四个查询助手,均为实测。以 5.1 的三条演示笔记为数据:
| 助手 | 参数 | 行为 | 实测备注 |
|---|---|---|---|
| 搜索 | filter=关键词 | 按标题与内容模糊匹配 | 中文需 URL 编码 |
| 排序 | sort=字段&direction=asc|desc | 按指定字段排序 | 官方文档称最多三个字段依次生效 |
| 分页 | page=N&length=M | 第 N 页、每页 M 条 | length 默认 20 |
| 裁剪 | fields=a,b,c | 响应只留指定字段 | 可与任意助手叠加 |
filter——?filter=购物(编码后)精确命中一条:
curl -s "http://localhost:8139/api.php/v1/notes?filter=%E8%B4%AD%E7%89%A9" -H "Authorization: Bearer $TOKEN"
[{"id":"1582cda4-ba69-4370-ad8e-6f88e9f0a0c6","user_id":"7a17c8fd-b84f-4f2c-8ba3-2a484c1a0e1e","content":"\u725b\u5976 \u9e21\u86cb \u9762\u5305","title":"\u8d2d\u7269\u6e05\u5355","pinned":true,"created_at":"2026-11-02T00:50:23.000000Z","updated_at":"2026-11-02T00:50:23.000000Z"}]
?filter=测试 则命中两条标题含「测试」的笔记(实测 200,输出节选见 5.1 数据结构)。
sort——按创建时间升序,顺序严格 22s→23s→24s:
curl -s "http://localhost:8139/api.php/v1/notes?sort=created_at&direction=asc&fields=id,title,created_at" -H "Authorization: Bearer $TOKEN"
[{"id":"38874e6b-bf4b-4246-a74d-a1d0b8085df5","title":"API \u6d4b\u8bd5\u7b14\u8bb0\u4e00","created_at":"2026-11-02T00:50:22.000000Z"},{"id":"1582cda4-ba69-4370-ad8e-6f88e9f0a0c6","title":"\u8d2d\u7269\u6e05\u5355","created_at":"2026-11-02T00:50:23.000000Z"},{"id":"01ee6648-c96c-4fe9-a4c6-aaaeea24ea8a","title":"API \u6d4b\u8bd5\u7b14\u8bb0\u4e8c\uff08\u5df2\u66f4\u65b0\uff09","created_at":"2026-11-02T00:50:24.000000Z"}]
按标题降序(中文标题「购物清单」排到首):
curl -s "http://localhost:8139/api.php/v1/notes?sort=title&direction=desc&fields=title" -H "Authorization: Bearer $TOKEN"
# [{"title":"\u8d2d\u7269\u6e05\u5355"},{"title":"API \u6d4b\u8bd5\u7b14\u8bb0\u4e8c\uff08\u5df2\u66f4\u65b0\uff09"},{"title":"API \u6d4b\u8bd5\u7b14\u8bb0\u4e00"}]
page/length——三条数据按每页 2 条翻页:
curl -s "http://localhost:8139/api.php/v1/notes?page=1&length=2&fields=id" -H "Authorization: Bearer $TOKEN"
# [{"id":"38874e6b-bf4b-4246-a74d-a1d0b8085df5"},{"id":"01ee6648-c96c-4fe9-a4c6-aaaeea24ea8a"}]
curl -s "http://localhost:8139/api.php/v1/notes?page=2&length=2&fields=id" -H "Authorization: Bearer $TOKEN"
# [{"id":"1582cda4-ba69-4370-ad8e-6f88e9f0a0c6"}]
fields——投影任意组合(上文已多次使用),批量同步场景只取 id,updated_at 可显著瘦身响应。
💡 技术拓展:REST 列表接口的分页模型常见三档——
模型 代表 特点 page/length 偏移分页 Plainpad(本文) 实现简单;深翻页性能差、并发写入下页界漂移 cursor 游标分页 Memos/多数现代 API 稳定不漂移,适合无限滚动 全量返回 小型配置类 API 无分页成本,数据量大时不可行 Plainpad 笔记量级(个人千条)下偏移分页足够;做增量同步时建议
sort=updated_at&direction=asc配合 page 遍历,并容忍页界漂移。
七、常见问题
7.1 按官方文档 POST /v1/notes/{ID} 更新笔记返回 405
{
"message": "The POST method is not supported for route v1/notes/01ee6648-c96c-4fe9-a4c6-aaaeea24ea8a. Supported methods: GET, HEAD, PUT, DELETE."
}
HTTP 405
文档滞后:更新笔记的正确方法是 PUT(5.1 实测 200)。405 响应里的 Supported methods 即权威提示。
7.2 按文档调 POST /v1/users/recover 返回 405
recover 会被路由匹配成 users/{id}(id=recover)的资源路由而 405。实际端点是 POST /v1/users/recovery(路由表实查,5.5 实测 200)。同理注意 reset-password 带连字符。
7.3 普通用户调管理端点返回 401 而不是 403
实测越权(非管理员访问 users 列表、settings 写入)统一返回 {"message":"Unauthorized"} + HTTP 401。写客户端时不要只按「401=未登录、403=无权限」的惯例分支——Plainpad 两种情况都是 401,需结合本地是否已持 token 判断是重登还是提示权限不足。
7.4 吊销会话不带 Authorization 头返回 401
curl -s -X DELETE http://localhost:8139/api.php/v1/sessions/EGRzpno23b9V3yKCEcj9Lmydlvdkdzt0Fn3fNJzmOnZvYoAmnwXMs4ZIS1IS -w '\nHTTP %{http_code}\n'
{
"message": "Unauthenticated"
}
HTTP 401
文档称该端点无需认证,实测必须同时带 Bearer 头(4.4 实测 200 的写法)。
7.5 对已安装实例 POST /v1 返回 401
这是防误清库守卫(5.6 源码解释):表已存在即拒绝,即使管理员 token 也 401。首装请交给部署入口流程(B2 篇 4.4),不要脚本化调用该端点。
7.6 脚本运行中途突然全部 401
两种原因:token 过期(约 12 小时,4.1 响应 expires_at)或应用被重建(file 会话驱动,C1 篇第一章)。健壮脚本应在收到 401 时自动重登换新 token 重试一次,并避免把 token 写死在代码里。
八、参考链接
- 官方 API 文档(存在 7.1/7.2/7.4 三处滞后):https://github.com/alextselegidis/plainpad/blob/master/docs/api.md (2026-11-02 实查)
- GitHub 仓库:https://github.com/alextselegidis/plainpad (1.2.0)
- 站内同栏:《Plainpad 1.2.0 介绍与选型指南》、《Plainpad 1.2.0 Docker Compose 部署实战》、《Plainpad 1.2.0 配置参数详解》
- 站内同类 API 参考:《Blinko API 使用详解》