Task Keeper 3.112 API 使用详解

Task Keeper 3.112 API 使用详解:External REST API 全端点、appSecret 鉴权、curl 实测回显与错误码表。

Task KeeperTask Keeper任务管理自托管REST API2026/10/25

Task Keeper 3.112 API 使用详解

目标:讲清 Task Keeper(PiGa)v3.112 的 External REST API 体系——认证方式、全部对外端点、逐条 curl 实测回显与错误码。已在 Ubuntu 22.04 + Task Keeper v3.112 环境逐端点实测通过。

💡 说明:Task Keeper 没有独立的 CLI 工具集——bin/server 只是应用启动脚本(附带 -DdropAndCreateNewDb / -DcreateAdminUser 两个维护标志,已在《Ubuntu 22.04 安装 Task Keeper 3.112 指南》4.5 覆盖)。它的自动化能力由 /externalapi/ 一组 REST 端点承担,因此本篇按 D5(API 使用详解)展开。

一、环境信息

  • 应用版本:Task Keeper v3.112(Play Framework 2.8.20)
  • 服务端点:http://localhost:9000(裸机安装)或宿主机映射端口(Compose 部署)
  • 演示账号:admin / q1w2e3r4
  • 应用密钥(appSecret):application.conf 中 play.http.secret.key 的值,本文实测环境为 OQnYaBOXBc6lLC4IFudvdoG46XLm9f8I

二、前置条件

  1. Task Keeper 已安装并运行(安装见本专栏《Ubuntu 22.04 安装 Task Keeper 3.112 指南》或《Task Keeper 3.112 Docker Compose 部署指南》),健康检查通过:
curl -s http://localhost:9000/externalapi/healthcheck/

实测输出:

OK
  1. 拿到应用密钥。裸机形态:
grep -oP 'play.http.secret.key="\K[^"]+' /opt/task-keeper/conf/application.conf

实测输出:

OQnYaBOXBc6lLC4IFudvdoG46XLm9f8I

容器形态下即 compose 里 APPLICATION_SECRET 环境变量的值。

三、API 总览

Task Keeper 的 HTTP 接口分三组(依据:release 包内 conf/routes 路由表,查询日期 2026-10-25):

