Gotify 3.1.1 API 使用详解

Gotify 3.1.1 CLI 与 REST API/WebSocket 全域实测:三子命令、22 条端点总览、令牌三形态与会话提权闭环、令牌轮换陷阱、消息推送分页删除、WebSocket 实时推送验证、优先级语义与 FAQ。

Gotify即时通信Gotify2026/10/21

Gotify 3.1.1 API 使用详解

目标:把 Gotify 3.1.1 的两个程序化入口讲全——CLI 三子命令与 REST API/WebSocket 全域端点,认证三形态、令牌轮换、会话提权、消息分页与优先级语义均以逐条 curl 实测闭环呈现。本文全部命令与回显在 Ubuntu 22.04(x86_64)实测环境运行,服务版本 /version 实测为 3.1.1,实测端口 8144(8128-8152 槽位约定;标准默认端口 80)。安装与配置基础见 《Ubuntu 22.04 安装 Gotify 3.1.1 指南》 与 《Gotify 3.1.1 配置参数详解》,组件定位见 《Gotify 3.1.1 介绍与选型指南》。

一、环境信息

  • 操作系统:Ubuntu 22.04.4 LTS(Jammy Jellyfish),x86_64;
  • 服务版本:Gotify 3.1.1(2026-09-15 发布,安装篇所装,与本文实验同版本);
  • 运行形态:官方二进制 /opt/gotify/gotify serve + systemd(服务名 gotify);
  • 实测地址:http://127.0.0.1:8144;
  • 初始管理员:admin(Basic Auth 口令为安装时设定值);
  • 实验账号链:会话登录 → 首个 client(ops-cli)→ 二级 client(android-phone / low-priv-cli)→ 应用令牌(demo-app);
  • API 定义来源:运行实例自带的 Swagger 页面 http://127.0.0.1:8144/swagger(OpenAPI 2.0 规范,22 条路径),官方在线文档 gotify.net/docs/rest-api 与 gotify.net/api-docs;
  • 本文令牌均为实验环境即抛值,读者在自己实例上应使用自建令牌。

二、CLI 面:三个子命令

Gotify 3.0.0 起 CLI 随二进制发行,gotify --help 实测(无参数时打印总帮助):

/opt/gotify/gotify --help

实测回显:

Usage: gotify [flags] <command> [arguments]

Commands:
  serve                       Start the Gotify server.
  migrate-config <file.yml>   Convert an old YAML config file to the new env
                              format and print it to stdout.
  version                     Show version information

2.1 gotify version

打印版本、构建信息与 Go 模块依赖清单(安装篇 4.4 节有完整节选),快速核验二进制版本:

/opt/gotify/gotify version | head -5

实测回显:

Version: 3.1.1
Commit: e6361caa512547052b1013dbf4544e6d7ff32604
Build Date: 2026-09-15-18:26:53
Go Build Info:
go      go1.26.0

2.2 gotify serve 与 --help 陷阱

serve 启动服务进程(systemd 单元的 ExecStart 即 gotify serve)。注意 serve 不解析 --help,加上 --help 会直接把服务拉起来:

/opt/gotify/gotify serve --help

实测回显(root 会话前台执行,默认绑 80 已被占用):

2026-10-21T19:47:09+08:00 INF Gotify build_date=2026-09-15-18:26:53 version=3.1.1
2026-10-21T19:47:09+08:00 ERR Server error error="listen tcp :80: bind: address already in use"

排障时查命令面用 gotify --help(无子命令的总帮助),不要给 serve 传 --help;改监听端口靠 GOTIFY_SERVER_PORT 环境变量而非命令行参数(配置篇 5.1 节)。

2.3 gotify migrate-config

2.x → 3.x 配置迁移子命令:读取旧 config.yml,把等价环境变量打印到 stdout(19 行 GOTIFY_* 全量回显见安装篇 8.5 节),不修改旧文件。语法:

/opt/gotify/gotify migrate-config /etc/gotify/config.yml

CLI 面小结:3.1.1 的 CLI 只有三个子命令,运维动作(改配置、轮换令牌、管理用户)全部走环境变量 + REST API,这也是 3.x「CLI 薄、API 厚」的形态。

