用有范围的管理 API 令牌读取提交
管理 API 令牌只存哈希、只显示一次,并按能力限定范围:五条读取路由覆盖表单、提交、拒收和一条尝试线程,用游标分页,没有任何写入面。
本文由机器翻译。 阅读英文原文

管理 API 是 FormFlow 的读取侧:五条 GET 路由、一个持有者令牌,以及无法写入任何东西。团队自己的工具需要 FormFlow 已经保存的提交时用的就是它,而不是电子表格里的一份副本。
把令牌收窄到刚好够用
令牌属于团队,不属于某个人。创建时要给它名字、至少一项能力——submissions:read、refusals:read 或 forms:read——可选一组表单,也可选一个到期日。FormFlow 只保存哈希,所以凭证只在创建时显示一次,此后不再显示。撤销立即生效;过期的令牌保留记录但释放名额。
其中两个设置承担了大部分工作。限定到具名表单的令牌只能到那些表单,别的一概不到。只带 forms:read 的令牌可以列出表单元数据,却读不到任何一条提交。这两者都不是偏好问题:越过令牌范围的读取,回应与读取一条不存在的记录完全一样,所以“不存在”和“无权访问”看起来相同。
五条读取
所有路由都在 /api/v1/manage 之下,用 Authorization 头里的持有者令牌认证。
GET /api/v1/manage/forms — 表单元数据。需要 forms:read。
GET /api/v1/manage/submissions — 一页提交。需要 submissions:read。
GET /api/v1/manage/submissions/{uuid} — 一条提交,包含列表省略的垃圾信号。
GET /api/v1/manage/refused-submissions — 表单拒收账本保留的拒收记录。需要 refusals:read。
GET /api/v1/manage/attempts/{attemptId} — 一次尝试在记录过它的各个账本中的有序生命线。
提交的请求体以字段名为键,按提交时的样子返回。大多数字段是字符串,多选是列表,文件字段是带存储名称的引用,网格是以行标签为键的对象。只有单条提交那条路由会返回垃圾信号。
用游标翻页
列表路由返回 data 和 meta。limit 取 1 到 200,默认 50,顺序是 created_at 再按 id,降序。meta.has_more 说明是否还有下一页;meta.next_cursor 是要作为 cursor 回传的不透明值。这里没有偏移量,因为集合在你阅读时仍在移动,偏移量会悄悄跳过或重复行。
cursor=""
while :; do
page=$(curl -sS "https://formflow.digital/api/v1/manage/submissions?limit=100&cursor=$cursor" \
-H "Authorization: Bearer $FORMFLOW_TOKEN")
echo "$page" | jq -r '.data[] | [.uuid, .form_id, .created_at] | @tsv'
cursor=$(echo "$page" | jq -r '.meta.next_cursor // empty')
[ "$(echo "$page" | jq -r '.meta.has_more')" = "true" ] || break
done筛选是刻意收窄的:form_id、since、until、is_spam、unread 和 q。q 是在盲索引上做精确词元搜索,不是子串搜索,而且只在启用了搜索索引时才作答。
从一条拒收追到它的尝试
被拒收的提交和提交是两种记录。拒收记录被保留,是为了让客户能据此处理;它从不投递,也永远不会变成一次计费提交。两者都带尝试 ID,而尝试路由汇总那一次尝试的线索——预检摩擦、拒收、接受、抢救、恢复、回执以及每一项投递义务——不含任何提交值,也不含填写者元数据。
尝试 ID 本身并不唯一,所以这条线索始终限定在一个表单内。当同一个 ID 出现在多个表单上时,回应是 409 并列出它们,你再用 form_uuid 重发请求来选择。
读取面的钝处
在它之上搭东西之前,还有三条边界值得知道。请求体在 FormFlow 内部解密并按存储原样返回,所以能读提交的令牌就是能读个人数据的令牌,它的存放值得和凭证本身一样小心。某条记录的请求体无法解密时,会带着 payload_error 为 payload_unavailable 返回,而这一页的其余部分照常到达。状态码很精确:令牌无效、过期或被撤销是 401;缺少能力或套餐不可用是 403;记录在令牌范围之外是 404;筛选或游标无效是 422;限流是 429,并带 Retry-After。
完整契约——每个参数、响应结构和错误——都在 /docs/api。而 v1.0.0 的变更记录条目 记录了这个 API 发布时的样子:一份有文档的管理 API,与那唯一接收提交的端点并列。