分组前缀鉴权方式用途
页面与会话/login/、/logout/、/loggedin/、/app/*表单登录 → 会话 Cookie浏览器访问与脚本登录
内部 API/scalajsapi/*、/websocket/hydropush/*会话 Cookie前端 SPA 与服务端通信(Scala.js 自动路由,二进制协议,不对外)
External API/externalapi/*URL 路径内携带 appSecret脚本/定时任务集成(本篇重点)

External API 端点一览:

方法与路径功能是否需 appSecret
GET /externalapi/healthcheck/健康检查否
GET /externalapi/dbschema/:appSecret/查看数据库建表 DDL是
GET /externalapi/inserttask/:docId/:parentTag/:appSecret/*content向指定文档插入任务是
GET /externalapi/gettasksasjson/:docId/:parentTag/:appSecret/导出指定标签子树为 JSON是
GET /externalapi/share/:docId/:loginName/:appSecret/把文档授权给指定用户是
GET /externalapi/processdelayedtasks/:appSecret/处理到期的延迟任务是
GET /externalapi/regenordertokens/:dryOrWet/:appSecret/重建任务排序令牌是
GET /externalapi/clearoldentitymodifications/:dryOrWet/:appSecret/清理历史实体修改日志是

通用约定:

  • 全部走 GET(含写操作),便于浏览器/书签/简单脚本直接触发;
  • 鉴权方式:把 play.http.secret.key 作为路径段 :appSecret 传入,密钥错误返回 500 错误页(无 401/403 细分);
  • 无分页/过滤/排序参数:任务导出固定返回「指定标签子树」全量;
  • 响应体为纯文本(OK 开头)或 JSON。

💡 技术拓展:自托管应用的 API 鉴权常见几种方式——

方式代表特点
共享密钥(路径/参数携带)Task Keeper最简单,适合内网脚本;密钥即根权限
Bearer Token / PATGitea、GitLab可按用户签发与吊销
OAuth2 / OIDCVikunja 等标准三方集成,配置最重

Task Keeper 采用共享密钥模式,密钥等同管理员权限,务必只在可信网络中调用,且不要写进会外泄的脚本仓库。

四、认证与首次调用

4.1 表单登录(脚本模拟浏览器)

登录接口为表单 POST,成功后返回 303 跳转并种下会话 Cookie:

curl -s -c /tmp/tk.txt -X POST http://localhost:9000/login/ \
  -d "loginName=admin&password=q1w2e3r4" -o /dev/null -w "%{http_code}\n"

实测输出:

303

携带 Cookie 校验登录态:

curl -s -b /tmp/tk.txt http://localhost:9000/loggedin/

实测输出:

true

密码错误时返回 400(实测):curl -s -X POST http://localhost:9000/login/ -d "loginName=admin&password=wrongpwd" -o /dev/null -w "%{http_code}\n" 输出 400。未登录访问 /app/ 返回 303 跳转到 /login/?returnTo=%2Fapp%2F。

4.2 健康检查(无需密钥)

curl -s http://localhost:9000/externalapi/healthcheck/

实测输出:

OK

该端点不校验密钥,适合作为负载均衡/监控探针。

五、各 API 域详解

5.1 数据库结构查询:dbschema

导出全部数据表的建表 DDL,用于了解数据模型或做集成开发:

SECRET=OQnYaBOXBc6lLC4IFudvdoG46XLm9f8I
curl -s "http://localhost:9000/externalapi/dbschema/$SECRET/"

实测输出:

OK

## USERS ##
create table `USERS` (`loginName` TEXT NOT NULL,`passwordHash` TEXT NOT NULL,`name` TEXT NOT NULL,`isAdmin` BOOLEAN NOT NULL,`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,`lastUpdateTime` BLOB NOT NULL)

## DOCUMENT_ENTITIES ##
create table `DOCUMENT_ENTITIES` (`name` TEXT NOT NULL,`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,`lastUpdateTime` BLOB NOT NULL)

## DOCUMENT_PERMISSION_AND_PLACEMENTS ##
create table `DOCUMENT_PERMISSION_AND_PLACEMENTS` (`documentId` BIGINT NOT NULL,`userId` BIGINT NOT NULL,`orderToken` BLOB NOT NULL,`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,`lastUpdateTime` BLOB NOT NULL)

## TASK_ENTITIES ##
create table `TASK_ENTITIES` (`documentId` BIGINT NOT NULL,`contentHtml` TEXT NOT NULL,`orderToken` BLOB NOT NULL,`indentation` INTEGER NOT NULL,`collapsed` BOOLEAN NOT NULL,`checked` BOOLEAN NOT NULL,`delayedUntil` TIMESTAMP NULL,`tagsString` TEXT NOT NULL,`lastContentModifierUserId` BIGINT NOT NULL,`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,`lastUpdateTime` BLOB NOT NULL)

## ENTITY_MODIFICATION_ENTITY ##
create table `ENTITY_MODIFICATION_ENTITY` (`userId` BIGINT NOT NULL,`entityId` BIGINT NOT NULL,`modification` BLOB NOT NULL,`date` TIMESTAMP NOT NULL,`instantNanos` BIGINT NOT NULL,`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY)

密钥错误时实测返回 500 错误页(Play 默认 HTML 错误页,日志记录异常 id)。

5.2 插入任务:inserttask

GET /externalapi/inserttask/:docId/:parentTag/:appSecret/*content?isMarkdown=true&extraIndent=0

参数说明(依据:conf/routes 注释与 ExternalApi.scala 源码,查询日期 2026-10-25):

  • docId:文档 ID。文档在 Web 界面创建后,从浏览器地址栏 URL(/app/#/document/<id>)获得;
  • parentTag:父任务标签名(URL 编码)。新任务会插入到「文档中第一个带该标签的任务」之下,作为其子任务;
  • content:任务内容,推荐用 base64:<base64编码> 形式(直接放原文需自行 URL 编码);
  • isMarkdown(可选):true 时按 Markdown 解析内容;
  • extraIndent(可选):在父任务基础上额外缩进的层级数,默认 0。

调用示例(向文档 12345 的 #inbox 标签下插入一条 Markdown 任务):

SECRET=OQnYaBOXBc6lLC4IFudvdoG46XLm9f8I
CONTENT=$(echo -n "Buy milk **today**" | base64 -w0)
curl -s "http://localhost:9000/externalapi/inserttask/12345/inbox/$SECRET/base64:$CONTENT?isMarkdown=true"

成功返回 OK。文档不存在或标签不存在时返回 500 错误页(实测:对不存在的 docId=999 调用返回 500,应用日志含 IllegalArgumentException: Could not find entry with id=999)。

5.3 导出任务:gettasksasjson

GET /externalapi/gettasksasjson/:docId/:parentTag/:appSecret/

导出指定文档中「第一个带 :parentTag 标签的任务及其全部子任务」为 JSON 数组,元素含 contentHtml、indentation、checked、tags 等字段(对应 TASK_ENTITIES 表结构,见 5.1)。docId 获取方式同 5.2。

调用示例:

curl -s "http://localhost:9000/externalapi/gettasksasjson/12345/inbox/$SECRET/"

成功时返回 OK + JSON 数组;文档不存在或该文档中没有带此标签的任务时返回 500(源码中 require(parentAndBelow.nonEmpty, s"Could not find $parentTag") 触发)。

5.4 文档授权:share

GET /externalapi/share/:docId/:loginName/:appSecret/

把文档 :docId 授权给用户 :loginName(该用户须已在「用户管理」中创建)。成功返回 OK。实测对不存在的文档调用返回 500。

5.5 延迟任务处理:processdelayedtasks

GET /externalapi/processdelayedtasks/:appSecret/

Task Keeper 的「延迟任务」机制:编辑器内 alt+shift+D 把任务排入延迟队列(需要文档中存在带 #delayed_tasks 与 #todo_unsorted 标签的任务作为队列位)。本端点把已到期的延迟任务回流到待办区,官方建议用 cron 每日调用一次:

SECRET=OQnYaBOXBc6lLC4IFudvdoG46XLm9f8I
curl -s "http://localhost:9000/externalapi/processdelayedtasks/$SECRET/"

实测输出(当前无到期任务):

OK

Moved 0 tasks (including children).

配合 cron 每日 04:00 调用(裸机形态):

echo '0 4 * * * root curl -s "http://localhost:9000/externalapi/processdelayedtasks/OQnYaBOXBc6lLC4IFudvdoG46XLm9f8I/" >/dev/null' > /etc/cron.d/task-keeper-delayed

5.6 排序令牌重建:regenordertokens

GET /externalapi/regenordertokens/:dryOrWet/:appSecret/

任务在文档中的顺序由 orderToken(BLOB)决定,长期大量插入/移动后排位精度可能耗尽,该端点重建全文档排序令牌。dryOrWet 取 dry(仅演练输出将做的事)或 wet(实际执行)。

实测(全新实例,dry):

curl -s "http://localhost:9000/externalapi/regenordertokens/dry/$SECRET/"

输出:

OK

全新实例无文档时无额外输出;有文档时会逐文档输出 Processing document <id>: <name> 及重排详情。日常维护建议先 dry 确认范围,再 wet 执行。

5.7 修改日志清理:clearoldentitymodifications

GET /externalapi/clearoldentitymodifications/:dryOrWet/:appSecret/

Task Keeper 把每次实体变更记录在 ENTITY_MODIFICATION_ENTITY 表(用于多端同步),长期运行会膨胀,该端点清理历史记录。同样支持 dry/wet。

实测 dry(查看将清理多少):

curl -s "http://localhost:9000/externalapi/clearoldentitymodifications/dry/$SECRET/"

输出:

OK

Total number of entity modifications: 0
Number of entity modifications to be removed: 0

实测 wet(实际清理):

OK

Total number of entity modifications: 0
Number of entity modifications to be removed: 0

Successfully removed 0 old modifications

六、错误码与排障约定

实测归纳:

场景HTTP 状态码说明
正常调用200响应体以 OK 开头
appSecret 错误500Play HTML 错误页,服务端日志记录异常 id
docId 不存在500日志含 Could not find entry with id=<id>
parentTag 不存在500日志含 Could not find <tag>
登录密码错误400无 Cookie 种下
未登录访问 /app/303跳转 /login/?returnTo=...

排障路径:先 curl -s http://localhost:9000/externalapi/healthcheck/ 确认服务存活;500 类错误查服务端日志——裸机形态 journalctl -u task-keeper -n 50 --no-pager,容器形态 docker compose logs web | tail -50,错误页中的异常 id(如 8f5l4m0cn)可在日志中精确定位堆栈。

七、常见问题

7.1 调用 inserttask 报 404/路由不存在

检查 URL 段顺序:inserttask/:docId/:parentTag/:appSecret/*content——appSecret 在 content 之前,位置容易写反。content 建议统一用 base64: 前缀形式,避免特殊字符破坏路由解析。

7.2 密钥里有 / 导致路由解析错乱

appSecret 是 URL 路径段,生成密钥时避免 /、+、?、# 等字符。本文的生成命令(tr -dc 'a-zA-Z0-9')只保留字母数字,天然规避此问题。

7.3 能远程调用 External API 吗

可以,只要网络可达 9000 端口。但密钥随 URL 明文传输,公网暴露时务必在反向代理层加 TLS(如 Nginx + Let's Encrypt),否则密钥会被中间人截获。

7.4 内部 API(/scalajsapi/)能直接调吗

不建议。该组接口是 Scala.js 前端的自动路由,请求体为 boopickle 二进制序列化格式,无稳定对外契约,版本升级可能变更。脚本集成一律使用 /externalapi/。

八、参考链接