三、REST API 总览

3.1 端点全景

以下 22 条路径取自 3.1.1 实例自带的 OpenAPI 2.0 规范(/swagger 页内嵌 spec),按域分组:

域方法与路径语义
系统GET /health健康检查(无认证)
系统GET /version版本信息(无认证)
认证POST /auth/local/login本地登录换会话
认证POST /logout注销当前会话
用户GET /current/user当前用户信息(含服务开关状态)
用户PUT /current/user/password改自己口令
用户GET/POST /user · GET/PUT/DELETE /user/{id}用户管理(需提权会话或 Basic Auth)
客户端GET/POST /client · PUT/DELETE /client/{id}接收端客户端管理
客户端POST /client/{id}/elevate会话提权(限时)
应用GET/POST /application · PUT/DELETE /application/{id}发送端应用管理
应用GET /application/{id}/image · PUT /application/{id}/image应用图标
应用GET /application/{id}/message该应用的消息列表
应用PUT /application/{id}/security令牌轮换
消息POST /message推送消息(应用令牌)
消息GET /message · DELETE /message/{id}查询/删除消息(客户端令牌)
流GET /stream(WebSocket 升级)实时消息流
插件GET /plugin · GET /plugin/{id}/config插件列表/配置
插件POST /plugin/{id}/disable · /enable · /display插件开关与展示

3.2 认证方式定义

spec 的 securityDefinitions 共 7 种,归为三类:

类别方式说明
应用令牌Authorization: Bearer Axxx / X-Gotify-Key: Axxx / ?token=Axxx用于推送(POST /message)与应用管理
客户端令牌Authorization: Bearer Cxxx / X-Gotify-Key: Cxxx / ?token=Cxxx用于查询/删除消息、WebSocket 流、客户端与用户管理
用户凭据Basic Auth(用户名:口令)管理操作与会话登录;部分敏感端点仅认 Basic Auth 或提权会话

两类令牌前缀不同(3.x 新增):应用令牌 gtfya. 开头,客户端令牌 gtfyc. 开头——从令牌字面即可分辨类型,误用会在域检查层被拒(3.4 节实测)。

3.3 可视化调试入口

浏览器打开 http://<服务器>:8144/swagger/(注意尾斜杠)进入 Swagger UI,右上角 Authorize 填入令牌后可在页面内直接试调全部端点;本文所有 curl 实验的预期行为均与该页 spec 一致。

四、认证机制实测

4.1 令牌三形态对照

用同一个客户端令牌(gtfyc. 前缀)依次以三种方式调用 GET /message:

CT='gtfyc.xxxx'
curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $CT" http://127.0.0.1:8144/message
curl -s -o /dev/null -w '%{http_code}\n' -H "X-Gotify-Key: $CT" http://127.0.0.1:8144/message
curl -s -o /dev/null -w '%{http_code}\n' "http://127.0.0.1:8144/message?token=$CT"
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8144/message

实测回显:

200
200
200
401

三种形态等价,无凭据 401。?token= 形态最方便但会进访问日志与浏览器历史,生产优先 Bearer 头。

4.2 会话登录:POST /auth/local/login

3.x 的登录端点需要 Basic Auth 承载用户名口令 + 表单字段 name 指定会话名(创建一个会话客户端);用 JSON body 直接提交会被拒:

# 错误姿势(JSON body):401
curl -s -w '\nHTTP:%%{http_code}\n' -X POST -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"xxxx"}' http://127.0.0.1:8144/auth/local/login

实测回显:

{"error":"Unauthorized","errorCode":401,"errorDescription":"basic auth required"}
HTTP:401

正确姿势(-u Basic + formData):

curl -s -i -u admin:xxxx -d 'name=ops-cli' -X POST http://127.0.0.1:8144/auth/local/login | grep -iE '^(HTTP|Set-Cookie)'

实测回显:

HTTP/1.1 200 OK
Content-Type: application/json
Set-Cookie: gotify-client-token=gtfyc.xxxx; Path=/; Max-Age=604800; HttpOnly; SameSite=Strict

