API 参考
概述
基础 URL:http://<address>:<port>/(默认 0.0.0.0:10100)。
认证方式:令牌通过 ?token= 查询参数或 Authorization: Bearer <token> 请求头传递(查询参数优先)。令牌按作用域分组:
- Read — 可访问只读渲染端点。
- Admin — 可访问全部端点(Admin 令牌同时满足 Read 要求)。
所有受保护的端点都位于 /app/ 命名空间下。未认证或作用域不足的请求返回 401 Unauthorized。
中间件
每个请求都会经过以下中间件栈(自外向内):
| 中间件 | 行为 |
|---|---|
| 超时 | 请求超过 9 秒返回 504 Gateway Timeout |
| 追踪 | HTTP 请求追踪(TraceLayer) |
| 压缩 | 透明负载压缩(CompressionLayer) |
| 请求体限制 | 超过 10 MB 返回 413 Payload Too Large |
| 请求追踪器 | 为每个请求添加 x-request-id 响应头并记录日志 |
| 指标 | 为每个请求记录 Prometheus 指标 |
| CORS | 可配置的来源(setting.cors_origins,默认 ["*"]);所有响应附带 Access-Control-Allow-* 与 HSTS 头。来源列表每请求从 live config 读取,修改 cors_origins 后热重载立即生效 |
| 速率限制 | 按 IP 限流(默认每 60 秒 600 次)+ 并发限制(默认 600),超限返回 429 Too Many Requests(带 Retry-After 头) |
| 内容类型校验 | POST/PUT/PATCH 必须携带 Content-Type: application/json 或 application/x-www-form-urlencoded,缺失返回 400,类型不符返回 415 |
| 认证 | 按路由作用域校验令牌(Read/Admin) |
公开端点
以下端点无需认证。
GET /
健康检查。返回纯文本问候信息。
响应:200 OK
Hello world!
GET /v
版本信息。
响应:200 OK
thurgio 0.18.0 (git-describe 快照)
GET /health
以 JSON 格式返回详细的系统健康状态。
响应:200 OK
{
"status": "healthy",
"version": "thurgio 0.18.0 (...)",
"database": "connected",
"memory_usage_mb": 42,
"cpu_cores": 8,
"timestamp": "2025-01-01T00:00:00Z"
}
字段说明:
| 字段 | 类型 | 描述 |
|---|---|---|
status | string | 始终为 "healthy" |
version | string | 名称 + 版本号 + git 快照 |
database | string | 始终为 "connected" |
memory_usage_mb | integer | RSS 内存(MB,来自 /proc/self/status 的 VmRSS) |
cpu_cores | integer | 可用并行度 |
timestamp | string | RFC 3339 时间戳(UTC) |
GET /ready
就绪探针——检查 SQLite 数据库是否可读。
响应 200 OK(数据库可读):
{
"status": "ready",
"database": "connected"
}
响应 503 Service Unavailable(数据库不可读):
{
"status": "not_ready",
"database": "disconnected"
}
GET /metrics
Prometheus 格式的指标数据,包含请求指标、熔断器快照、渲染线程池指标(renderer_active_threads、renderer_backlog_smoothed、renderer_threads_spawned_total、renderer_threads_exited_total、renderer_slots_total)与内存占用。
响应:200 OK
纯文本 Prometheus 暴露格式。
GET /favicon.svg / GET /favicon-dark.svg
编译期嵌入的站点图标(crates/thurgio-server/assets/favicon.svg 与 favicon-dark.svg)。登录页与 Dashboard 通过 <link rel="icon" media="(prefers-color-scheme: ...)"> 按系统配色自动切换亮/暗图标。
响应:200 OK,内容类型 image/svg+xml。
GET /icon-app.svg
编译期嵌入的渐变 App 图标(crates/thurgio-server/assets/icon-app.svg),Dashboard 头部 brand 标记使用。
响应:200 OK,内容类型 image/svg+xml。
GET /app/login / POST /app/login
令牌登录页与校验接口(无需认证)。
GET返回登录页 HTML。POST接收 JSON{"token": "..."},校验令牌作用域:
响应 200 OK(有效令牌):
{ "ok": true, "scope": "admin" }
scope 为 "admin" 或 "read"。
响应 400 Bad Request(缺少 token 字段):
{ "ok": false, "error": "missing token" }
响应 401 Unauthorized(令牌无效):
{ "ok": false, "error": "invalid token" }
只读端点(Read/Admin 令牌)
以下端点接受 Read 或 Admin 作用域令牌。
渲染 Provider
GET /app/read/l?name={name}
按名称渲染 provider(name 为必填查询参数)。渲染前会按 TTL 检查并更新数据。
查询参数:
| 参数 | 类型 | 描述 |
|---|---|---|
name | string | 必填——要渲染的 provider 名称(缺失或为空 → 400) |
d | string | 可选——设置 Content-Disposition: attachment; filename="..."(为空时使用 name) |
token | string | 认证令牌(也可通过 Authorization: Bearer 头传递) |
| (任意) | string | 其他查询参数将作为渲染参数(hp)传入 |
请求级规则覆盖(ADR-061,仅 provider 端点,不写入配置):
| 参数 | 语义 |
|---|---|
pre_rename / filter / rename | 覆盖配置中对应阶段(逗号分隔的规则名);空值 = 清空该阶段 |
ap_pre_rename / ap_filter / ap_rename | 追加到配置对应阶段规则之后 |
emoji | 覆盖 emoji 策略(enable/disable/hide;非法值 → 400) |
udp | 覆盖 udp 策略(none/false/try/true;非法值 → 400) |
tpl | 覆盖本次渲染模板(空值回退 provider template) |
同一阶段同时传普通与 ap_ 参数(如 rename= + ap_rename=)→ 400;未知规则名静默忽略;带规则参数时从原始响应内容重新解析(仅 tpl 时复用缓存)。snippet(/app/read/r)与 adhoc 遮蔽命中时不支持规则覆盖。
# 覆盖 filter:本次请求只保留 us + vmess 规则命中的节点
curl ".../app/read/l?name=my-provider&token=...&filter=us,vmess"
# 追加规则 / 清空过滤
curl ".../app/read/l?name=my-provider&token=...&ap_filter=vmess"
curl ".../app/read/l?name=my-provider&token=...&filter="
# 切换模板
curl ".../app/read/l?name=my-provider&token=...&tpl=clash.tpl"
响应:渲染后的 provider 内容,并转发提供者响应的 subscription-userinfo 头。
渲染 Snippet
GET /app/read/r?name={name}
按名称渲染 snippet(name 为必填查询参数)。
查询参数:与 /app/read/l?name= 相同(但不支持请求级规则覆盖)。
响应:渲染后的 snippet 内容,并转发请求携带的 subscription-userinfo 头。
渲染 UI
GET /app/read/ui
后台触发一次更新,然后以 HTML 形式渲染 ui.tpl 模板。上下文注入当前令牌的 scope 变量("admin"/"read")。
响应:200 OK,内容类型为 text/html。
渲染上下文
GET /app/read/c
以 JSON 格式渲染完整的 Tera 上下文(config、p、hp、headers、version 等),同样注入 scope 变量;config 内的 proxies/content 字段已剔除(dashboard 只消费资源键名)。
响应:200 OK,内容类型为 application/json。
构建通知(SSE)
GET /app/read/events
Server-Sent Events 流。每次构建完成推送 build_complete 事件,每 15 秒发送一次 keep-alive 心跳。
响应:200 OK,text/event-stream。
订阅健康度
GET /app/read/health
订阅健康度聚合:为每个 provider/snippet 报告熔断器状态(closed/open/half-open)、失败计数、最后失败时间(Unix 秒)与最后成功更新时间(RFC 3339),并汇总为总体状态。数据源为熔断器池快照与 DB 中持久化的抓取时间戳,不触发更新。
响应:200 OK
{
"overall": "degraded",
"summary": { "total": 3, "closed": 2, "open": 1, "half_open": 0 },
"items": [
{
"key": "provider:example",
"name": "example",
"type": "provider",
"breaker": "open",
"failure_count": 5,
"last_failure_at": 1755160000,
"last_update_at": "2026-08-14T12:00:00Z"
}
]
}
overall 取值:healthy(无 open/half-open)、degraded(存在但未全部 open)、unhealthy(全部 open);空配置视为 healthy。未抓取过的 item 两个时间字段均为 null。
订阅商店
GET /app/read/subs
聚合列出全部配置订阅。provider 在前(声明顺序),带解析后节点数(proxies)与流量元数据(来自 subscription-userinfo 头,无该头或不可解析时各字段为 null);snippet 在后(无节点数与元数据字段)。adhoc 临时资源不列出。
响应:200 OK
{
"count": 2,
"items": [
{
"name": "example",
"kind": "provider",
"proxies": 42,
"upload": 1073741824,
"download": 10737418240,
"total": 107374182400,
"used": 11811160064,
"expire": 1893456000,
"expire_iso": "2030-01-01T00:00:00Z"
},
{ "name": "ruleset", "kind": "snippet", "proxies": null, "upload": null, "download": null, "total": null, "used": null, "expire": null, "expire_iso": null }
]
}
除数据就绪门卫外不触发抓取,纯只读聚合。
订阅元数据
GET /app/read/meta?name={name}
报告单个 provider 的流量/配额元数据,解析其缓存响应中的 subscription-userinfo 头。
查询参数:
| 参数 | 类型 | 描述 |
|---|---|---|
name | string | 必填——provider 名 |
响应:200 OK
{
"name": "example",
"upload": 1073741824,
"download": 10737418240,
"total": 107374182400,
"used": 11811160064,
"expire": 1893456000,
"expire_iso": "2030-01-01T00:00:00Z"
}
| 字段 | 类型 | 描述 |
|---|---|---|
used | integer | null | 已用流量(upload + download),两者均未知时为 null |
expire_iso | string | null | 到期时间的 UTC RFC 3339 形式 |
| 其余 | integer | null | 上传/下载/总量/到期 Unix 秒,未知时为 null |
仅支持 provider 名:snippet 或 adhoc 同名 → 400;未知 → 404;响应无该头或无可解析字段 → 404。
Provider 历史序列
GET /app/read/history?name={name}&limit={n}
报告单个 provider 持久化的快照时间序列——每次成功同步上游记录一个点(时间戳、节点数、当时的流量元数据),按时间升序。只读 KV 持久层:无就绪门卫、不触发网络;从未同步过的 provider 返回空列表。
查询参数:
| 参数 | 类型 | 描述 |
|---|---|---|
name | string | 必填——provider 名 |
limit | integer | 可选——只保留最近 N 个快照(结果仍升序) |
响应:200 OK
{
"name": "example",
"snapshots": [
{
"ts": 1755153600,
"proxies": 40,
"upload": null, "download": null, "total": null, "expire": null
},
{
"ts": 1755160000,
"proxies": 42,
"upload": 1073741824, "download": 10737418240, "total": 107374182400, "expire": 1893456000
}
]
}
provider-only 策略与 /app/read/meta 一致:snippet/adhoc 同名 → 400;未知 → 404。
历史快照对比
GET /app/read/history/diff?name={name}&from={a}&to={b}
对比同一 provider 时间序列中的两个快照。from/to 先按快照时间戳精确匹配,匹配不到再作为 0 基序列索引解析;非数字、无法定位、或 from 新于 to 均 → 400。
响应:200 OK
{
"name": "example",
"from": { "ts": 1755153600, "proxies": 40, "...": "..." },
"to": { "ts": 1755160000, "proxies": 42, "...": "..." },
"proxies_delta": 2
}
快照只持久化聚合约(时间戳/节点数/流量元数据),不含逐节点明细,因此没有节点级 added/removed 分解——需要时用 CLI 的 thurgio diff nodes 对比原始订阅源。
延迟测试
GET /app/read/latency?name={name}&timeout_ms={ms}&retry={n}
对单个 provider 的全部已解析代理做 TCP 连接延迟测试。结果按次计算、永不缓存(延迟是时效性数据)。代理列表来自已解析的代理库(与渲染缓存同源),不重新解析原始内容。
查询参数:
| 参数 | 类型 | 缺省 | 描述 |
|---|---|---|---|
name | string | — | 必填——provider 名 |
timeout_ms | integer | 3000 | 单次探测超时(钳制到 [100, 10000]) |
retry | integer | 1 | 每个代理重试次数(钳制到 [1, 3]) |
并发度取 setting.concurrency_limit。
响应:200 OK(结果按延迟升序,不可达代理排在最后)
{
"name": "example",
"count": 2,
"results": [
{ "proxy_name": "HK-01", "alive": true, "latency_ms": 45 },
{ "proxy_name": "US-01", "alive": false, "latency_ms": null }
]
}
UDP 能力
GET /app/read/udp?name={name}
逐代理报告 UDP 转发生效标志。这是订阅元数据的静态能力报告(非实时探测):标志在解析期应用全局 setting.udp 策略后得出——源条目省略 udp 字段但策略为 Try 时可能报 true。
查询参数:name(必填,provider 名)。
响应:200 OK(按 provider 内顺序)
{
"name": "example",
"count": 2,
"results": [
{ "proxy_name": "HK-01", "udp": true },
{ "proxy_name": "US-01", "udp": false }
]
}
入口落地检测
GET /app/read/geo?name={name}&timeout_ms={ms}
把每个已解析代理的 server 主机做 DNS 解析,并经 ip-api 批量 GeoIP 查询国家/地区。
查询参数:
| 参数 | 类型 | 缺省 | 描述 |
|---|---|---|---|
name | string | — | 必填——provider 名 |
timeout_ms | integer | 5000 | 单主机解析超时(钳制到 [100, 10000]) |
并发解析上限 8。单主机失败不传播错误——对应条目字段置 null。
响应:200 OK(按 provider 内顺序,重复 server 保留)
{
"name": "example",
"count": 2,
"results": [
{ "server": "hk.example.com", "ip": "103.1.2.3", "country": "Hong Kong", "country_code": "HK" },
{ "server": "us.example.com", "ip": null, "country": null, "country_code": null }
]
}
流媒体解锁探测
GET /app/read/unlock?services={csv}&timeout_ms={ms}&proxy={url}
探测流媒体服务在当前出口网络的可用性与地区。
查询参数:
| 参数 | 类型 | 缺省 | 描述 |
|---|---|---|---|
services | csv | 全部三项 | 要探测的服务:netflix、youtube、disney(别名 disneyplus);未知值 → 400 |
timeout_ms | integer | 5000 | 单服务探测超时(钳制到 [100, 10000]) |
proxy | URL | 无 | 本次请求使用的一次性上游代理(http(s):// 或 socks5://),非法格式 → 400 |
出口语义:探测的是 thurgio 进程自身的出口网络(或
proxy=指定的上游)——不是逐节点的出口。逐节点测试需要内嵌代理核心,超出 v1 范围。并发探测上限 3。
响应:200 OK(按请求顺序)
{
"results": [
{ "service": "netflix", "available": true, "region": "JP", "latency_ms": 230 },
{ "service": "youtube", "available": false, "region": null, "latency_ms": 180 },
{ "service": "disney", "available": false, "region": null, "latency_ms": null, "error": "timeout" }
]
}
被拦截的 HTTP 状态归类为“不可用“而非错误;只有网络错误/超时才填 error 字段。
管理端点(仅 Admin 令牌)
以下端点仅接受 Admin 作用域令牌。
设置参数
GET /app/admin/p
通过查询字符串设置渲染参数,与内存中的配置参数深度合并(点分键如 dns.enabled 会写入嵌套结构)。
查询参数:任意键值对——每个都成为一个渲染参数。
响应:空内容,200 OK。
设置提供者源 URL
GET /app/admin/s
动态更新提供者的源 URL。不使用查询参数——依赖于请求头。
必需请求头:
| 请求头 | 值 |
|---|---|
prov | 要更新的提供者名称 |
url | 提供者的新源 URL |
更新后会自动重置该提供者的熔断器并触发一次强制更新。
响应:200 OK
{ "status": "ok", "provider": "<name>", "source": "<url>" }
响应 400 Bad Request(缺少 prov 或 url 头);404 Not Found(提供者不存在)。
构建并写入输出
GET /app/admin/write
完整构建流程:更新所有提供者,渲染所有 provider/snippet,并将输出文件写入磁盘。
响应:空内容,200 OK(失败时返回错误 JSON)。
强制刷新
GET /app/admin/refresh
强制更新所有提供者和 snippet。与 /app/admin/write 不同,此端点仅执行更新步骤,不写入输出。
响应:空内容,200 OK(失败时返回错误 JSON)。
重新加载模板引擎
GET /app/admin/reload
重新加载 Tera 模板引擎,拾取磁盘上模板文件的所有变更。
响应:空内容,200 OK。
持久化到数据库
GET /app/admin/save
将所有当前 provider/snippet 响应持久化到数据库(未 fetch 过的 item 跳过,避免覆盖 last-known-good 缓存行)。
响应:空内容,200 OK。
设置开发模式
GET /app/admin/dev?value={value}
启用或禁用开发模式(每次渲染前重新加载模板)。
查询参数:
| 参数 | 类型 | 描述 |
|---|---|---|
value | boolean | 必填——true 或 false(缺失或非法值 → 400) |
响应:设置后的当前开发模式值的字符串——"true" 或 "false"。
创建临时 Provider/Snippet
GET /app/admin/adhoc
创建一次性 provider/snippet(不写入配置文件),立即渲染返回;同名临时资源可经 /app/read/l、/app/read/r 后续引用,且对配置中的同名正式项呈遮蔽效果。缓存持久化到 DB,重启后命中缓存即恢复;按 adhoc.max_items/adhoc.ttl_seconds 自动清理。
查询参数:
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
type | string | 是 | provider / snippet |
url | string | 是 | 数据源 URL |
name | string | 否 | 名称,缺省自动生成 adhoc-<8 位 hex> |
format | string | 否 | clash/meta/v2rayn/sip002/surge/auto,默认 auto(仅 provider 生效) |
tpl | string | 否 | 模板名;缺省回退 setting.adhoc.tpl.provider/tpl.snippet |
pre_rename / filter / rename | string | 否 | 逗号分隔的规则名,按顺序应用(未知规则名静默忽略) |
force | boolean | 否 | true 忽略内存 + DB 缓存强制重新获取 |
响应:渲染后的内容,内容类型 text/plain,并携带 Content-Disposition(d 参数)与抓取响应的 subscription-userinfo 头。
备份配置到 GitHub Gist
GET /app/admin/gist?token={gh_pat}&description={text}
把进程工作目录(即 -r/--root 切换后的目录)下的声明配置 YAML 备份为 GitHub 私密 Gist。
查询参数:
| 参数 | 类型 | 描述 |
|---|---|---|
token | string | 可选——GitHub PAT;缺省时依次回退 config params 的 gist.token、环境变量 GITHUB_TOKEN |
description | string | 可选——Gist 描述,默认 thurgio config backup |
备份文件清单:thurgio.yaml / provider.yaml / params.yaml / snippet.yaml / filter.yaml / rename.yaml / ruleset.yaml 中实际存在的文件;一个都不存在 → 400。token 三个来源全空 → 400。v1 无状态,不持久化任何备份记录。
响应:200 OK
{
"id": "abc123...",
"html_url": "https://gist.github.com/xxx/abc123",
"files": ["thurgio.yaml", "provider.yaml"]
}
上游失败 / 非 2xx / 错误信封 → 502 并携带上游消息。
短链接生成
GET /app/admin/shorten?url={long}&service={name}
经配置的短链接服务缩短 URL。服务模板取自 params:?service= 命中 shortlink.<service> 键时用之,否则用 shortlink.default;模板必须包含 {url} 占位符(点路径深合并产生的嵌套 mapping 形态同样接受)。内置 TinyURL 预设。
# params.yaml 示例
shortlink:
default: "https://tinyurl.com/api-create.php?url={url}"
查询参数:
| 参数 | 类型 | 描述 |
|---|---|---|
url | string | 必填——长链接,必须以 http:// / https:// 开头(否则 → 400) |
service | string | 可选——params 中的服务模板名 |
响应:200 OK
{ "short": "https://tinyurl.com/2p2xxxxx", "long": "https://example.com/very-long-url" }
未配置服务模板 → 400;上游请求失败或响应中提取不到短链 → 502。
查询参数语义
渲染端点(l/r/p/ui/c)的查询参数:
token用于认证,不会进入渲染参数。- 其余参数的值按 YAML 解析(
true→ 布尔、9090→ 数字、其他 → 字符串),作为hp注入模板(请求参数面,与配置参数面p分离)。 - 键中包含
.的参数(如dns.enabled=true)会深度合并进嵌套结构(hp.dns.enabled)。 - 单次请求参数超过 100 个会被拒绝(参数整体忽略并告警)。
d参数额外触发Content-Disposition: attachment; filename="<d>"响应头(用于订阅下载场景)。
响应状态码
| 状态码 | 含义 |
|---|---|
200 OK | 成功 |
400 Bad Request | 缺少必需请求头 / POST 缺少 Content-Type / 登录缺少 token |
401 Unauthorized | 令牌无效或缺失(或作用域不足) |
404 Not Found | 资源不存在(provider、snippet 等) |
413 Payload Too Large | 请求体超过 10 MB 限制 |
415 Unsupported Media Type | POST/PUT/PATCH 的 Content-Type 不受支持 |
429 Too Many Requests | 超过速率限制或并发限制(带 Retry-After 头) |
500 Internal Server Error | 服务器内部错误 |
503 Service Unavailable | 数据库未就绪(仅就绪探针) |
504 Gateway Timeout | 请求处理超过 9 秒 |