Stoat 0.15.5 API 使用详解
把 Stoat 0.15.5 服务端 REST API 的调用方式讲透——认证体系、账号/会话/引导(onboarding)流程、服务器与频道管理、消息收发与已读、文件上传下载,全部端点带实测请求与真实响应回显。已在自托管实例 `http://192.168.1.200:8140` 上逐条 curl 实测通过,注册/登录/建服/发消息/附件闭环全部可复现。
Stoat 0.15.5 API 使用详解
目标:把 Stoat 0.15.5 服务端 REST API 的调用方式讲透——认证体系、账号/会话/引导(onboarding)流程、服务器与频道管理、消息收发与已读、文件上传下载,全部端点带实测请求与真实响应回显。已在自托管实例
http://192.168.1.200:8140上逐条 curl 实测通过,注册/登录/建服/发消息/附件闭环全部可复现。
一、环境信息
本篇所有请求与响应均来自以下实测环境:
| 项目 | 值 |
|---|---|
| 服务端 | Stoat v0.15.5(API 自报 "revolt":"0.15.5") |
| 实例入口 | http://192.168.1.200:8140(API 路径前缀 /api) |
| API 风格 | REST + JSON(delta 服务,路径与上游 Revolt 同源) |
| 实时通道 | WebSocket ws://192.168.1.200:8140/ws |
| 文件服务 | http://192.168.1.200:8140/autumn(multipart 上传) |
| 客户端工具 | curl 7.81+ / Python 3 标准库 |
| 测试账号 | root@stoat.local(用户名 rootadmin,演示环境) |
二、前置条件
- 一套可访问的 Stoat 实例(部署过程见 《Stoat 0.15.5 Docker Compose 部署指南》),且能从你的网络访问其 80/443(或自定义入口端口)。
- 开放注册实例可直接注册;
invite_only实例需先在库内生成邀请码(配置见 《Stoat 0.15.5 配置参数详解》5.3 节)。 - 准备一个不在泄露密码黑名单内的口令——0.15.5 注册与登录都会校验,
q1w2e3r4这类常见口令会被拒绝(详见第七章 Q1)。
三、API 总览
Stoat 服务端按子系统拆分为多个服务,对外由统一入口按路径分发,客户端只需面对一个 base URL:
| 路径前缀 | 后端服务 | 职责 |
|---|---|---|
/api | api(delta) | 全部 REST 业务:账号/会话/用户/服务器/频道/消息/邀请等 |
/ws | events(bonfire) | WebSocket 实时事件(消息推送、在线状态) |
/autumn | autumn | 文件上传下载(附件/头像/表情等按 tag 区分) |
/january | january | 链接预览与图片代理 |
/gifbox | gifbox | Tenor GIF 代理 |
/livekit | livekit | 语音视频信令 |
/ingress | voice-ingress | 语音信令入口 |
版本与能力自报:GET /api/ 无需认证,返回服务端版本、功能开关与限额体系,是调试的第一入口。认证方式有两种(源码认证中间件核读确认):
| 认证头 | 适用身份 | 获取方式 |
|---|---|---|
x-session-token | 用户会话 | POST /api/auth/session/login 返回 |
x-bot-token | 机器人账号 | 建机器人后获取 |
限流约定:服务端对新注册用户(默认 72 小时内,features.limits.global.new_user_hours)施加更严格的限额——同一时刻最多 5 个好友请求、2 个 bot、50 个服务器;老用户默认 10/5/100。全部限额可通过 GET /api/ 的 limits 对象实时核对。错误响应统一为 JSON 对象,type 是机器可读错误码、location 是源码定位(调试用):
{"type":"CompromisedPassword","location":"crates/core/database/src/util/password.rs:68:20"}
四、认证与首次调用
4.1 服务自报(免认证)
curl -s http://192.168.1.200:8140/api/ | head -c 260
实测输出(节选):
{"revolt":"0.15.5","features":{"captcha":{"enabled":false,"key":""},"email":false,"invite_only":false,"autumn":{"enabled":true,"url":"http://192.168.1.200:8140/autumn"},...
关键字段解读:revolt 服务端版本;email 邮件功能是否启用;invite_only 是否邀请制;autumn/january/livekit 各子服务的公网 URL(客户端应使用这里自报的地址而非自行拼接);ws WebSocket 地址;vapid Web Push 公钥。
4.2 注册账号
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://192.168.1.200:8140/api/auth/account/create \
-H 'Content-Type: application/json' \
-d '{"email":"root@stoat.local","password":"q1w2e3r4T!"}'
实测输出:
204
成功返回 HTTP 204 且响应体为空。已配置 SMTP 的实例会在此后发送验证邮件;未配置 SMTP 的实例(email:false)注册即生效。错误码:CompromisedPassword(400,口令命中泄露库)、ShortPassword(400,短于 8 位)、InvalidEmail、MissingInvite/InvalidInvite(邀请制)、OperationFailed(500,对已存在邮箱重复注册时的误导性报错,实际语义是"该邮箱已注册",见第七章 Q2)。
4.3 登录换取会话令牌
curl -s -X POST http://192.168.1.200:8140/api/auth/session/login \
-H 'Content-Type: application/json' \
-d '{"email":"root@stoat.local","password":"q1w2e3r4T!","friendly_name":"cli"}'
实测输出(节选):
{"result": "Success", "_id": "01M658ARB9BY1A881X0ED94C13", "user_id": "01M65871VK4Y840R0RMN9X9Z38", "token": "TYcaWBdo4x_GKHNpzjwrVgqqlNVSgizglvUBkKBcG9mt2G0Arhsyol9MpNwOfCvj", "name": "cli", "last_seen": "2026-10-...
字段解读:result 成功时为 Success(开启 MFA 的账号返回 MFA,需走 /api/auth/mfa/ticket 二段流程);_id 会话 ID;token 会话令牌,后续所有请求以 x-session-token 头携带。重复登录每次生成新会话,互不影响(实测同一账号连续创建 5 个会话均成功)。错误码:UnverifiedAccount(403,邮箱未验证)、LockedOut(403,账号锁定)。
4.4 首次引导(Onboarding)
登录成功不代表可用:从未设置用户名的账号需先完成 onboarding。查询状态:
curl -s http://192.168.1.200:8140/api/onboard/hello -H "x-session-token: $TOKEN"
实测输出:
{"onboarding": true}
设置用户名完成引导(注意是 POST,不是 PUT):
curl -s -X POST http://192.168.1.200:8140/api/onboard/complete \
-H 'Content-Type: application/json' -H "x-session-token: $TOKEN" \
-d '{"username":"rootadmin"}'
实测输出:
{"_id": "01M65871VK4Y840R0RMN9X9Z38", "username": "rootadmin", "discriminator": "6699", "relationship": "User", "online": false}
字段解读:username + 4 位 discriminator 组成完整用户标识(类似旧版 Discord 的 用户名#编号)。错误码:InvalidUsername(400,触发保留名/长度/Unicode 校验——admin、revolt、stoat 三个名字实测均被拒绝)。
4.5 查询当前用户
curl -s http://192.168.1.200:8140/api/users/@me -H "x-session-token: $TOKEN"
实测输出:
{"_id": "01M65871VK4Y840R0RMN9X9Z38", "username": "rootadmin", "discriminator": "6699", "relationship": "User", "online": false}
@me 是当前会话用户的标准别名。至此认证链路全部打通,后续请求均可携带该令牌执行。
五、各 API 域详解
5.1 服务器域(servers)
创建服务器(同时自动生成一个默认文字频道):
curl -s -X POST http://192.168.1.200:8140/api/servers/create \
-H 'Content-Type: application/json' -H "x-session-token: $TOKEN" \
-d '{"name":"Stoat 实验室","description":"自托管验证用服务器"}'
实测输出(节选):
{"server": {"_id": "01M658ARBRVPTARSX9MTY4WG0M", "owner": "01M65871VK4Y840R0RMN9X9Z38", "name": "Stoat 实验室", "description": "自托管验证用服务器", "channels": ["01M658ARBRV8025VHGN5V0J84S"], "default_permissions": 77014766592, "ap...
⚠️ 注意响应结构:0.15.5 返回
{"server": {...}}包装对象,channels是频道 ID 字符串数组(不是嵌套对象),取默认频道 ID 写resp["server"]["channels"][0]。
在服务器下创建文字频道:
curl -s -X POST http://192.168.1.200:8140/api/servers/01M658ARBRVPTARSX9MTY4WG0M/channels \
-H 'Content-Type: application/json' -H "x-session-token: $TOKEN" \
-d '{"name":"通用聊天","type":"Text","description":"验证用频道"}'
实测输出:
{"channel_type": "TextChannel", "_id": "01M658ARBX3AM67CXQJZP0452D", "server": "01M658ARBRVPTARSX9MTY4WG0M", "name": "通用聊天", "description": "验证用频道"}
type 支持 Text(文字)、Voice(语音);响应以 channel_type 自报类型。错误码:TooManyServers(403,超出 limits 中的 servers 上限)。
5.2 消息域(channels / messages)
发送消息(nonce 是客户端生成的幂等防重 ID):
curl -s -X POST http://192.168.1.200:8140/api/channels/01M658ARBX3AM67CXQJZP0452D/messages \
-H 'Content-Type: application/json' -H "x-session-token: $TOKEN" \
-d '{"content":"你好 Stoat!这是一条来自 0.15.5 自托管实例的验证消息。","nonce":"157cc1ba395241a99ae751f1748ec944"}'
实测输出(节选):
{"_id": "01M658ARC2XJZHEG9V2QXSX5BS", "nonce": "157cc1ba395241a99ae751f1748ec944", "channel": "01M658ARBX3AM67CXQJZP0452D", "author": "01M65871VK4Y840R0RMN9X9Z38", "user": {"_id": "01M65871VK4Y840R0RMN9X9Z38", "username...
content 长度上限 2000 字符(limits.new_user.message_length);attachments 字段可携带文件 ID 数组(见 5.4 闭环)。错误码:PayloadTooLarge(413,超 body 上限)、MissingPermission(403,无该频道发消息权限)。
读取频道历史(分页参数 limit,最大 100):
curl -s "http://192.168.1.200:8140/api/channels/01M658ARBX3AM67CXQJZP0452D/messages?limit=5" -H "x-session-token: $TOKEN"
实测输出(节选,按时间近→远排列):
[{"_id": "01M658ARC2XJZHEG9V2QXSX5BS", "nonce": "157cc1ba395241a99ae751f1748ec944", "channel": "01M658ARBX3AM67CXQJZP0452D", ...
编辑消息(PATCH,返回对象带 edited 时间戳):
curl -s -X PATCH http://192.168.1.200:8140/api/channels/01M658ARBX3AM67CXQJZP0452D/messages/01M658ARC2XJZHEG9V2QXSX5BS \
-H 'Content-Type: application/json' -H "x-session-token: $TOKEN" \
-d '{"content":"(编辑)你好 Stoat!0.15.5 验证消息已编辑。"}'
实测输出(节选):
{"_id": "01M658ARC2XJZHEG9V2QXSX5BS", "nonce": "157cc1ba395241a99ae751f1748ec944", ..., "content": "(编辑)你好 Stoat!0.15.5 验证消息已编辑。", "edited": "20...
标记已读(ack,成功返回 204 空体;该操作同时触发 RabbitMQ ack 队列更新推送角标):
curl -s -o /dev/null -w '%{http_code}\n' -X PUT \
http://192.168.1.200:8140/api/channels/01M658ARBX3AM67CXQJZP0452D/ack/01M658ARC2XJZHEG9V2QXSX5BS \
-H "x-session-token: $TOKEN"
实测输出:
204
5.3 实时事件域(WebSocket)
REST 负责写操作,消息的实时下发走 WebSocket(GET /api/ 自报的 ws 地址)。握手验证:
curl -s -i -N -H 'Connection: Upgrade' -H 'Upgrade: websocket' \
-H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
http://192.168.1.200:8140/ws | head -3
实测输出:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
握手成功后(标准部署下经 Caddy 的 /ws 路径转发到 events 服务),客户端应先发送认证包(携带会话令牌),随后服务端推送 Ready 快照与后续事件流;消息的送达、编辑、已读回执都以事件形式实时推给在线客户端。REST 发消息后若无 WS 在线,事件会经 Redis/RabbitMQ 队列沉淀,客户端重连后通过 GET .../messages 增量补齐。
5.4 文件域(autumn)
上传附件(multipart,tag 为 attachments,其他 tag 如 avatars/banners/emojis 各有独立大小限额):
curl -s -H "x-session-token: $TOKEN" \
-F 'file=@/opt/stoat/upload_test.txt' \
http://192.168.1.200:8140/autumn/attachments
实测输出:
{"id":"hKJF9aL6PWdQByFT_miNdLqdqnAQizvdj7nN-l2jpC"}
⚠️ 注意两个实测行为:不带认证头直接上传返回 401
{"type":"NotAuthenticated",...};返回体是{"id": ...}JSON 对象(旧版上游是裸字符串 ID,写解析时留意)。
把文件 ID 作为消息附件发送(完成"挂载"):
curl -s -X POST http://192.168.1.200:8140/api/channels/01M658ARBX3AM67CXQJZP0452D/messages \
-H 'Content-Type: application/json' -H "x-session-token: $TOKEN" \
-d '{"content":"附件闭环验证","attachments":["hKJF9aL6PWdQByFT_miNdLqdqnAQizvdj7nN-l2jpC"]}'
实测输出关键字段:"attachments": [{"_id": "hKJF9aL6PWdQByFT_miNdLqdqnAQizvdj7nN-l2jpC", ...}](消息对象内出现附件详情)。
下载验证(URL 形态为 /autumn/<tag>/<文件ID>/<文件名>,免认证公开可访问):
curl -s http://192.168.1.200:8140/autumn/attachments/hKJF9aL6PWdQByFT_miNdLqdqnAQizvdj7nN-l2jpC/upload_test.txt
实测输出:
stoat upload test
⚠️ 重要:上传后未挂载的文件下载返回 404(服务端按
used_for判定,防未引用文件被盗链)——先发带attachments的消息再访问下载 URL。文件名与上传时不一致也会 404(可用original占位名重定向到真实文件名)。
六、分页、过滤与通用约定
- 分页:列表类端点用
?limit=N(消息历史实测?limit=5生效,默认 50、上限 100);支持?before=<消息ID>/?after=<消息ID>按游标翻页,?near=<消息ID>定位上下文,?sort=Latest倒序。 - 幂等:写操作请求体可带客户端生成的
nonce(32 位十六进制),同 nonce 重放不会产生重复消息——弱网重试场景务必携带。 - 空体成功:创建账号(204)、ack 已读(204)等"无内容返回"的操作用空响应体表达成功,客户端不要按 JSON 解析。
- 错误判定:2xx 之外统一看响应体
type字段做分支处理;location字段仅供报障时附上(对应服务端源码位置)。 - ID 形态:全系统对象 ID 为 ULID(如
01M658ARC2XJZHEG9V2QXSX5BS),按字典序即时间序,可直接用于游标排序。 - 大响应标注:本篇实测响应均为节选展示(完整对象含权限位、角色数组等字段,篇幅较长),字段语义以响应内自报为准。
七、常见问题
Q1:注册或登录都报 CompromisedPassword?
0.15.5 把约 10 万条泄露密码黑名单编译进了服务端二进制,注册(create_account)与登录(session/login)两个入口都会执行 assert_safe 校验,实测 q1w2e3r4 无论注册还是登录一律 400。换成不在黑名单的口令(如 q1w2e3r4T!)即可。服务端另支持外接 easypwned 泄露库服务(配置 [api.security] easypwned)。
Q2:对已注册邮箱再次调用注册接口返回 500 OperationFailed?
账号已存在时,注册流程转入"补发验证邮件/密码重置"分支;未配置 SMTP 的实例该分支必然失败(报错定位在 accounts/model.rs 的密码重置函数)。见到此错误按"邮箱已注册"处理,走登录流程;生产实例配置 SMTP 后此路径恢复正常语义。
Q3:建服务器成功但拿不到频道对象?
0.15.5 的创建服务器响应是 {"server": {...}} 包装结构,server.channels 是频道 ID 字符串数组。需要频道详情时逐个调 GET /api/channels/<id>,或直接用返回的默认频道 ID 发消息。
Q4:上传成功但下载 404?
附件必须先"挂载"——以 attachments 字段随消息发送(或设为头像等用途)后才开放下载,used_for 为空的文件拒绝外链。见 5.4 的闭环实测。
Q5:WebSocket 握手成功但收不到消息事件?
握手(101)只建立通道,还需在连接上发送认证包(含会话令牌)完成身份绑定后才会推送事件;同时确认客户端连接的是 GET /api/ 自报的 ws 地址——自托管实例改过域名/端口时,该自报值必须与实际入口一致(配置项见 《Stoat 0.15.5 配置参数详解》5.2 节)。
Q6:如何用脚本完成整套冒烟验证?
按本篇顺序串联即可:注册(204)→ 登录取 token → onboard(若 onboarding:true)→ 建服务器 → 建频道 → 发消息 → 读历史 → ack → 上传附件 → 挂载发消息 → 下载回读。每步的预期状态码与回显本文均已给出,全部通过即代表实例核心链路健康。
八、参考链接
- 后端 API 路由源码:https://github.com/stoatchat/stoatchat(crates/delta/src/routes/,端点定义与错误码体系)
- 上游 Revolt API 文档(与 0.15.5 路径同源,结构参考):https://docs.revolt.chat/api
- 自托管编排仓库:https://github.com/stoatchat/self-hosted(Caddyfile 路径分发规则)
- 认知与选型:《Stoat 0.15.5 介绍与选型指南》(服务拆分与事件机制背景)
- 部署实操:《Stoat 0.15.5 Docker Compose 部署指南》(实例从零搭建与入口验证)
- 配置参考:《Stoat 0.15.5 配置参数详解》(限额、注册策略与认证相关配置项)