登录即创建了一个名为 ops-cli 的会话客户端,令牌同时通过 Set-Cookie 下发(默认有效期 604800 秒 = 7 天;响应体中也含同值 token)。配置篇 5.5 节已实测该 Cookie 默认无 Secure 标志,HTTPS 部署时打开 GOTIFY_SERVER_SECURECOOKIE=true。

注销会话用 POST /logout(携带会话凭据调用后,同凭据即失效):

curl -s -o /dev/null -w '%{http_code}\n' -X POST -H "Authorization: Bearer $CT" http://127.0.0.1:8144/logout
curl -s -w '\nHTTP %{http_code}\n' -H "Authorization: Bearer $CT" http://127.0.0.1:8144/current/user

实测回显:

200
{"error":"Unauthorized","errorCode":401,"errorDescription":"you need to provide a valid access token or user credentials to access this api"}
HTTP 401

4.3 令牌域隔离

应用令牌只能推消息、管理自身,客户端令牌才能查/删消息与流订阅;跨域调用被拒:

AT='gtfya.xxxx'
curl -s -H "Authorization: Bearer $AT" http://127.0.0.1:8144/client

实测回显:

{"error":"Unauthorized","errorCode":401,"errorDescription":"you need to provide a valid access token or user credentials to access this api"}

五、系统与元信息端点

5.1 GET /health(无认证)

curl -s http://127.0.0.1:8144/health; echo

实测回显:

{"health":"green","database":"green"}

两个分量分别表示服务与数据库健康态,监控探针直接用它。

5.2 GET /version(无认证)

curl -s http://127.0.0.1:8144/version; echo

实测回显:

{"version":"3.1.1","commit":"e6361caa512547052b1013dbf4544e6d7ff32604","buildDate":"2026-09-15-18:26:53"}

5.3 GET /current/user(需认证,返回服务开关)

curl -s -u admin:xxxx http://127.0.0.1:8144/current/user

实测回显:

{"id":1,"name":"admin","admin":true,"createdAt":"2026-10-21T19:54:33.694487038+08:00"}

用客户端令牌调用时,返回体在用户字段之外还带服务级开关状态(GotifyInfo 结构):

{"version":"3.1.1","register":false,"localAuth":true,"oidc":false,"oidcIdpName":"OIDC","oidcAutoRedirect":false}

register/localAuth/oidc 即配置篇 5.6 节讨论的三个开关的运行时视图,前端据此决定登录页形态。

六、client 域:接收端管理与会话提权

6.1 创建与列表

会话登录后创建第二个客户端(模拟手机端),并列出全部客户端:

curl -s -X POST -H "Authorization: Bearer $CT" -H 'Content-Type: application/json' \
  -d '{"name":"android-phone"}' http://127.0.0.1:8144/client

实测回显:

{"id":2,"token":"gtfyc.9GSxjM5oFECLTGI8L-YRRxgcV4LRdIGYd-feBdaGFuw","name":"android-phone","createdAt":"2026-10-21T19:58:49.205016311+08:00","lastUsed":null,"expiresAfterInactivitySeconds":0}
curl -s -H "Authorization: Bearer $CT" http://127.0.0.1:8144/client

实测回显:

[{"id":1,"name":"ops-cli","createdAt":"2026-10-21T19:58:32.865008051+08:00","lastUsed":"2026-10-21T19:58:32.878656804+08:00","elevatedUntil":"2026-10-21T20:58:32.864485526+08:00","expiresAfterInactivitySeconds":604800,"expiresAt":"2026-10-28T19:58:32.878656804+08:00"},{"id":2,"name":"android-phone","createdAt":"2026-10-21T19:58:49.205016311+08:00","lastUsed":null,"expiresAfterInactivitySeconds":0}]

⚠️ 注意:POST /client 与 POST /application 的响应是令牌唯一一次明文出现的时刻——3.x 令牌入库即哈希,列表接口不再返回 token 字段(上表 id=1 的条目即无 token)。令牌务必落密钥管理,丢了只能轮换重建。

6.2 会话提权闭环(/client/{id}/elevate)

