API 参考
太一只暴露一份 ConnectRPC API。同一份契约同时服务内置管理 UI(HTTP 上的 JSON)、tiyi CLI(HTTP 上的 JSON)与节点流(gRPC 双向)—— UI 能做的事,脚本同样能做。共 23 个服务、238 个 RPC,另有一小组非 RPC 的 HTTP 端点。
传输
每个过程都是对 /{package}.{Service}/{Method} 的 POST,例如 POST /tiyi.v1.SiteService/ListSites。同一批路由上支持三种线上协议:
| 协议 | Content-Type | 适用场景 |
|---|---|---|
| Connect(一元) | application/json | curl、脚本、CI —— 无需帧封装,也无需生成代码。 |
| Connect(流式) | application/connect+json | 非 gRPC 客户端调用服务端流与双向流。 |
| gRPC / gRPC-Web | application/grpc | 生成的 gRPC 客户端;节点流。 |
API 同时监听管理地址(--addr,默认 0.0.0.0:8080)与本地 Unix socket(--admin-socket,默认 /run/tiyi/admin.sock)。
鉴权
三种鉴权面,按部署上下文选用:
| 鉴权面 | 机制 | 使用方 |
|---|---|---|
| 本地管理 socket | admin.sock 上的 OS 文件权限 |
与服务端同主机的 tiyi CLI。请求以合成的超级管理员身份运行 —— 这正是无凭据也能重置密码的原因。 |
| HS256 JWT | HTTP 上的 Authorization: Bearer <jwt> |
管理 UI、远程 CLI、任何从主机外驱动 API 的客户端。由 AuthService.Login 签发,AuthService.Refresh 续期。 |
| 节点流 token | 一次性入网 token → 长期流 token | AgentStreamService.Connect 上的节点。token 由 AgentService.IssueEnrollmentToken 签发。 |
登录
$ curl -sS http://tiyi:8080/tiyi.v1.AuthService/Login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"..."}'
{
"accessToken": "<jwt>",
"expiresAt": "2026-07-26T21:13:41Z",
"user": {
"id": "<uuid>",
"tenantId": "<uuid>",
"username": "admin",
"roles": ["Administrator"],
"accessCodes": ["site:read", "site:write", "..."]
}
}
三个容易踩的点:
- 登录请求体是扁平的 ——
{"username":…,"password":…},不要再包一层凭据对象。 - refresh token 不在响应体里。它以
HttpOnly、SameSite=Strict的 cookietiyi_refresh下发,作用域为Path=/tiyi.v1.AuthService/。浏览器会自动回带;脚本需要自己保存(curl -c),或在调用Refresh时显式传{"refreshToken":"…"}。 Refresh会轮换 refresh token;重放已消费的 token 会被当作凭据泄露处理:调用返回unauthenticated并清除会话。请始终保存最新值。
默认有效期:access token 8 小时(auth.access_token_ttl),refresh token 7 天(auth.refresh_token_ttl)。配置了 LDAP 或 RADIUS 时,同一个 Login 调用会按顺序回退尝试。
本地管理 socket
$ curl -sS --unix-socket /run/tiyi/admin.sock \
http://tiyi.local/tiyi.v1.SystemService/GetSystemSettings \
-H "Content-Type: application/json" -d '{}'
http://tiyi.local 只是占位符 —— 真正决定去向的是 socket 路径。socket 默认权限 0600;--admin-socket-mode 0660 --admin-socket-group tiyi-admin 可以共享给一个 Unix 组。能打开该 socket 就等于拥有完全控制权,请把该组视同 root。
CLI
$ export TIYI_API=https://tiyi.example.com # 不设置则走本地 socket
$ export TIYI_TOKEN="$TOKEN"
$ tiyi site list
命令级参数 --api、--token、--admin-socket 会覆盖环境变量。
请求约定
- 响应使用 camelCase(
primaryHost);请求同时接受 camelCase 与原始 snake_case(primary_host)。 - 64 位整数是 JSON 字符串 ——
"revision": "2",发送时同样要加引号。 - 枚举是带前缀的字符串 ——
"RESOURCE_STATUS_ACTIVE"、"WAF_MODE_BLOCKING",零值恒为*_UNSPECIFIED。 - 时间戳是 RFC 3339 UTC ——
"2026-07-26T13:15:08.217701748Z"。 - 写操作要包一层资源字段:
CreateSite收的是{"site": {…}},不是把字段平铺在顶层。平铺会返回invalid_argument: site is required。 - field mask 是逗号连接的字符串,不是数组:
"updateMask": "name,status"。不传 mask 会整体覆盖,导致不认识新字段的旧客户端把它清空。 - 未知字段会被忽略而不是报错。写错的 key 静默失效 —— 写完请回读确认。
- 每个请求都按调用方 token 的租户隔离,绝不取请求体里的租户字段。
revision 与乐观并发
带版本的资源都有 revision,每次写入自增。提交时带上你读到的 revision;若已被他人抢先写入,调用会返回 failed_precondition: revision conflict。重新读取、重放改动、再重试即可。省略 revision 则跳过该检查。
路由与站点级策略覆盖有各自独立的 RPC(UpdateSiteRouting、UpsertSitePolicyOverride),正是为了避免不理解路由的客户端用一次普通 UpdateSite 把它们抹掉。
分页
有界列表(站点、用户、证书、策略)用偏移分页:{"page":{"page":1,"pageSize":50}},页码从 1 开始,默认每页 50,上限 200。日志与事件查询用游标:把上一次的 nextCursor 传回,直到 hasMore 为 false。需要直接跳页时用 offset 代替 cursor,上限 10 万行,超出应改为收窄时间范围。
23 个服务
| 服务 | 范围 |
|---|---|
AuthService | 登录、登出、刷新、当前用户、access code、修改密码。 |
SystemService | 健康、设置、Dashboard 汇总、声明式 apply、CRS/GeoIP/二进制发布、升级批次。 |
MenuService | 按权限过滤的管理界面导航。 |
UserService | 用户 CRUD、角色分配、锁定/解锁、密码重置。 |
RoleService | 角色 CRUD 与权限目录。 |
SiteService | 站点 CRUD、启用/停用、编译配置预览、路径路由、站点级策略覆盖。 |
UpstreamService | 上游池 CRUD;被引用时拒绝删除。 |
CertService | 上传、ACME 签发/续期、下载、DNS provider CRUD。 |
PolicyService | WAF 策略 CRUD、分层更新、版本快照与回滚、SecLang 预览、测试台。 |
RuleOverrideService | 单条 CRS 规则的行为与范围化覆盖生命周期。 |
CustomRuleService | 自定义 SecLang 与可视化规则的生命周期、排序、模板。 |
IpListService | 可复用 IP 数据、分层绑定、CSV 导入导出、查询与优先级。 |
RateLimitService | 端点与客户端范围的限速资源。 |
CrsService | CRS 目录浏览、导入、排除包安装与挂载。 |
AgentService | 节点 CRUD、入网 token、安装脚本、命令、配置 bundle、指标采样。 |
AgentGroupService | 基于标签与显式列表的节点分组及匹配预览。 |
AgentStreamService | 带鉴权的节点双向会话流。 |
EvidenceUploadService | 请求证据上传独占一条流,避免大请求体阻塞控制面流量。 |
TrustService | 客户端 IP 信任配置(租户 + 站点覆盖)、CDN 快照、explain。 |
AlertService | 告警规则与通道 CRUD、ack/resolve、静默、备注、通道测试发送。 |
LogService | 安全/访问/错误事件查询、保留证据、导出、日志策略、实时 tail。 |
AuditService | 防篡改审计查询、链状态与校验。 |
AIService | 可选且默认关闭的建议、Copilot 分析与 Chat。 |
逐 RPC 的完整参考 —— 每个请求/响应字段以及所需权限 —— 由 schema 自动生成,随源码树发布在 docs/api/,protobuf 定义在 proto/tiyi/v1/。
错误
错误返回标准码、给人看的 message,有时还带类型化 details。请基于 code(以及 details[].debug.reason)分支,不要基于 message。
{
"code": "unauthenticated",
"message": "invalid session",
"details": [{ "type": "google.rpc.ErrorInfo",
"debug": { "reason": "TOKEN_INVALID", "domain": "tiyi.io" } }]
}
| 码 | HTTP | 常见原因 | 可重试? |
|---|---|---|---|
invalid_argument | 400 | 参数被验证器拒绝。 | 否 —— 先改请求。 |
failed_precondition | 400 | revision 冲突、资源被引用或其他状态保护。 | 重新读取后重试。 |
unauthenticated | 401 | token 缺失、格式错误或过期。 | 刷新后重试一次。 |
permission_denied | 403 | 会话有效但缺少所需权限;也用于 ACCOUNT_LOCKED。 | 否。 |
not_found | 404 | ID 不存在或已软删除。 | 否。 |
already_exists | 409 | 违反唯一性约束。 | 否。 |
resource_exhausted | 429 | 写入准入队列已满。 | 是 —— 遵守 Retry-After。 |
unimplemented | 501 | schema 中已声明但尚未接线。 | 否。 |
unavailable | 503 | SQLite 繁忙或依赖不可用。 | 是 —— 退避重试。 |
deadline_exceeded | 504 | 服务端超时。 | 收窄查询范围。 |
internal | 500 | 未处理的服务端故障。 | 否 —— 请反馈。 |
已知的 ErrorInfo reason:BAD_CREDENTIALS、TOKEN_INVALID、ACCOUNT_LOCKED。
写入准入
写类 RPC 会经过写入准入闸门排队,避免并发写入压垮内嵌 SQLite。队列满时返回 resource_exhausted,并带上以秒为单位的 Retry-After 头。读取从不排队。批量导入应限制写并发并遵守该响应头。
RBAC
每个 RPC 声明所需权限 —— site:read、policy:write、log:export、system:apply 等,共 57 项。服务端将 JWT subject 的角色与权限表比对;持有 tiyi:superadmin 的角色通过所有检查。内置角色只有 Administrator 且拥有全部权限,因此请用 tiyi role 或角色页面创建最小权限角色,并用 RoleService.ListPermissions 查看实时目录,而不是依赖固定清单。
七个过程无需会话即可访问:SystemService.Health、AuthService.Login / Refresh / Logout、AgentService.Enroll、AgentStreamService.Connect、EvidenceUploadService.Upload。后三者改由入网 token 或流 token 把关。
流式 RPC
流式调用可走 gRPC,也可走 Connect 流式协议。使用 Connect 时设置 Content-Type: application/connect+json,并给每条消息加 5 字节信封:1 字节标志位 + 大端 uint32 长度。流失败时 HTTP 状态码仍是 200 —— 真正的状态在结尾帧(标志位 0x02)里,只看 HTTP 码的客户端会漏掉错误。
AgentStreamService.Connect—— 双向。节点会话:hello、状态上报、事件、apply 结果、观测批次;服务端下发 welcome、配置更新、命令、challenge。EvidenceUploadService.Upload—— 双向。请求证据体,与控制流隔离。AgentService.StreamAgentEvents—— 服务端流。节点在线/离线与指标。LogService.TailSecurityEvents—— 服务端流。实时安全事件流。LogService.StreamRequestEvidenceBody/DownloadRequestEvidenceBody—— 服务端流。分块下发保留的请求体。AlertService.StreamAlerts—— 服务端流。告警生命周期事件。SystemService.StreamUpgradeRun—— 服务端流。按节点的升级进度。AIService.StreamAnalysis/StreamChat—— 服务端流。Copilot 增量输出。
断线请退避重连。这些流都不会重放历史,断连期间遗漏的数据需要另行查询补齐。
非 RPC 的 HTTP 端点
| 端点 | 监听面 | 鉴权 | 用途 |
|---|---|---|---|
GET /healthz | 两者 | 无 | 存活/就绪探针:健康 200,否则 503,两种情况都返回 JSON。 |
GET /download/tiyi | 两者 | 无 | 下发当前二进制,供尚无凭据的新节点自举。 |
/api/v1/telemetry/* | 两者 | JWT + telemetry:read | 精确计数:qps、series、topk、apitree 以及 API 资产处置路由。 |
GET /metrics | 仅 socket | socket 权限 | 观测管线的 Prometheus/OpenMetrics 抓取端点。 |
GET /debug/* | 仅 socket | socket 权限 | logsink、告警、AI、许可的诊断计数器。非稳定契约。 |
管理监听同时提供管理 UI,未知路径会回落到 SPA 并返回 HTTP 200 —— 所以对「仅 socket」路径 curl 成功,拿到的其实是 HTML 而非数据。请检查 Content-Type。
生成客户端
把代码生成器指向源码树中的 proto/tiyi/v1。已验证的组合:connect-go(CLI 使用)、connect-es / @connectrpc/connect-web(管理 UI 使用),以及任意标准 gRPC 客户端(适合流式为主的集成)。由于一元 Connect 就是 HTTP 上的普通 JSON,一个简单的 fetch 或 requests 封装同样够用。
包名是 tiyi.v1;字段号永不复用,删除的字段保留为 reserved,因此旧客户端可以继续解析新响应。新的可选字段与新 RPC 在小版本中引入 —— 请把未知响应字段当作可忽略。
API 契约
本文档覆盖面向运维的 API 表面,并应与已安装二进制保持一致 —— 不一致请提 issue。