myTinyTodo 1.8.6 API 使用详解
myTinyTodo 1.8.6 JSON API 全路由实测详解:MTT-Token 认证模型、session/login/logout 令牌流转、清单与任务的 action 分发语义、标签查询、ext-settings 扩展路由、CSV/iCal/RSS 导出订阅与 403/404 排障。
myTinyTodo 1.8.6 API 使用详解
目标:完整讲解 myTinyTodo 1.8.6 内置 JSON API(
api.php)的认证模型、全部路由、请求/响应格式与实测示例,并覆盖 CSV/iCalendar 导出与 RSS 订阅两个数据出口。myTinyTodo 没有官方 CLI 工具,API 是脚本化与第三方集成的唯一官方途径;本文所有请求与回显均在 Ubuntu 22.04.5 + nginx + PHP-FPM + myTinyTodo 1.8.6(SQLite 与 MariaDB 双后端)环境实测。
一、环境信息
| 项目 | 值 |
|---|---|
| 应用版本 | myTinyTodo v1.8.6(includes/version.php 实测 Version::VERSION='1.8.6') |
| 运行环境 | Ubuntu 22.04.5 LTS,nginx 1.18.0 + php8.1-fpm;Apache 2.4.52 + mod_php8.1 亦验证通过 |
| 数据库 | SQLite(db/todolist.db)为主,关键流程在 MariaDB 10.6.23 后端复测 |
| API 入口 | http://<服务器地址>/api.php?_path=<路由> |
| 测试口令 | 示例口令 q1w2e3r4(存储形式 sha256:13a5c202...,实测 `printf 'q1w2e3r4' |
| 测试数据 | 初始清单 Todo(id=1)与内置虚拟清单 All tasks(id=-1) |
二、前置条件
- 已完成安装并设置访问口令(见 《Ubuntu 22.04 安装 myTinyTodo 1.8.6 指南》 第四章);
- 明确 API 的三种鉴权级别(源码
api.php实证):- 会话令牌级(读写):登录用户的读写操作,请求头
MTT-Token必须等于当前会话令牌; - 匿名读级:未登录时,仅对「已发布」清单的只读查询放行(如
GET /tasks?list=N,代码检查published=1); - 无鉴权:
POST /session与POST /login本身(它们也是创建令牌的入口)。
- 会话令牌级(读写):登录用户的读写操作,请求头
- 请求约定:请求体
Content-Type: application/json;每个需要鉴权的请求都必须携带MTT-Token请求头(实测缺头即403 Access denied! No token provided.)。
三、API 总览(全部路由)
api.php 的路由注册表(源码 api.php $endpoints 数组,与实测一一对应):
路由(_path=) | 方法 | 用途 |
|---|---|---|
/session | POST | 创建会话,取得初始令牌 |
/login | POST | 口令登录,换发正式令牌 |
/logout | POST | 登出并作废令牌 |
/lists | GET / POST / PUT | 清单查询 / 新建 / 批量操作 |
/lists/{id} | GET / PUT / DELETE(POST 兼容 PUT) | 单清单查询 / 操作(带 action)/ 删除 |
/tasks | GET / POST / PUT | 任务查询 / 新建 / 排序操作 |
/tasks/{id} | PUT / DELETE(POST 兼容 PUT) | 单任务操作(带 action)/ 删除 |
/tasks/parseTitle | POST | 预解析智能语法(不落库) |
/tasks/newCounter | POST | 未读新任务计数 |
/suggestTags | GET | 标签自动补全 |
/tagCloud/{listId} | GET | 标签云(含计数与字重) |
/ext-settings/{bundleId} | GET / PUT / POST | 扩展设置读写(仅已启用扩展) |
通用响应形态:任务/清单类返回 {"total": N, "list": [...], "time": <unix时间戳>};操作类返回 {"total": 0|1};错误返回裸文本(403/404)或 {"msg": "..."}。
四、认证与令牌管理
4.1 三步获得可用令牌(实测)
第一步,创建会话(无需鉴权):
===== POST /session -> 200
{
"token": "ecd868a1-72d3-4f93-9b14-0e3dd7474bfc",
"session": "8jkgbqrqk9epnec33lj26fugn3"
}
返回的 token 用于携带 MTT-Token 调用 login(否则 login 一步就会 403);session 是 PHP 会话 ID,后续请求需带着会话 Cookie(mtt-session)才能维持同一会话。
第二步,口令登录(携带第一步的 token):
===== POST /login -> 200
{
"logged": 1,
"token": "324a5fc9-34be-4176-aa49-97bd45df17cf"
}
登录成功后必须改用响应中的新 token——服务器端已轮换会话令牌(源码 update_token()),旧令牌立即失效。口令错误时返回 {"logged": 0}(HTTP 仍为 200,实测回显)。
第三步,用新 token 调用业务接口(请求头 MTT-Token: <token> + 会话 Cookie)。
4.2 令牌机制要点(源码实证)
- 设有口令的应用:令牌存于服务端会话(
$_SESSION['token']),MTT-Token请求头与其逐字符比对,任何不匹配即 403; - 未设口令的应用:令牌存于
mtt-tokenCookie,MTT-Token头与之比对; POST /logout返回{"logged":0}并作废当前令牌(实测回显);- 实测坑位:测试脚本中最常见的 403 原因就是「换了会话仍用旧令牌」或「某次请求漏带 tok 头」——403 的
Access denied! No token provided.永远意味着头与当前会话不匹配,请逐请求核对。
五、清单管理 API
5.1 查询清单
===== GET /lists -> 200
{
"total": 2,
"list": [
{
"id": -1,
"name": "All tasks",
"sort": 3,
"published": 0,
"showCompl": 1,
"showNotes": 0,
"hidden": 1,
"feedKey": ""
},
{
"id": 1,
"name": "Todo",
"sort": 0,
"published": 0,
"showCompl": 1,
"showNotes": 0,
"hidden": 0,
"feedKey": ""
}
],
"time": 1792803861
}
字段含义:id=-1 是内置虚拟清单「All tasks」(跨清单视图,hidden=1,不可删除);sort 为排序方式编号;published 决定 RSS 是否可访问;showCompl/showNotes 是清单显示开关;feedKey 是发布后生成的 RSS 访问键。
5.2 新建清单
===== POST /lists(body: {"name": "收件箱"}) -> 200
{
"total": 1,
"list": [
{
"id": 4,
"name": "收件箱",
"sort": 0,
"published": 0,
"showCompl": 1,
"showNotes": 0,
"hidden": 0,
"feedKey": ""
}
]
}
5.3 操作单清单:必须带 action(重要实测发现)
PUT /lists/{id} 的请求体必须包含 action 字段,服务器按 action 分发(源码 ListsController::putId() 的 switch:rename / sort / publish / enableFeedKey / showNotes / hide / clearCompleted / delete)。实测验证:不带 action 时返回 {"total":0} 且什么都不改——
===== PUT /lists/2(body: {"name": "工作任务", "sort": 1},无 action) -> 200
{
"total": 0
}
(随后 GET /lists 确认清单 2 仍叫「工作」——静默无操作)
正确写法与实测回显:
===== PUT /lists/2(body: {"action": "rename", "name": "工作任务"}) -> 200
{
"total": 1,
"list": [
{
"id": 2,
"name": "工作任务",
"sort": 0,
"published": 0,
"showCompl": 1,
"showNotes": 0,
"hidden": 0,
"feedKey": ""
}
]
}
===== PUT /lists/2(body: {"action": "sort", "sort": 1}) -> 200
{
"total": 1
}
发布清单(RSS 前置条件)同样是 action:
===== PUT /lists/1(body: {"action": "publish", "publish": 1}) -> 200
{
"total": 1
}
5.4 删除清单
===== DELETE /lists/3 -> 200
{
"total": 1
}
六、任务管理 API
6.1 创建任务:智能语法一投递
智能语法开启(设置 smartsyntax=1)时,title 字段可直接写 ±priority 标题 #标签 @截止日 一行式:
===== POST /tasks(body: {"list": 1, "title": "+2 整理季度周报 #work @2026-10-28"}) -> 200(节选)
{
"total": 1,
"list": [
{
"id": 3,
"title": "整理季度周报",
"titleText": "整理季度周报",
"listId": 1,
"compl": 0,
"prio": 1,
"note": "",
"tags": "work",
"tags_ids": "2",
"duedate": "10/26/2026",
"dueClass": "soon",
"dueStr": "in 2 days",
"dueInt": 20261026,
"dueTitle": "Due 26 Oct 2026"
}
]
}
实测解读:标题剥掉了语法前缀(剩余「整理季度周报」),prio、tags、duedate 三项全部结构化落库;dueClass/dueStr/dueTitle 是服务端计算的到期提示。
6.2 创建任务:newFull 结构化投递
===== POST /tasks(body: {"newFull": {...}}) -> 200(节选)
{
"total": 1,
"list": [
{
"id": 4,
"title": "部署验证 myTinyTodo",
"listId": 1,
"compl": 0,
"prio": 2,
"note": "<p><strong>要点</strong>:检查 PHP 扩展</p>",
"noteText": "**要点**:检查 PHP 扩展",
"tags": "ops,urgent",
"duedate": "10/25/2026",
"dueClass": "today",
"dueStr": "tomorrow"
}
]
}
注意 note 存的是渲染后的 HTML、noteText 是 Markdown 原文——两者都随任务返回。
6.3 预解析智能语法(不落库)
===== POST /tasks/parseTitle(body: {"title": "+2 买牛奶 #home @2026-10-25"}) -> 200
{
"title": "买牛奶",
"prio": 2,
"tags": "home",
"duedate": "10/25/2026"
}
6.4 查询任务
| 调用 | 实测结果 |
|---|---|
GET /tasks?list=1 | 返回清单 1 未完成任务 |
GET /tasks?list=1&compl= | compl 留空 = 含已完成任务(实测 total: 4) |
GET /tasks?list=1&s=周报(URL 编码 s=%E5%91%A8%E6%8A%A5) | 关键词搜索,实测命中 1 条 |
GET /tasks?list=1&t=ops | 按标签过滤,实测命中带 ops 标签的任务 |
GET /tasks?list=-1 | 跨全部清单(响应带 listName 字段标注归属,实测 total: 4) |
6.5 操作单任务:同样必须带 action
PUT /tasks/{id} 的 action 全集(源码 TasksController::putId()):complete / priority / note / edit / move / order 等。实测逐个验证:
===== PUT /tasks/4(body: {"action": "complete", "compl": 1}) -> 200(节选)
{
"total": 1,
"list": [
{ "id": 4, "compl": 1, "dateCompleted": "24 Oct 2026",
"dateCompletedFull": "24 Oct 2026 09:04" }
]
}
===== PUT /tasks/3(body: {"action": "priority", "prio": -1}) -> 200
{
"total": 1,
"list": [ { "id": 3, "prio": -1 } ]
}
===== PUT /tasks/3(body: {"action": "note", "note": "周五下班前提交"}) -> 200
{
"total": 1,
"list": [
{ "id": 3,
"note": "<p>周五下班前提交</p>",
"noteText": "周五下班前提交" }
]
}
===== PUT /tasks/3(body: {"action": "edit", "title": "整理季度周报(改)",
"note": "含三张图", "prio": 1, "duedate": "2026-10-30", "tags": "work,report"}) -> 200
{
"total": 1,
"list": [ { "id": 3, "title": "整理季度周报(改)", "listName": "Todo", ... } ]
}
===== PUT /tasks(body: {"action": "order", "order": [{"id": 3, "diff": 10}]}) -> 200
{
"total": 1
}
===== PUT /tasks/4(body: {"action": "move", "to": 4}) -> 200
{
"total": 1
}
说明:edit 是一次提交全字段的整单编辑(等价于界面的编辑表单);order 走 PUT /tasks(集合路由)按 diff 偏移手动排序;move 的 to 是目标清单 id。
6.6 删除任务
===== DELETE /tasks/{id} -> 200
{
"total": 1
}
6.7 未读计数
===== POST /tasks/newCounter(body: {"lists": [{"listId": 1, "later": 0}]}) -> 200
{
"ok": true,
"total": 1,
"tasks": [],
"lists": [ { "listId": 1, "counter": 3 } ],
"time": 179280...
}
later 传上次查询的时间戳即可统计增量新任务(界面角标的数据源)。
七、标签查询 API
===== GET /suggestTags?q=w&list=1 -> 200
[
"work"
]
===== GET /tagCloud/1 -> 200
{
"total": 3,
"items": [
{ "tag": "home", "tagText": "home", "id": 1, "count": 2, "w": 2 },
{ "tag": "report", "tagText": "report", "id": 5, "count": 1, "w": 0 },
{ "tag": "work", "tagText": "work", "id": 2, "count": 1, "w": 0 }
]
}
suggestTags 服务输入框自动补全(q 前缀匹配);tagCloud 返回标签云,count 是任务数、w 是字重等级。
八、扩展设置 API
路由 /ext-settings/{bundleId} 只为已启用的扩展注册。实测两步验证:
未启用扩展时(应用默认 extensions: []):
===== GET /ext-settings/CustomCSS -> 404
{"msg":"Unknown extension"}
扩展生效的前提(实测链路):应用自带的扩展打包在 ext/extensions.tar.gz 里,需先解包出目录——
tar -C /var/www/mytinytodo-app/ext -xzf /var/www/mytinytodo-app/ext/extensions.tar.gz
ls /var/www/mytinytodo-app/ext/
CustomCSS
backup
extensions.tar.gz
index.html
notifications
updater
随后在 Web 界面 settings.php 扩展区启用(写入 config.json 的 extensions 数组),API 路由即出现:
===== GET /ext-settings/CustomCSS -> 200
<h3 class="page-title">... Custom CSS </h3>
<form id='ext_settings_form' data-ext=...
===== PUT /ext-settings/CustomCSS(body: {"css": "body{background:#fafafa}"}) -> 200
{"saved":1,"msg":"Saved"}
内置扩展共 4 个(extension.json 实测):backup(备份/恢复 v1.2.1)、CustomCSS(自定义 CSS v1.0.1)、notifications(任务通知,内置 Telegram 通道)、updater(在线检查更新)。
九、数据导出与订阅
这两个出口不在 api.php 路由内,是独立端点,走浏览器会话 Cookie 鉴权:
9.1 导出 CSV
===== GET /export.php?list=1 -> 200 Content-Type: text/csv; charset=utf-8
Completed;Priority;Task;Notes;Tags;Due;DateCreated;DateCompleted
0;1;"采购牛奶";"";"home";2026-10-26;2026-10-24 08:55:29 +0800;
0;1;"整理季度周报(改)";"含三张图";"work,report";2026-10-30;2026-10-24 09:04:21 +0800;
9.2 导出 iCalendar
===== GET /export.php?list=1&format=ical -> 200 Content-Type: text/calendar; charset=utf-8
BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
CALSCALE:GREGORIAN
PRODID:-//myTinyTodo//iCalendar Export v1.4//EN
X-WR-CALNAME:Todo
X-MTT-TIMEZONE:Asia/Shanghai
BEGIN:VTODO
UID:00a5230b-bc5f-4c25-a8c8-8eaec50f7cef
CREATED:20261024T005529Z
DTSTAMP:20261024T005529Z
SUMMARY:采购牛奶
DUE;VALUE=DATE:20261026
PRIORITY:5
X-MTT-PRIORITY:1
DESCRIPTION:Tags: home
...
可直接把该 URL 订阅进支持 iCal 的日历客户端。注意时区行 X-MTT-TIMEZONE 跟随应用设置。
9.3 RSS 订阅(发布前置)
未发布清单访问 RSS 的实测回显(HTTP 200,内容为错误页文本):
Access denied!<br> List is not published.
按 5.3 节先 PUT /lists/1 {"action":"publish","publish":1}(或界面清单右键勾选「发布」)后:
===== GET /feed.php?list=1 -> 200 Content-Type: text/xml; charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Todo - New tasks</title>
<link>http://<服务器地址>/?list=1</link>
<description>New tasks</description>
<lastBuildDate>Sat, 24 ...
RSS 输出的是清单的「新任务流」,需要长期订阅时把 feed.php?list=1(或带 feedKey 的地址)交给 RSS 阅读器。
十、常见问题(FAQ)
Q1:请求返回 403 Access denied! No token provided.?
三选一原因:请求根本没带 MTT-Token 头;带了但与当前会话令牌不匹配(最常见:login 后没换用响应里的新 token,或换了会话 Cookie 仍用旧 token);GET 只读请求访问了未发布清单且未登录(匿名只读仅限已发布清单)。实测缺头/错 token 都是同一句 403 裸文本。
Q2:PUT /lists/{id} 或 PUT /tasks/{id} 返回 {"total":0} 但数据没变?
请求体少了 action 字段——路由按 action 分发,没有 action 就静默无操作(5.3 节实测:连 total 都是 200 包着的假成功)。清单 action:rename/sort/publish/enableFeedKey/showNotes/hide/clearCompleted/delete;任务 action:complete/priority/note/edit/move 等。
Q3:_path 里带查询参数得到 404?
路由只认 _path 的路径部分,查询参数必须拆到 URL 上。实测对比:api.php?_path=tasks?list=1 → 404 Unknown endpoint;正确写法 api.php?_path=tasks&list=1 → 200。
Q4:/ext-settings/xxx 返回 {"msg":"Unknown extension"}?
该扩展未启用或未解包。先 tar -C <应用目录>/ext -xzf <应用目录>/ext/extensions.tar.gz,再在设置页启用(写入 extensions 数组),路由才会注册(8 节实测全链路)。
Q5:中文标题/备注经 API 读写正常吗?
正常。实测创建 整理季度周报、部署验证 myTinyTodo、备注 要点:检查 PHP 扩展 等均完整往返;唯一注意点是 GET 查询参数里的中文要 URL 编码(s=周报 → s=%E5%91%A8%E6%8A%A5),且响应里备注是双轨的(note 为 HTML、noteText 为 Markdown 原文)。
Q6:想在第三方脚本里长期轮询任务,怎么设计?
用「session → login → 保存 token+Cookie」的会话对轮询所有请求复用;不要每请求都重新 login(服务器会反复轮换令牌)。退出时调一次 POST /logout 作废令牌。
十一、参考链接
- 官方仓库(api.php 路由表即权威文档):https://github.com/maxpozdeev/mytinytodo
- 官方网站:https://www.mytinytodo.net/
- 认知篇(架构与选型背景):《myTinyTodo 1.8.6 介绍与选型指南》
- 安装篇(API 的运行前提):《Ubuntu 22.04 安装 myTinyTodo 1.8.6 指南》
- 配置篇(口令哈希与 ext-settings 的联动参数):《myTinyTodo 1.8.6 配置参数详解》