创建一个低权限客户端 low-priv-cli(id=3),试图用它创建用户:

curl -s -X POST -H "Authorization: Bearer $CT3" -H 'Content-Type: application/json' \
  -d '{"name":"low-priv-cli"}' http://127.0.0.1:8144/client
curl -s -w '\nHTTP:%%{http_code}\n' -X POST -H "Authorization: Bearer $CT3" -H 'Content-Type: application/json' \
  -d '{"name":"dev1","pass":"dev1pass"}' http://127.0.0.1:8144/user

实测回显(第二步):

{"error":"Forbidden","errorCode":403,"errorDescription":"session not elevated, use basic auth or call /client:elevate"}
HTTP:403

敏感操作(用户管理)要求 Basic Auth 或限时提权会话。给 low-priv-cli 提权,先故意漏掉 durationSeconds:

curl -s -w '\nHTTP:%%{http_code}\n' -X POST -H "Authorization: Bearer $CT3" \
  -H 'Content-Type: application/json' -d '{"password":"xxxx"}' http://127.0.0.1:8144/client/3/elevate

实测回显:

{"error":"Bad Request","errorCode":400,"errorDescription":"Field 'durationSeconds' is required"}
HTTP:400

补全后重试(password 为 admin 口令,durationSeconds 单位秒):

curl -s -w '\nHTTP:%%{http_code}\n' -X POST -H "Authorization: Bearer $CT3" \
  -H 'Content-Type: application/json' \
  -d '{"password":"xxxx","durationSeconds":3600}' http://127.0.0.1:8144/client/3/elevate

实测回显:


HTTP:204

204 无响应体即提权成功。此时 GET /client/3 的回显中 elevatedUntil 已推进一小时(格式化展示):

{
  "id": 3,
  "name": "low-priv-cli",
  "createdAt": "2026-10-21T20:00:24.609440591+08:00",
  "lastUsed": "2026-10-21T20:00:24.650808914+08:00",
  "elevatedUntil": "2026-10-21T21:01:09.296728896+08:00",
  "expiresAfterInactivitySeconds": 0
}

提权后的会话即可执行用户管理(第八章实测)。默认提权窗口最长 1 小时,窗口内重复提权可续期;这是 3.x 针对会话劫持面新增的纵深防御。

七、application 域:发送端管理与令牌轮换

7.1 创建与更新

创建发送端应用 demo-app:

curl -s -X POST -H "Authorization: Bearer $CT" -H 'Content-Type: application/json' \
  -d '{"id":0,"name":"demo-app","description":"演示应用"}' http://127.0.0.1:8144/application

实测回显:

{"id":1,"token":"gtfya.Ew-NsxQaf1KWMPyIehKFXKBGqBgZRldwSEv0BClTGKs","name":"demo-app","description":"演示应用","internal":false,"image":"static/defaultapp.png","defaultPriority":0,"createdAt":"2026-10-21T19:58:49.223754952+08:00","lastUsed":null,"sortKey":"a0"}

更新名称与默认优先级:

curl -s -X PUT -H "Authorization: Bearer $CT" -H 'Content-Type: application/json' \
  -d '{"name":"demo-app","description":"演示应用(已更新)","defaultPriority":4}' \
  http://127.0.0.1:8144/application/1

实测回显(关注 token 字段):

{"id":1,"token":"gtfya.cKm0_b-kaM83PTZPad6QX8RumExhM3wh03iawQ0De7Y","name":"demo-app","description":"演示应用(已更新)","internal":false,"image":"static/defaultapp.png","defaultPriority":4,"createdAt":"2026-10-21T19:58:49.223754952+08:00","lastUsed":"2026-10-21T19:59:16.417547789+08:00","sortKey":"a0"}

⚠️ 注意(重要陷阱):PUT /application/{id} 响应里的 token 是脱敏回显值,不是新令牌,也不能用。实测用旧 token 推送仍 200、用响应里的新值推送 401——该字段只是把库内哈希映射出的占位串回显出来,真实令牌并未轮换。要轮换必须走下一节的 security 端点。

7.2 令牌轮换闭环(PUT /application/{id}/security)

curl -s -X PUT -H "Authorization: Bearer $CT" -H 'Content-Type: application/json' \
  -d '{"regenerateToken":true}' http://127.0.0.1:8144/application/1/security

实测回显:

{"regenerateToken":{"token":"gtfya.RY17B0UMyS0INH5_HdP32Xd6aMx3wQK57w60kYPCrY4"}}

这就是真轮换:旧令牌立即失效、新令牌此处明文返回一次。验证闭环——旧 token 推送 401、新 token 推送 200:

curl -s -o /dev/null -w '%{http_code}\n' -X POST -H "Authorization: Bearer gtfya.Ew-Nsx(旧)" \
  -H 'Content-Type: application/json' -d '{"title":"旧token","message":"应401"}' http://127.0.0.1:8144/message
curl -s -H "Authorization: Bearer gtfya.RY17B0U(新)" -H 'Content-Type: application/json' \
  -d '{"title":"轮换后推送","message":"regenerateToken 生效","priority":2}' http://127.0.0.1:8144/message

实测回显:

401
{"id":7,"appid":1,"message":"regenerateToken 生效","title":"轮换后推送","priority":2,"date":"2026-10-21T20:02:19.400400215+08:00"}

客户端令牌同理,PUT /client/{id}/security({"regenerateToken":true})完成轮换。

八、message 域:推送、分页与删除

8.1 推送(POST /message,应用令牌)

最小推送与富格式推送(extras 控制 WebUI 渲染与点击跳转):

AT='gtfya.xxxx'
curl -s -H "Authorization: Bearer $AT" -H 'Content-Type: application/json' \
  -d '{"title":"部署完成","message":"Gotify 3.1.1 已在 8144 端口就绪","priority":5}' \
  http://127.0.0.1:8144/message
curl -s -H "Authorization: Bearer $AT" -H 'Content-Type: application/json' \
  -d '{"title":"磁盘告警","message":"/var 分区使用率 92%","priority":8,
       "extras":{"client::display":{"contentType":"text/markdown"},
                 "client::notification":{"click":{"url":"https://example.com/monitor/disk"}}}}' \
  http://127.0.0.1:8144/message

实测回显:

{"id":1,"appid":1,"message":"Gotify 3.1.1 已在 8144 端口就绪","title":"部署完成","priority":5,"date":"2026-10-21T19:59:16.41906769+08:00"}
{"id":2,"appid":1,"message":"/var 分区使用率 92%","title":"磁盘告警","priority":8,"extras":{"client::display":{"contentType":"text/markdown"},"client::notification":{"click":{"url":"https://example.com/monitor/disk"}}},"date":"2026-10-21T19:59:16.428773726+08:00"}

priority 语义(官方 gotify.net/docs/priority,查询日期 2026-10-21):Android 端 0 为无通知(仅应用内可见)、1-3 仅图标、4-7 追加声音、8-10 追加震动;WebUI 对 ≥4 的消息播放声音。上面的 5/8/2 正对应「常规提醒 / 高优告警 / 低优记录」三档。

8.2 列表与分页(GET /message,客户端令牌)

连推 5 条分页测试消息后,按 limit=2 查询:

curl -s -H "Authorization: Bearer $CT" 'http://127.0.0.1:8144/message?limit=2'

实测回显(最新在前):

{"paging":{"next":"/message?limit=2&since=4","size":2,"since":4,"limit":2},"messages":[{"id":5,"appid":1,"message":"分页测试第 5 条","title":"批量消息 5","priority":2,"date":"2026-10-21T20:00:11.411250626+08:00"},{"id":4,"appid":1,"message":"分页测试第 4 条","title":"批量消息 4","priority":2,"date":"2026-10-21T20:00:11.403137897+08:00"}]}

游标翻页直接用 paging.next 的值发起下一跳(相对路径,3.x 变更,需自行拼主机名):

curl -s -H "Authorization: Bearer $CT" 'http://127.0.0.1:8144/message?limit=2&since=4'

实测回显:

{"paging":{"next":"/message?limit=2&since=2","size":2,"since":2,"limit":2},"messages":[{"id":3,"appid":1,"message":"这条消息经 /stream 实时下发","title":"WebSocket 实时验证","priority":6,"date":"2026-10-21T19:59:59.449049731+08:00"},{"id":2,"appid":1,"message":"/var 分区使用率 92%","title":"磁盘告警","priority":8,"extras":{...},"date":"2026-10-21T19:59:16.428773726+08:00"}]}

无更多数据时 next 字段缺省。应用维度消息列表用 GET /application/1/message(应用令牌可查自己,客户端令牌亦可查),回显结构与全局列表一致。

8.3 删除闭环(DELETE /message/{id})

删除对应用令牌关闭、客户端令牌开放。先用应用令牌试删(预期 401):

curl -s -w '\nHTTP %{http_code}\n' -X DELETE "http://127.0.0.1:8144/message/8?token=$AT"

实测回显:

{"error":"Unauthorized","errorCode":401,"errorDescription":"you need to provide a valid access token or user credentials to access this api"}
HTTP 401

换客户端令牌:

curl -s -w '\nHTTP %{http_code}\n' -X DELETE "http://127.0.0.1:8144/message/8?token=$CT"
curl -s -w '\nHTTP %{http_code}\n' -X DELETE "http://127.0.0.1:8144/message/8?token=$CT"

实测回显(成功 + 重复删除):


HTTP 200
{"error":"Not Found","errorCode":404,"errorDescription":"message does not exist"}
HTTP 404

删除成功 200、已删消息再删 404(幂等语义靠 404 表达,脚本重试时按此处理)。清空某应用全部消息用 DELETE /application/{id}/message(同 client 令牌权限)。

九、user 域:提权后的用户管理

第六章的 low-priv-cli 提权后,创建用户成功:

curl -s -w '\nHTTP %{http_code}\n' -X POST -H "Authorization: Bearer $CT3" \
  -H 'Content-Type: application/json' -d '{"name":"dev1","pass":"dev1pass"}' http://127.0.0.1:8144/user

实测回显:

{"id":2,"name":"dev1","admin":false,"createdAt":"2026-10-21T20:00:55.255101531+08:00"}
HTTP 200

列用户与再建一个:

curl -s -H "Authorization: Bearer $CT3" http://127.0.0.1:8144/user

实测回显:

[{"id":1,"name":"admin","admin":true,"createdAt":"2026-10-21T19:54:33.694487038+08:00"},{"id":2,"name":"dev1","admin":false,"createdAt":"2026-10-21T20:00:55.255101531+08:00"}]
{"id":3,"name":"dev2","admin":false,"createdAt":"2026-10-21T20:01:09.345169167+08:00"}
HTTP 200

用户管理闭环小结:普通会话 403 → elevate 204(+1h 窗口)→ POST/GET /user 200。改别人口令走 PUT /user/{id},删除走 DELETE /user/{id}(同权限要求);改自己口令用 PUT /current/user/password(无需提权)。

十、plugin 与实验性端点

10.1 GET /plugin

curl -s -H "Authorization: Bearer $CT" http://127.0.0.1:8144/plugin

实测回显:

[]

本实例未装载插件(配置篇目录参数 GOTIFY_PLUGINSDIR 为空目录),返回空数组。插件系统支持 Go 共享库形态的热插拔(enable/disable/config/display 四组端点见第三章总览)。

10.2 AI Agent (MCP) 端点:3.1.1 实测 404

官方文档新增「AI Agents (MCP)」指南,提及服务端暴露 /mcp 端点。3.1.1 稳定版二进制实测:

curl -s -w '\nHTTP %{http_code}\n' http://127.0.0.1:8144/mcp

实测回显:

{"error":"Not Found","errorCode":404,"errorDescription":"page not found"}
HTTP 404

该能力尚未进入 3.1.1 稳定版(面向 master 主线),生产评估时勿按稳定版默认可用对待,升级后再验证。

十一、WebSocket /stream 实测

GET /stream(客户端令牌)升级为 WebSocket 长连接,实时接收全部消息。验证脚本思路:建连 → 1 秒后用 HTTP 推一条 → 等待从流上收到同一帧 → 主动关闭。探针代码(依赖 pip install websocket-client):

#!/usr/bin/env python3
import json, threading, time, urllib.request
import websocket

BASE = "http://127.0.0.1:8144"
WS_URL = "ws://127.0.0.1:8144/stream"
CLI_TOKEN = "gtfyc.xxxx"
APP_TOKEN = "gtfya.xxxx"

def push_after(seconds):
    def _run():
        time.sleep(seconds)
        body = json.dumps({"title": "WebSocket 实时验证",
                           "message": "这条消息经 /stream 实时下发",
                           "priority": 6}).encode()
        req = urllib.request.Request(BASE + "/message?token=" + APP_TOKEN,
                                     data=body,
                                     headers={"Content-Type": "application/json"},
                                     method="POST")
        with urllib.request.urlopen(req) as resp:
            print("[push] HTTP", resp.status, resp.read().decode())
    threading.Thread(target=_run).start()

ws = websocket.create_connection(WS_URL + "?token=" + CLI_TOKEN, timeout=15)
print("[ws] connected:", ws.connected)
push_after(1)
try:
    while True:
        frame = ws.recv()
        print("[ws] recv:", frame)
        if "WebSocket 实时验证" in frame:
            break
finally:
    ws.close()

实测回显:

[ws] connected: True
[push] HTTP 200 {"id":3,"appid":1,"message":"这条消息经 /stream 实时下发","title":"WebSocket 实时验证","priority":6,"date":"2026-10-21T19:59:59.449049731+08:00"}
[ws] recv: {"id":3,"appid":1,"message":"这条消息经 /stream 实时下发","title":"WebSocket 实时验证","priority":6,"date":"2026-10-21T19:59:59.449049731+08:00"}

[ws] closed: True

HTTP 推送与 WebSocket 下发帧字段完全一致,端到端延迟在秒级内。连接保持期间服务端每 45 秒发 ping 帧(GOTIFY_SERVER_STREAM_PINGPERIODSECONDS,配置篇第三章),跨域接入需配 GOTIFY_SERVER_STREAM_ALLOWEDORIGINS(同源始终放行)。

十二、常见问题(FAQ)

12.1 令牌只有一次明文机会,丢了怎么办?

创建响应(POST /application、POST /client、登录 Set-Cookie)是明文唯一出现时刻,列表与查询接口不回 token。丢失后:应用走 PUT /application/{id}/security、客户端走 PUT /client/{id}/security 轮换(7.2 节闭环),轮换前旧令牌持续有效,泄露场景应立即轮换。

12.2 PUT /application/{id} 返回了一个 token,为什么用不了?

那是脱敏回显值(7.1 节实测陷阱):旧 token 仍 200、响应值 401。真轮换只认 security 端点。

12.3 用户管理接口为什么一直 403?

/user 域要求 Basic Auth 或提权会话(POST /client/{id}/elevate,需 admin 口令与 durationSeconds,默认窗口 1 小时,6.2 节闭环)。纯客户端令牌永远 403,这是设计行为而非故障。

12.4 推送 200 但收不到?

按链路排查:①令牌前缀对不对(应用 gtfya. 才能推);②消息是否进了库(GET /message 或 WebUI 查);③接收端是否在线(WebUI 依赖 /stream 长连接,Android 端受 priority 影响,priority=0 不弹通知,8.1 节语义表);④服务端 /health 与 journalctl。

12.5 paging.next 直接 curl 报错?

next 是相对路径(如 /message?limit=2&since=4),需拼接 http://host:port 前缀再请求(8.2 节)。

12.6 /mcp 端点 404 是坏了吗?

不是。MCP 端点未随 3.1.1 稳定版发布(10.2 节实测),官方文档面向 master 主线;升级包含该能力的版本后再验证。

12.7 Basic Auth 全局可用吗?

可用但建议最小化:Basic 等于完整管理员权限(还能绕过提权窗口),自动化脚本优先用客户端/应用令牌分域授权,Basic 只留给提权与应急操作。

十三、参考链接