隧道管理
本章覆盖 HyperFRP Panel 后端的隧道(proxy / frpc 隧道)管理接口,共 16 个:本人隧道列表、分页搜索、创建、批量删除、批量启停、随机远程端口、详情、更新、删除、启停开关、绑定域名、配置预览、快捷启动命令、单隧道强制下线,以及单隧道 / 合并两种格式的配置文件下载。
鉴权基调:本章全部 16 个接口都需要登录,通过 Authorization: Bearer <token> 请求头携带 JWT(仅支持请求头方式,不接受 URL 参数传递),Bearer 令牌的获取与使用方式详见认证指南。所有路由经过全站 WAF 防护,且逐一挂载 NoCache 缓存中间件(响应携带 Cache-Control: no-store, no-cache, must-revalidate, private,禁止任何缓存)。除两个配置下载端点直接返回二进制附件外,其余接口均返回统一包络 {"code": 0, "message": "...", "data": ...},错误码与 HTTP 状态码对齐;与具体接口无关的通用约定见通用约定。
以下各接口的「错误场景」表只列接口特有分支。所有接口共有的鉴权失败分支不再逐条重复:
| 场景 | HTTP | 说明 |
|---|---|---|
未携带 Authorization 请求头 | 401 | {"code":401,"message":"未提供认证令牌"} |
| JWT 无效或已过期 | 401 | {"code":401,"message":"无效的令牌或令牌已过期"} |
| 服务端会话失效 | 401 | {"code":401,"message":"会话无效或已过期,请重新登录"} |
| 账户已被封禁 | 403 | {"code":403,"message":"您的账户已被封禁","data":{"isBanned":true,"ban_reason":"..."}} |
| 认证版本或账户状态变更 | 401 | {"code":401,"message":"账户状态或认证版本已变更,请重新登录"} |
隧道对象模型
本章接口返回隧道对象时有两种形态,字段以源码 ProxyResponseDTO 与 models.Proxy 的 JSON 标签为准:
- 精简 DTO(下称「隧道 DTO」):仅
GET /api/proxies列表接口使用,隐藏 GORM 模型细节,node为精简节点对象; - 完整模型(下称「完整隧道」):创建、详情、更新、启停、绑定域名、搜索等接口返回,字段更多,
node为预载的节点对象。
隧道 DTO 字段(GET /api/proxies 列表专用)
| 字段 | 类型 | 说明 |
|---|---|---|
id | number | 隧道 ID |
name | string | 隧道名称 |
type | string | 隧道类型,枚举见创建接口 |
nodeId | number | 所属节点 ID |
localIp | string | 本地服务 IP |
localPort | number | 本地服务端口 |
remotePort | number | 远程端口(tcp/udp/stcp/sudp 使用);http/https/tcpmux 恒为 0 |
domain | string | 绑定域名,多个以英文逗号分隔(http/https/tcpmux 使用);其他类型为空串 |
comment | string | 备注 |
status | string | 隧道状态,枚举:pending / stopped / running / error / disabled |
enabledUser | boolean | 用户侧启用开关 |
statusAdmin | boolean | 管理员侧启用开关(为 false 时隧道无法运行) |
adminReason | string | 管理员处置原因(如禁用说明) |
trafficInBytes | number | 累计入站流量(字节) |
trafficOutBytes | number | 累计出站流量(字节) |
useEncryption | boolean | 是否启用传输加密 |
useCompression | boolean | 是否启用传输压缩 |
transportProtocol | string | 传输协议:tcp(默认)/ quic(节点开启 QUIC 时可用) |
proxyProtocolVersion | string | Proxy Protocol 版本:空串(不启用)/ v1 / v2;仅 tcp/udp 支持 |
ccProtectionEnabled | boolean | CC 防护开关 |
bandwidthLimit | string | 带宽上限(如 10MB),未设置为空串 |
maxConnections | number | 最大连接数,0 表示不限制 |
secretConfigured | boolean | 敏感配置(sk 等访问密钥)是否已设置;只回布尔,永不返回明文或密文 |
configurationComplete | boolean | 权威协议配置在最近一次写入时是否完整可消费 |
protocolConfig | object | 不出现 | 解析后的非敏感权威协议配置,结构见创建接口 protocolConfig 说明;未配置时省略 |
createdAt | string | 创建时间(RFC3339) |
updatedAt | string | 更新时间(RFC3339) |
node | object | 不出现 | 精简节点对象,结构见下;节点缺失时省略 |
精简节点对象 node(ProxyNodeDTO)字段:
| 字段 | 类型 | 说明 |
|---|---|---|
id | number | 节点 ID |
name | string | 节点名称 |
host | string | 节点主机地址(域名或 IP) |
port | number | FRPS 主连接端口 |
domain | string | 节点域名 |
location | string | 地理位置描述(如 中国 广东 广州) |
status | string | 节点状态:online / api_only / unreachable / frps_stopped / offline / maintenance |
完整隧道字段(创建 / 详情 / 更新 / 启停 / 绑定域名 / 搜索返回)
在隧道 DTO 字段之外,完整模型还有以下字段;两者重复的字段语义相同,不再重复列出。
| 字段 | 类型 | 说明 |
|---|---|---|
userId | number | 所属用户 ID(即当前登录用户) |
node | object | 不出现 | 嵌套节点对象。创建 / 详情 / 更新响应会预载完整节点模型;启停与绑定域名响应不预载(该键省略);搜索响应预载完整节点模型(含节点运行指标等全部非凭据字段,节点 token 等长期凭据字段从不外泄) |
subDomain | string | 恒为空串(历史兼容虚拟字段) |
locations | string | 恒为空串(路径路由经由 protocolConfig.locations 表达) |
hostHeaderRewrite | string | 恒为空串(经由 protocolConfig.hostHeaderRewrite 表达) |
headers | string | 恒为空串(经由 protocolConfig.headers 表达) |
plugin | string | 恒为空串(HTTPS 证书插件由配置生成器按 pluginCrtPath / pluginKeyPath 固定输出 https2http) |
pluginParams | string | 恒为空串 |
pluginCrtPath | string | HTTPS 插件证书路径(仅 https 类型使用) |
pluginKeyPath | string | HTTPS 插件私钥路径(仅 https 类型使用) |
extraParams | string | 恒为空串(保留字段) |
lastActiveAt | null | 恒为 null(虚拟字段,未启用) |
forceOfflineGeneration | number | 持久强制下线代次:每次强制下线 / 强制注销 / 访问密钥重置时原子递增,FRPS 发现代次变化即关闭运行实例 |
cc_rules | object | 不出现 | CC 防护规则 { "rate": number, "burst": number }(rate 为每秒令牌数,burst 为突发容量);未开启 CC 防护时省略。注意这是对象中唯一使用下划线命名的键 |
protocolConfig | object | 不出现 | 同隧道 DTO;未配置或版本不受支持时省略 |
user | — | 恒不出现(omitempty 且不预载) |
列表与查询
GET /api/proxies
返回当前登录用户的全部隧道(不分页),按创建时间倒序排列。
鉴权:JWT Bearer · 限流:无专用限流(全站 WAF 防护照常生效) · 缓存:NoCache(Cache-Control: no-store, no-cache, must-revalidate, private)
请求示例
curl https://api.hyperfrp.com/api/proxies \
-H "Authorization: Bearer <token>"const res = await fetch('https://api.hyperfrp.com/api/proxies', {
headers: {
'Authorization': 'Bearer <token>',
},
});
const data = await res.json();
console.log(data);import requests
res = requests.get(
'https://api.hyperfrp.com/api/proxies',
headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)package main
import (
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.hyperfrp.com/api/proxies", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
data 为隧道 DTO 数组(可为空数组),字段见「隧道 DTO 字段」总表。
响应示例
{
"code": 0,
"message": "成功",
"data": [
{
"id": 42,
"name": "web-ssh",
"type": "tcp",
"nodeId": 2,
"localIp": "127.0.0.1",
"localPort": 22,
"remotePort": 30001,
"domain": "",
"comment": "内网 SSH",
"status": "running",
"enabledUser": true,
"statusAdmin": true,
"adminReason": "",
"trafficInBytes": 1048576,
"trafficOutBytes": 2097152,
"useEncryption": true,
"useCompression": false,
"transportProtocol": "tcp",
"proxyProtocolVersion": "",
"ccProtectionEnabled": false,
"bandwidthLimit": "",
"maxConnections": 0,
"secretConfigured": false,
"configurationComplete": true,
"createdAt": "2026-09-19T10:00:00+08:00",
"updatedAt": "2026-09-19T12:30:00+08:00",
"node": {
"id": 2,
"name": "华南-广东-01",
"host": "gd01.hyperfrp.com",
"port": 7000,
"domain": "gd01.hyperfrp.com",
"location": "中国 广东 广州",
"status": "online"
}
}
]
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| 数据库查询失败 | 500 | {"code":500,"message":"获取隧道列表失败"} |
GET /api/proxies/search
分页搜索当前用户的隧道,支持关键词、类型、节点、启用状态与运行状态过滤,以及白名单字段排序。响应返回完整隧道模型(非 DTO)。
鉴权:JWT Bearer · 限流:无专用限流(全站 WAF 防护照常生效) · 缓存:NoCache(Cache-Control: no-store, no-cache, must-revalidate, private)
请求参数(Query)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
keyword | string | 否 | 按隧道名称模糊匹配(SQL LIKE %keyword%) |
type | string | 否 | 按类型精确过滤(tcp / udp / http / https / stcp / sudp / xtcp / tcpmux) |
nodeId | number | 否 | 按节点 ID 精确过滤(大于 0 时生效) |
enabled | string | 否 | 按用户侧启用状态过滤:仅字符串 "true" 视为 true,其余任何值(含 "false")均按 false 过滤;留空不过滤 |
status | string | 否 | 按运行状态精确过滤(pending / stopped / running / error / disabled) |
page | number | 否 | 页码,默认 1,上限 10000 |
pageSize | number | 否 | 每页条数,默认 10,上限 100 |
sortBy | string | 否 | 排序字段,白名单:createdAt / updatedAt / name / type / status / enabled / localPort / remotePort;其他值一律回退 createdAt |
sortOrder | string | 否 | ASC(大小写均可)升序,其余一律降序(默认 DESC) |
请求示例
curl "https://api.hyperfrp.com/api/proxies/search?keyword=web&type=tcp&nodeId=2&enabled=true&page=1&pageSize=10&sortBy=updatedAt&sortOrder=DESC" \
-H "Authorization: Bearer <token>"const res = await fetch('https://api.hyperfrp.com/api/proxies/search?keyword=web&type=tcp&nodeId=2&enabled=true&page=1&pageSize=10&sortBy=updatedAt&sortOrder=DESC', {
headers: {
'Authorization': 'Bearer <token>',
},
});
const data = await res.json();
console.log(data);import requests
res = requests.get(
'https://api.hyperfrp.com/api/proxies/search?keyword=web&type=tcp&nodeId=2&enabled=true&page=1&pageSize=10&sortBy=updatedAt&sortOrder=DESC',
headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)package main
import (
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.hyperfrp.com/api/proxies/search?keyword=web&type=tcp&nodeId=2&enabled=true&page=1&pageSize=10&sortBy=updatedAt&sortOrder=DESC", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
proxies | array | 完整隧道模型数组,字段见「完整隧道字段」总表;node 为预载的完整节点对象。注意:搜索结果不会回填 secretConfigured 与 protocolConfig 虚拟字段——secretConfigured 恒为 false、protocolConfig 恒省略,需以详情接口为准 |
pagination.total | number | 过滤后的总条数 |
pagination.page | number | 当前页码(归一化后的实际值) |
pagination.pageSize | number | 每页条数(归一化后的实际值) |
pagination.totalPages | number | 总页数(向上取整) |
响应示例
{
"code": 0,
"message": "成功",
"data": {
"proxies": [
{
"id": 42,
"userId": 1001,
"nodeId": 2,
"node": { "id": 2, "name": "华南-广东-01", "host": "gd01.hyperfrp.com" },
"name": "web-ssh",
"type": "tcp",
"localIp": "127.0.0.1",
"localPort": 22,
"remotePort": 30001,
"domain": "",
"subDomain": "",
"locations": "",
"hostHeaderRewrite": "",
"headers": "",
"plugin": "",
"pluginParams": "",
"pluginCrtPath": "",
"pluginKeyPath": "",
"useEncryption": true,
"useCompression": false,
"transportProtocol": "tcp",
"proxyProtocolVersion": "",
"extraParams": "",
"status": "running",
"enabledUser": true,
"statusAdmin": true,
"adminReason": "",
"trafficInBytes": 1048576,
"trafficOutBytes": 2097152,
"lastActiveAt": null,
"comment": "内网 SSH",
"createdAt": "2026-09-19T10:00:00+08:00",
"updatedAt": "2026-09-19T12:30:00+08:00",
"ccProtectionEnabled": false,
"forceOfflineGeneration": 0,
"configurationComplete": true,
"secretConfigured": false
}
],
"pagination": {
"total": 1,
"page": 1,
"pageSize": 10,
"totalPages": 1
}
}
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| 查询参数绑定失败 | 400 | {"code":400,"message":"无效的查询参数"} |
| 统计总数失败 | 500 | {"code":500,"message":"统计隧道数量失败"} |
| 查询列表失败 | 500 | {"code":500,"message":"搜索隧道失败"} |
创建与批量
POST /api/proxies
创建一条属于当前用户的隧道。服务端在事务内对用户与节点行加锁后完成全部校验并写入,避免并发创建下的端口 / 域名 / 配额竞争。
鉴权:JWT Bearer · 限流:无专用限流(全站 WAF 防护照常生效) · 缓存:NoCache(Cache-Control: no-store, no-cache, must-revalidate, private)
隧道类型枚举(type)
| 值 | 说明 |
|---|---|
tcp | TCP 转发,需 remote_port |
udp | UDP 转发,需 remote_port |
http | HTTP 反向代理,需 domains(按域名路由) |
https | HTTPS 反向代理,需 domains,可选本地证书插件路径 |
stcp | 安全 TCP(secret key 访问),需 remote_port 与 secretConfig.sk |
sudp | 安全 UDP(secret key 访问),需 remote_port 与 secretConfig.sk |
xtcp | P2P 穿透 TCP,需 secretConfig.sk |
tcpmux | TCP 复用(httpconnect 多路复用器),需 domains 与 protocolConfig.multiplexer |
类型值大小写不敏感(服务端统一转小写),其余值一律拒绝。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 是 | 隧道名称,1–100 个字符,仅允许字母、数字、下划线与短横线;同一用户内唯一 |
type | string | 是 | 隧道类型,枚举见上表 |
local_ip | string | 是 | 本地服务 IP(如 127.0.0.1),≤255 字符 |
local_port | number | 是 | 本地服务端口,1–65535 |
remote_port | number | 类型相关 | 远程端口。tcp/udp/stcp/sudp 必填:1–65535、在节点允许的端口范围内且未被该节点其他隧道占用;http/https/tcpmux 忽略(强制置 0) |
domains | string | 类型相关 | 绑定域名。注意是英文逗号分隔的字符串而非数组(如 "a.example.com,b.example.com")。http/https 必填、tcpmux 必填;tcp/udp/stcp/sudp 强制清空。每段域名 trim + 转小写、去重,单段 ≤253 字符,拼接后总长 ≤255 字符;仅允许字母 / 数字 / 连字符 / 点(不支持 * 通配符),拒绝 javascript: / data: / vbscript: / file: 前缀 |
comment | string | 否 | 备注,≤500 字符 |
nodeId | number | 是 | 目标节点 ID(注意此键为小驼峰) |
use_encryption | boolean | 否 | 是否启用传输加密,默认 false |
use_compression | boolean | 否 | 是否启用传输压缩,默认 false |
transport_protocol | string | 否 | 传输协议:空串视为 tcp;仅允许 tcp / quic。quic 要求目标节点已开启 QUIC |
proxy_protocol_version | string | 否 | Proxy Protocol 版本:空串或 disabled 视为不启用;tcp 仅允许 v1 / v2,udp 仅允许 v2,其他类型提交任何非空值均报错 |
plugin_crt_path | string | 否 | HTTPS 插件证书路径,仅 https 类型保留(其他类型强制清空),≤255 字符 |
plugin_key_path | string | 否 | HTTPS 插件私钥路径,约束同上 |
cc_protection_enabled | boolean | 否 | CC 防护开关,默认 false。开启后未显式提交规则时套用默认规则 {"rate":20,"burst":40} |
protocolConfig | object | 否 | 非敏感权威协议配置扩展字段(可选),结构见下 |
secretConfig | object | 类型相关 | 敏感参数配置,结构见下。stcp/sudp/xtcp 必填且 sk 非空(fail-closed);普通类型可选。提交后 AES-GCM 加密落库,任何接口不再返回明文 / 密文 |
protocolConfig(ProxyAuthorizedConfig)扩展字段:表字段(type / remote_port / domains / use_encryption 等)是权威来源,此对象只补充表内不存在的扩展项,全部可选、经规范化后落库——
| 字段 | 类型 | 说明 |
|---|---|---|
customDomains | string[] | 附加路由域名(会 trim / 小写 / 排序 / 去重) |
subdomain | string | 子域名 |
locations | string[] | http/https 路由路径 |
hostHeaderRewrite | string | Host 头重写 |
headers / responseHeaders | object | 附加请求 / 响应头(键做 canonical MIME 规范化,各 ≤20 条,键值不得含控制字符) |
routeByHTTPUser | string | 按用户路由 |
group | string | 负载均衡组名(组密钥走 secretConfig.groupKey) |
multiplexer | string | tcpmux 复用器,仅允许 httpconnect;tcpmux 类型必填 |
transportProtocol | string | 仅允许 tcp / quic |
bandwidthLimit / bandwidthLimitMode | string | 带宽上限及模式(client / server) |
allowUsers | string[] | stcp/sudp/xtcp 允许访问用户,≤50 条 |
pluginCrtPath / pluginKeyPath | string | HTTPS 插件证书 / 私钥路径 |
超限(单字段 >200 字符、配置 JSON >32KB)或含未知字段时整体拒绝。其余未列出的字段(version、remotePort、type 等)由服务端按表字段自动填充,客户端提交值以表字段为准。
secretConfig(ProxySecretConfig,仅出现在请求中):
| 字段 | 类型 | 说明 |
|---|---|---|
sk | string | stcp/sudp/xtcp 访问密钥(必填于这三类),≤128 字符且不得含控制字符 |
groupKey | string | 负载均衡组密钥,可选 |
httpUser / httpPwd | string | http/https 基础认证用户名 / 密码,可选 |
请求示例
curl -X POST https://api.hyperfrp.com/api/proxies \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "web-ssh",
"type": "tcp",
"local_ip": "127.0.0.1",
"local_port": 22,
"remote_port": 30001,
"nodeId": 2,
"use_encryption": true,
"use_compression": false,
"transport_protocol": "tcp",
"comment": "内网 SSH"
}'// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
const res = await fetch('https://api.hyperfrp.com/api/proxies', {
method: 'POST',
headers: {
'Authorization': 'Bearer <token>',
},
});
const data = await res.json();
console.log(data);import requests
# 另一种用法:GET
# 另一种用法:GET
# 另一种用法:GET
# 另一种用法:GET
# 另一种用法:GET
# 另一种用法:GET
# 另一种用法:GET
# 另一种用法:GET
# 另一种用法:GET
# 另一种用法:GET
# 另一种用法:GET
res = requests.post(
'https://api.hyperfrp.com/api/proxies',
headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
package main
import (
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.hyperfrp.com/api/proxies", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}校验流程与错误场景
服务端按以下顺序校验(都在同一事务内,行级锁保护)。注意:普通用户创建入口保持历史兼容,所有业务校验失败(含端口占用、域名冲突、配额已满等冲突类错误)统一返回 HTTP 400 + code: 400,并以 message 区分具体场景;HTTP 409 仅出现在更新 / 绑定域名 / 切换节点的冲突分支。
| 场景 | HTTP | 说明 |
|---|---|---|
| 请求体无法解析为 JSON | 400 | 无效的请求数据 |
| name / type / local_ip / local_port / nodeId 缺失或为零值 | 400 | 请填写所有必填字段:名称、类型、本地IP、本地端口、节点 |
| 名称不符合字符 / 长度规则;local_ip / comment / 证书路径超长 | 400 | 分别提示:隧道名称长度必须在 1-100 个字符之间、隧道名称只允许包含字母、数字、下划线和短横线、本地 IP 不能超过 255 个字符、备注不能超过 500 个字符、HTTPS 插件证书路径不能超过 255 个字符 |
local_port 越界 | 400 | 本地端口必须在 1-65535 之间 |
| 所选节点不存在 | 400 | 所选节点不存在 |
| 节点用户组白名单不含当前用户组 | 400 | 您当前的用户组无权使用节点 [节点名] |
| 中国大陆节点未实名 | 400 | 使用中国大陆节点需要先完成实名认证 |
| 隧道配额已满(用户 / 用户组配额取大者) | 400 | 隧道创建失败:隧道配额已满 (N个),无法创建更多隧道 |
| type 不在枚举内 | 400 | 不支持的隧道类型:<值>。允许的类型为: tcp, udp, http, https, stcp, sudp, xtcp, tcpmux |
节点配置了 allowedProtocols 且不含该类型 | 400 | 节点 [节点名] 不允许创建 <TYPE> 类型隧道 |
节点状态不可用(仅 online / api_only / unreachable 可建) | 400 | 节点 '节点名' 当前不在服务状态,请选择其他节点 |
| 名称与当前用户既有隧道重名 | 400 | 已存在名为 '<名称>' 的隧道,请使用其他名称 |
transport_protocol 非法 | 400 | 传输协议只支持 tcp 或 quic |
quic 但节点未开启 QUIC | 400 | 所选节点未开启 QUIC,无法使用 QUIC 传输协议 |
proxy_protocol_version 与类型不匹配 | 400 | TCP 类型仅支持 proxy protocol v1 或 v2 / UDP 类型仅支持 proxy protocol v2 / 仅 TCP 或 UDP 类型支持 Proxy Protocol |
remote_port 越界 | 400 | 远程端口必须在 1-65535 之间 |
remote_port 不在节点允许的端口范围 | 400 | 远程端口 <N> 不在节点允许的端口范围内 |
remote_port 已被该节点其他隧道占用 | 400 | 远程端口 <N> 在节点 <节点名> 上已被占用,请更换 |
http/https/tcpmux 未提供 domains | 400 | HTTP/HTTPS 类型隧道必须指定绑定域名 / TCPMUX 类型隧道必须指定路由域名 |
| 域名格式无效 / 总长超 255 | 400 | 域名格式无效: <域名> / 自定义域名总长度不能超过 255 个字符 |
| 域名已被同节点其他隧道占用 | 400 | 域名 <域名> 已被节点 [节点名] 上的隧道 [隧道名] 占用 |
tcpmux 未提供 protocolConfig.multiplexer | 400 | TCPMUX 类型隧道必须配置 multiplexer(httpconnect) |
stcp/sudp/xtcp 未提供 secretConfig.sk | 400 | STCP/SUDP/XTCP 类型隧道必须提供 sk 密钥 |
protocolConfig / secretConfig 含未知字段或超限 | 400 | 隧道权威配置格式无效或超出安全限制 / 隧道敏感配置格式无效或超出安全限制 |
| 敏感配置加密器不可用 / 加密失败 | 500 | 隧道敏感配置加密不可用,已拒绝操作 / 隧道敏感配置加密失败 / 创建隧道失败 |
响应字段
HTTP 201。data 为完整隧道模型(含预载 node,user 不返回),字段见「完整隧道字段」总表。新隧道初始态:enabledUser=true、statusAdmin=true、status="stopped",secretConfigured / configurationComplete 按实际配置计算。
响应示例
{
"code": 0,
"message": "创建成功",
"data": {
"id": 42,
"userId": 1001,
"nodeId": 2,
"node": { "id": 2, "name": "华南-广东-01", "host": "gd01.hyperfrp.com", "port": 7000 },
"name": "web-ssh",
"type": "tcp",
"localIp": "127.0.0.1",
"localPort": 22,
"remotePort": 30001,
"domain": "",
"subDomain": "",
"pluginCrtPath": "",
"pluginKeyPath": "",
"useEncryption": true,
"useCompression": false,
"transportProtocol": "tcp",
"proxyProtocolVersion": "",
"extraParams": "",
"status": "stopped",
"enabledUser": true,
"statusAdmin": true,
"adminReason": "",
"trafficInBytes": 0,
"trafficOutBytes": 0,
"lastActiveAt": null,
"comment": "内网 SSH",
"createdAt": "2026-09-19T10:00:00+08:00",
"updatedAt": "2026-09-19T10:00:00+08:00",
"ccProtectionEnabled": false,
"forceOfflineGeneration": 0,
"configurationComplete": true,
"secretConfigured": false
}
}注意事项
domains字段虽然名字是复数,但类型是逗号分隔字符串,不是数组;提交数组会导致绑定失败。- 域名不支持
*通配符(格式正则不允许),泛域名场景请逐个提交具体子域名。 remote_port的节点允许范围判断优先级:节点配置了allowPortRanges(多段)时必须在任一段内;否则按allowedPortsStart–allowedPortsEnd(两者均 >0 时);两者都未配置则不限制范围。随机端口接口(见下)的取值范围只按allowedPortsStart–End计算,两者口径可能不同。secretConfig一经提交即 AES-GCM 加密落库;响应中只有secretConfigured布尔标记,sk 等明文不再可获取,请自行妥善保存。cc_protection_enabled=true且未提交规则时,服务端套用默认规则{"rate":20,"burst":40}并以cc_rules字段回显。
POST /api/proxies/batch-delete
批量删除当前用户名下的隧道。非本人或不存在的 ID 会被静默忽略(不报错),返回实际删除数量;仅当一条都删不到时返回 404。
鉴权:JWT Bearer · 限流:无专用限流(全站 WAF 防护照常生效) · 缓存:NoCache(Cache-Control: no-store, no-cache, must-revalidate, private)
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
ids | number[] | 是 | 待删除的隧道 ID 列表,不得为空数组 |
请求示例
curl -X POST https://api.hyperfrp.com/api/proxies/batch-delete \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "ids": [42, 43, 44] }'const res = await fetch('https://api.hyperfrp.com/api/proxies/batch-delete', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer <token>',
},
body: JSON.stringify({ "ids": [42, 43, 44] })
});
const data = await res.json();
console.log(data);import requests
res = requests.post(
'https://api.hyperfrp.com/api/proxies/batch-delete',
headers={'Authorization': 'Bearer <token>'},
json={
'ids': [
42,
43,
44
]
},
)
data = res.json()
print(data)package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
var payload bytes.Buffer
json.NewEncoder(&payload).Encode(map[string]any{
"ids": []any{
42,
43,
44,
},
})
req, _ := http.NewRequest("POST", "https://api.hyperfrp.com/api/proxies/batch-delete", &payload)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
message | string | 成功删除 N 个隧道(N 为实际删除数量,可能小于 ids 长度) |
响应示例
{
"code": 0,
"message": "成功",
"data": { "message": "成功删除 3 个隧道" }
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
ids 缺失或为空数组 | 400 | 请提供有效的隧道ID列表 |
| 列表中的 ID 均不属于当前用户(或不存在) | 404 | 没有找到任何属于您的隧道进行删除,请检查ID是否正确 |
| 数据库删除失败 | 500 | 批量删除隧道失败 |
POST /api/proxies/batch-status
批量启用 / 禁用当前用户名下的隧道(写 enabledUser 开关)。非本人或不存在的 ID 静默忽略;一条都更新不到时返回 404。
鉴权:JWT Bearer · 限流:无专用限流(全站 WAF 防护照常生效) · 缓存:NoCache(Cache-Control: no-store, no-cache, must-revalidate, private)
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
ids | number[] | 是 | 隧道 ID 列表,不得为空数组 |
enable | boolean | 是 | true 批量启用,false 批量禁用 |
请求示例
curl -X POST https://api.hyperfrp.com/api/proxies/batch-status \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "ids": [42, 43], "enable": false }'const res = await fetch('https://api.hyperfrp.com/api/proxies/batch-status', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer <token>',
},
body: JSON.stringify({ "ids": [42, 43], "enable": false })
});
const data = await res.json();
console.log(data);import requests
res = requests.post(
'https://api.hyperfrp.com/api/proxies/batch-status',
headers={'Authorization': 'Bearer <token>'},
json={
'ids': [
42,
43
],
'enable': false
},
)
data = res.json()
print(data)package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
var payload bytes.Buffer
json.NewEncoder(&payload).Encode(map[string]any{
"ids": []any{
42,
43,
},
"enable": false,
})
req, _ := http.NewRequest("POST", "https://api.hyperfrp.com/api/proxies/batch-status", &payload)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
message | string | 成功启用 N 个隧道 或 成功禁用 N 个隧道(N 为实际更新数量) |
响应示例
{
"code": 0,
"message": "成功",
"data": { "message": "成功禁用 2 个隧道" }
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
ids 缺失或为空数组 | 400 | 请提供有效的隧道ID列表 |
| 列表中的 ID 均不属于当前用户(或不存在) | 404 | 没有找到任何属于您的隧道进行更新,请检查ID是否正确 |
| 数据库更新失败 | 500 | 批量更新隧道状态失败 |
注意事项
- 无论启用还是禁用,本接口都会把
status置为stopped(启用后等待 FRPS 上报,回到running需要客户端重新连接)。 - 禁用不会修改
statusAdmin(管理员侧开关);管理员禁用的隧道即使用户启用也无法运行。
端口与详情
GET /api/proxies/random-port/:nodeId
为指定节点随机取一个当前未被占用的远程端口,供创建隧道时预填。端口不会预留,创建时仍会做占用校验。
鉴权:JWT Bearer · 限流:无专用限流(全站 WAF 防护照常生效) · 缓存:NoCache(Cache-Control: no-store, no-cache, must-revalidate, private)
请求参数(Path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
nodeId | number | 是 | 节点 ID |
请求示例
curl https://api.hyperfrp.com/api/proxies/random-port/2 \
-H "Authorization: Bearer <token>"const res = await fetch('https://api.hyperfrp.com/api/proxies/random-port/2', {
headers: {
'Authorization': 'Bearer <token>',
},
});
const data = await res.json();
console.log(data);import requests
res = requests.get(
'https://api.hyperfrp.com/api/proxies/random-port/2',
headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)package main
import (
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.hyperfrp.com/api/proxies/random-port/2", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
port | number | 随机可用端口 |
响应示例
{
"code": 0,
"message": "成功",
"data": { "port": 30017 }
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
nodeId 非数字 | 400 | 无效的节点ID |
| 节点不存在 | 400 | 节点未找到 |
| 节点端口范围配置不正确(起始 ≥ 结束) | 400 | 节点的端口范围配置不正确,起始端口必须小于结束端口 |
| 连续 100 次随机均撞上已占用端口 | 400 | 尝试多次后仍未找到可用端口,请稍后重试或检查节点端口范围 |
注意事项
- 取值范围来自节点的
allowedPortsStart–allowedPortsEnd;未配置(≤0)时分别回退为 10000 与 65535。 - 返回的端口只保证「调用时刻未被占用」,不写入任何预留记录;高并发下仍可能与其他用户创建冲突,以创建接口的校验结果为准。
GET /api/proxies/:id
获取单条隧道详情。仅能查看本人隧道;隧道不存在或属于他人时一律按 404 处理(权限隐藏,不区分两种情况)。
鉴权:JWT Bearer · 限流:无专用限流(全站 WAF 防护照常生效) · 缓存:NoCache(Cache-Control: no-store, no-cache, must-revalidate, private)
请求参数(Path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | number | 是 | 隧道 ID |
请求示例
curl https://api.hyperfrp.com/api/proxies/42 \
-H "Authorization: Bearer <token>"const res = await fetch('https://api.hyperfrp.com/api/proxies/42', {
headers: {
'Authorization': 'Bearer <token>',
},
});
const data = await res.json();
console.log(data);import requests
res = requests.get(
'https://api.hyperfrp.com/api/proxies/42',
headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)package main
import (
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.hyperfrp.com/api/proxies/42", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
data 为完整隧道模型(含预载 node,且已回填 secretConfigured / protocolConfig 虚拟字段),字段见「完整隧道字段」总表。
响应示例
{
"code": 0,
"message": "成功",
"data": {
"id": 42,
"userId": 1001,
"nodeId": 2,
"node": { "id": 2, "name": "华南-广东-01", "host": "gd01.hyperfrp.com", "port": 7000, "domain": "gd01.hyperfrp.com", "location": "中国 广东 广州", "status": "online" },
"name": "web-ssh",
"type": "tcp",
"localIp": "127.0.0.1",
"localPort": 22,
"remotePort": 30001,
"domain": "",
"useEncryption": true,
"useCompression": false,
"transportProtocol": "tcp",
"proxyProtocolVersion": "",
"status": "running",
"enabledUser": true,
"statusAdmin": true,
"adminReason": "",
"trafficInBytes": 1048576,
"trafficOutBytes": 2097152,
"comment": "内网 SSH",
"ccProtectionEnabled": false,
"forceOfflineGeneration": 0,
"configurationComplete": true,
"secretConfigured": false,
"createdAt": "2026-09-19T10:00:00+08:00",
"updatedAt": "2026-09-19T12:30:00+08:00"
}
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| 隧道不存在或不属于当前用户 | 404 | 隧道未找到或您无权访问 |
| 数据库查询失败 | 500 | 查询隧道失败 |
PUT /api/proxies/:id
更新本人隧道。字符串字段「空串 / 未提交 = 不修改」;布尔字段用指针语义「未提交 = 不修改」;提交 nodeId 且与现值不同时进入「切换节点」分支(此时其余字段全部忽略,只处理换节点)。更新成功后隧道 status 置为 stopped,等待客户端按新配置重连。
鉴权:JWT Bearer · 限流:无专用限流(全站 WAF 防护照常生效) · 缓存:NoCache(Cache-Control: no-store, no-cache, must-revalidate, private)
请求参数(Path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | number | 是 | 隧道 ID |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 否 | 新名称。非空且与现值不同时校验:1–100 字符、仅字母 / 数字 / 下划线 / 短横线,且不得与本用户其他隧道重名 |
type | string | 否 | 切换隧道类型(转小写后校验枚举)。切换到 stcp/sudp/xtcp 时,若原隧道未设置过敏感配置且本次未提交 secretConfig,拒绝;切换到 tcpmux 时必须同时提交 protocolConfig.multiplexer |
local_ip | string | 否 | 非空才更新 |
local_port | number | 否 | 大于 0 才更新 |
remote_port | number | 否 | 仅 tcp/udp/stcp/sudp 生效:大于 0 且与现值不同时做节点内占用校验(冲突返回 409);http/https 忽略;tcpmux 强制置 0 |
domains | string | 否 | 仅 http/https/tcpmux 生效:非空且与现值不同时做域名冲突校验(冲突返回 409) |
use_encryption | boolean | 否 | 布尔指针语义:未提交不修改 |
use_compression | boolean | 否 | 布尔指针语义:未提交不修改 |
transport_protocol | string | 否 | 非空才更新;仅 tcp / quic,且目标节点须开启 QUIC |
proxy_protocol_version | string | 否 | 非空才更新;tcp 仅 v1 / v2,udp 仅 v2,其他类型报错。类型改为非 tcp/udp 时自动清空 |
plugin_crt_path / plugin_key_path | string | 否 | 仅 https 类型生效,非空才更新 |
cc_protection_enabled | boolean | 否 | 布尔指针语义:未提交不修改 |
nodeId | number | 否 | 非零且与现值不同时切换节点(见下) |
protocolConfig | object | 否 | 权威协议配置扩展字段,覆盖语义;未提交时沿用已保存值(不会丢失 locations / headers 等) |
secretConfig | object | 否 | 显式替换敏感配置(结构同创建接口);未提交时保留原值 |
clearSecretConfig | boolean | 否 | true 显式清空敏感配置(优先于 secretConfig) |
切换节点分支(nodeId 非零且变化)的校验顺序:目标节点存在(不存在返回 404)→ 目标节点状态须为 online / api_only / unreachable(否则 400)→ 用户组有权使用目标节点(否则 403)→ 原隧道的远程端口(tcp/udp/stcp/sudp)或域名(http/https/tcpmux)在新节点上不冲突(冲突返回 409)。切换节点时 name / type / 地址端口等其余字段不会被处理。
请求示例
curl -X PUT https://api.hyperfrp.com/api/proxies/42 \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"local_port": 2222,
"remote_port": 30002,
"use_encryption": true
}'// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
const res = await fetch('https://api.hyperfrp.com/api/proxies/42', {
method: 'PUT',
headers: {
'Authorization': 'Bearer <token>',
},
});
const data = await res.json();
console.log(data);import requests
# 另一种用法:GET
# 另一种用法:GET
# 另一种用法:GET
# 另一种用法:GET
res = requests.put(
'https://api.hyperfrp.com/api/proxies/42',
headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
// 另一种用法:GET
package main
import (
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("PUT", "https://api.hyperfrp.com/api/proxies/42", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
data 为完整隧道模型(含预载 node,已回填虚拟字段),字段见「完整隧道字段」总表。
响应示例
{
"code": 0,
"message": "成功",
"data": {
"id": 42,
"name": "web-ssh",
"type": "tcp",
"localIp": "127.0.0.1",
"localPort": 2222,
"remotePort": 30002,
"status": "stopped",
"enabledUser": true,
"statusAdmin": true,
"configurationComplete": true,
"secretConfigured": false,
"updatedAt": "2026-09-19T13:00:00+08:00"
}
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| 请求体无法解析 | 400 | 无效的请求数据 |
| 隧道不存在或不属于当前用户 | 404 | 隧道未找到或您无权访问 |
| 名称 / 类型 / 传输协议 / Proxy Protocol 校验失败 | 400 | 消息同创建接口对应条目 |
| 切换到需要 sk 的类型但无密钥 | 400 | STCP/SUDP/XTCP 类型隧道必须提供 sk 密钥 |
| 切换到 tcpmux 但缺 multiplexer / 路由域名 | 400 | TCPMUX 类型隧道必须配置 multiplexer(httpconnect) / TCPMUX 类型隧道必须指定路由域名 |
| 名称与本用户其他隧道重名 | 409 | 您已存在名为 '<名称>' 的隧道,请使用其他名称 |
| 新远程端口在当前节点被占用 | 409 | 远程端口 <N> 在当前节点上已被隧道 [<名称>] 占用 |
| 新域名被同节点其他隧道占用 | 409 | 服务端返回的冲突描述(域名 <域名> 已被节点 [节点名] 上的隧道 [隧道名] 占用) |
| 目标新节点不存在 | 404 | 目标新节点不存在 |
| 目标新节点状态不可用 | 400 | 新节点 '<名称>' 当前不可用,无法切换 |
| 无权使用目标新节点 | 403 | 您当前的用户组无权使用新节点 [<名称>] |
| 目标新节点上端口 / 域名冲突 | 409 | 远程端口 <N> 在新节点 [<名称>] 上已被隧道 [<名称>] 占用 等 |
| QUIC 但节点未开启 | 400 | 当前节点未开启 QUIC,无法使用 QUIC 传输协议 |
| 已保存的 protocolConfig 损坏 / 版本不受支持 | 400 | 已保存的隧道权威配置无效,请重新提交 protocolConfig |
| 持久化失败(内部错误) | 500 | 更新隧道失败 |
注意事项
- 本接口不支持修改备注
comment:请求体中没有该字段,备注仅在创建时可设置。 secretConfig与clearSecretConfig的组合语义:只提交secretConfig= 替换;只提交clearSecretConfig: true= 清空;两者都不提交 = 保留原值。- 普通字段与敏感密文、权威配置重建在同一事务内原子提交,任一步失败整体回滚,不会出现部分更新。
DELETE /api/proxies/:id
删除本人的一条隧道。隧道不存在或属于他人时按 404 处理(权限隐藏)。
鉴权:JWT Bearer · 限流:无专用限流(全站 WAF 防护照常生效) · 缓存:NoCache(Cache-Control: no-store, no-cache, must-revalidate, private)
请求示例
curl -X DELETE https://api.hyperfrp.com/api/proxies/42 \
-H "Authorization: Bearer <token>"const res = await fetch('https://api.hyperfrp.com/api/proxies/42', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer <token>',
},
});
const data = await res.json();
console.log(data);import requests
res = requests.delete(
'https://api.hyperfrp.com/api/proxies/42',
headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)package main
import (
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://api.hyperfrp.com/api/proxies/42", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
message | string | 固定为 隧道已删除 |
响应示例
{
"code": 0,
"message": "成功",
"data": { "message": "隧道已删除" }
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| 隧道不存在或不属于当前用户 | 404 | 隧道未找到或您无权访问 |
| 数据库删除失败 | 500 | 删除隧道失败 |
状态与域名
PUT /api/proxies/:id/status
切换本人隧道的用户侧启用开关(enabledUser)。禁用时同时把 status 置为 stopped;启用不改变 status(等 FRPS 上报后回到 running)。
鉴权:JWT Bearer · 限流:无专用限流(全站 WAF 防护照常生效) · 缓存:NoCache(Cache-Control: no-store, no-cache, must-revalidate, private)
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
enable | boolean | 是 | true 启用,false 禁用 |
请求示例
curl -X PUT https://api.hyperfrp.com/api/proxies/42/status \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "enable": false }'const res = await fetch('https://api.hyperfrp.com/api/proxies/42/status', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer <token>',
},
body: JSON.stringify({ "enable": false })
});
const data = await res.json();
console.log(data);import requests
res = requests.put(
'https://api.hyperfrp.com/api/proxies/42/status',
headers={'Authorization': 'Bearer <token>'},
json={
'enable': false
},
)
data = res.json()
print(data)package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
var payload bytes.Buffer
json.NewEncoder(&payload).Encode(map[string]any{
"enable": false,
})
req, _ := http.NewRequest("PUT", "https://api.hyperfrp.com/api/proxies/42/status", &payload)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
message | string | 隧道已启用 或 隧道已禁用 |
proxy | object | 更新后的完整隧道模型(字段见「完整隧道字段」总表;不预载 node) |
响应示例
{
"code": 0,
"message": "成功",
"data": {
"message": "隧道已禁用",
"proxy": {
"id": 42,
"name": "web-ssh",
"type": "tcp",
"status": "stopped",
"enabledUser": false,
"statusAdmin": true
}
}
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| 请求体无法解析 | 400 | 无效的请求数据 |
| 隧道不存在或不属于当前用户 | 404 | 隧道未找到或您无权访问 |
| 数据库保存失败 | 500 | 更新隧道状态失败 |
PUT /api/proxies/:id/bind-domain
为本人隧道绑定(更换)路由域名。仅 http / https 类型可用;tcpmux 类型需走更新接口。域名做冲突校验后以事务方式保存并重建权威配置,不会丢失既有扩展字段。
鉴权:JWT Bearer · 限流:无专用限流(全站 WAF 防护照常生效) · 缓存:NoCache(Cache-Control: no-store, no-cache, must-revalidate, private)
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
domains | string | 是 | 新的绑定域名,英文逗号分隔的字符串(格式 / 长度规则同创建接口 domains) |
请求示例
curl -X PUT https://api.hyperfrp.com/api/proxies/43/bind-domain \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "domains": "blog.example.hyperfrp.com,www.example.hyperfrp.com" }'const res = await fetch('https://api.hyperfrp.com/api/proxies/43/bind-domain', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer <token>',
},
body: JSON.stringify({ "domains": "blog.example.hyperfrp.com,www.example.hyperfrp.com" })
});
const data = await res.json();
console.log(data);import requests
res = requests.put(
'https://api.hyperfrp.com/api/proxies/43/bind-domain',
headers={'Authorization': 'Bearer <token>'},
json={
'domains': 'blog.example.hyperfrp.com,www.example.hyperfrp.com'
},
)
data = res.json()
print(data)package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
var payload bytes.Buffer
json.NewEncoder(&payload).Encode(map[string]any{
"domains": "blog.example.hyperfrp.com,www.example.hyperfrp.com",
})
req, _ := http.NewRequest("PUT", "https://api.hyperfrp.com/api/proxies/43/bind-domain", &payload)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
message | string | 固定为 域名绑定成功 |
proxy | object | 更新后的完整隧道模型(字段见「完整隧道字段」总表;不预载 node) |
响应示例
{
"code": 0,
"message": "成功",
"data": {
"message": "域名绑定成功",
"proxy": {
"id": 43,
"name": "web-blog",
"type": "http",
"domain": "blog.example.hyperfrp.com,www.example.hyperfrp.com",
"enabledUser": true,
"statusAdmin": true,
"configurationComplete": true
}
}
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| 请求体无法解析 | 400 | 无效的请求数据 |
| 隧道不存在或不属于当前用户 | 404 | 隧道未找到或您无权访问 |
| 隧道类型不是 http / https | 400 | 只有 HTTP 和 HTTPS 类型的隧道才能绑定域名,当前隧道类型为 <TYPE> |
| 域名已被同节点其他隧道占用 | 409 | 域名 <域名> 已被节点 [节点名] 上的隧道 [隧道名] 占用 |
| 域名格式无效 | 409 | 域名格式无效: <域名>(冲突检查入口以 Conflict=true 回传格式错误) |
| 持久化失败(内部错误) | 500 | 绑定域名失败 / 检查域名冲突失败 |
配置生成与客户端辅助
GET /api/proxies/:id/config-preview
生成本人隧道的 FRPC 客户端配置预览,同时返回 INI(legacy 格式)与 TOML(新格式)两份文本。生成前会做传输协议 fail-closed 校验;隧道名称含配置文件不安全字符时拒绝生成。
鉴权:JWT Bearer · 限流:无专用限流(全站 WAF 防护照常生效) · 缓存:NoCache(Cache-Control: no-store, no-cache, must-revalidate, private)
请求示例
curl https://api.hyperfrp.com/api/proxies/42/config-preview \
-H "Authorization: Bearer <token>"const res = await fetch('https://api.hyperfrp.com/api/proxies/42/config-preview', {
headers: {
'Authorization': 'Bearer <token>',
},
});
const data = await res.json();
console.log(data);import requests
res = requests.get(
'https://api.hyperfrp.com/api/proxies/42/config-preview',
headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)package main
import (
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.hyperfrp.com/api/proxies/42/config-preview", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
ini | string | INI 格式配置全文 |
toml | string | TOML 格式配置全文 |
响应示例
{
"code": 0,
"message": "成功",
"data": {
"ini": "[common]\nserver_addr = gd01.hyperfrp.com\nserver_port = 7000\nuser = \"AKxxxxxxxx\"\ntoken = \"<节点token>\"\n\n[web-ssh]\ntype = tcp\nmeta_tunnel_id = 42\nmeta_user_id = 1001\nlocal_ip = 127.0.0.1\nlocal_port = 22\nremote_port = 30001\nuse_encryption = true\n",
"toml": "serverAddr = \"gd01.hyperfrp.com\"\nserverPort = 7000\nuser = \"AKxxxxxxxx\"\n\n[auth]\nmethod = \"token\"\ntoken = \"<节点token>\"\n\n[[proxies]]\nname = \"web-ssh\"\ntype = \"tcp\"\nmetadatas.tunnel_id = \"42\"\nmetadatas.user_id = \"1001\"\nlocalIP = \"127.0.0.1\"\nlocalPort = 22\nremotePort = 30001\n\n[proxies.transport]\nuseEncryption = true\n"
}
}生成内容结构说明
- INI:
[common]段(server_addr/server_port/user=用户访问密钥 /token=节点 FRPS token;QUIC 时附加transport.protocol = "quic"),随后每个隧道一段[<隧道名>]:type、meta_tunnel_id、meta_user_id,非 https 类型输出local_ip/local_port;tcp/udp 追加remote_port,http/https 追加custom_domains;https 类型改为插件形式(plugin = https2http+plugin_local_addr/plugin_crt_path/plugin_key_path,证书路径留空时输出占位提示文本);按需追加use_encryption/use_compression/proxy_protocol_version。 - TOML:顶层
serverAddr/serverPort/user,[auth](method = "token"+ 节点 token),QUIC 时追加[transport];每个隧道一个[[proxies]]块:name/type/metadatas.tunnel_id/metadatas.user_id/localIP/localPort/remotePort/customDomains(数组);https 类型追加[proxies.plugin];加密 / 压缩 / Proxy Protocol 任一存在时追加[proxies.transport]。 - 合并下载(见下)复用同一套生成器,
[common]/ 顶层只出现一次,其后串联该节点全部已启用隧道。
错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| 隧道不存在或不属于当前用户 | 404 | 隧道未找到或您无权访问 |
| 传输协议非法或节点未开启 QUIC | 400 | 当前节点未开启 QUIC,无法使用 QUIC 传输协议 / 传输协议 "<值>" 不受支持,仅支持 tcp 或 quic |
| 隧道名称含配置文件不安全字符 | 500 | 隧道名称不符合配置格式要求 |
注意事项
- 配置中内嵌用户访问密钥(user)与节点 FRPS token(token),均为敏感凭据:预览文本请勿截图 / 转发到公开场合。
- 隧道名称允许中文与点号(
^[\p{L}\p{N}_.-]+$),但名称超长或含控制字符时生成器拒绝输出,需先改名。
POST /api/proxies/:id/quick-start-command
生成三平台的一键启动命令(使用官方 hyperfrpc 客户端拉起指定隧道)。命令内嵌用户访问密钥与面板 API 基址(取自节点配置并规范化,无尾斜杠);参数统一经单引号包裹,防 shell / PowerShell 注入。
鉴权:JWT Bearer · 限流:无专用限流(全站 WAF 防护照常生效) · 缓存:NoCache(Cache-Control: no-store, no-cache, must-revalidate, private)
请求示例
curl -X POST https://api.hyperfrp.com/api/proxies/42/quick-start-command \
-H "Authorization: Bearer <token>"const res = await fetch('https://api.hyperfrp.com/api/proxies/42/quick-start-command', {
method: 'POST',
headers: {
'Authorization': 'Bearer <token>',
},
});
const data = await res.json();
console.log(data);import requests
res = requests.post(
'https://api.hyperfrp.com/api/proxies/42/quick-start-command',
headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)package main
import (
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.hyperfrp.com/api/proxies/42/quick-start-command", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
success | boolean | 固定 true |
commands.windows | string | Windows(PowerShell)启动命令 |
commands.linux | string | Linux 启动命令 |
commands.macos | string | macOS 启动命令 |
响应示例
{
"code": 0,
"message": "成功",
"data": {
"success": true,
"commands": {
"windows": "hyperfrpc.exe -u 'AKxxxxxxxx' -p 42 --api-url 'https://api.hyperfrp.com/api'",
"linux": "./hyperfrpc -u 'AKxxxxxxxx' -p 42 --api-url 'https://api.hyperfrp.com/api'",
"macos": "./hyperfrpc -u 'AKxxxxxxxx' -p 42 --api-url 'https://api.hyperfrp.com/api'"
}
}
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| 隧道不存在或不属于当前用户 | 404 | 隧道未找到或您无权访问 |
| 用户访问密钥为空 | 500 | 无法获取用户的访问密钥 (Access Key) |
| 隧道关联节点信息丢失 | 500 | 隧道关联的节点信息丢失 |
| 节点面板 API 地址无效 | 400 | 节点面板 API 地址无效 |
注意事项
- 命令内嵌 accessKey(账号级凭据)明文:拿到命令即可用你的身份创建 / 修改隧道,请勿公开分享、截图或提交到代码仓库;泄露后应立即在控制台重置访问密钥(重置会使全部隧道强制下线)。
- 每次调用都会写入审计日志(动作
tunnel-quick-start)。
POST /api/proxies/:id/force-unregister
对本人单条隧道执行强制下线:事务内原子递增持久强制下线代次(forceOfflineGeneration + 1)并把 status 置为 stopped,同时尝试发送 Redis 即时下线信号。代次是下线意图的事实来源——即使即时通知失败,FRPS 也会在下次状态检查时关闭该隧道。
鉴权:JWT Bearer · 限流:无专用限流(全站 WAF 防护照常生效) · 缓存:NoCache(Cache-Control: no-store, no-cache, must-revalidate, private)
请求示例
curl -X POST https://api.hyperfrp.com/api/proxies/42/force-unregister \
-H "Authorization: Bearer <token>"const res = await fetch('https://api.hyperfrp.com/api/proxies/42/force-unregister', {
method: 'POST',
headers: {
'Authorization': 'Bearer <token>',
},
});
const data = await res.json();
console.log(data);import requests
res = requests.post(
'https://api.hyperfrp.com/api/proxies/42/force-unregister',
headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)package main
import (
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.hyperfrp.com/api/proxies/42/force-unregister", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
success | boolean | 固定 true |
message | string | 强制下线指令已发送成功;即时通知失败时追加 ;即时通知失败,已由持久下线记录兜底,将在节点下次状态检查时生效 |
forceOfflineFailed | boolean | true 表示 Redis 即时通知失败(持久代次已生效,仅延迟生效) |
响应示例
{
"code": 0,
"message": "成功",
"data": {
"success": true,
"message": "强制下线指令已发送成功",
"forceOfflineFailed": false
}
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| 隧道不存在或不属于当前用户 | 404 | 隧道未找到或您无权访问 |
| 代次递增 / 状态更新失败 | 500 | 强制下线失败 |
配置下载
两个下载端点直接返回配置文件二进制附件(Content-Type: application/octet-stream + Content-Disposition: attachment),不走统一 JSON 包络;错误时仍返回 JSON 错误体。配置内容结构与预览接口一致(见上文「生成内容结构说明」),其中同样内嵌访问密钥与节点 token,下载后请妥善保管。
GET /api/proxies/config/download/:id/:format
下载本人单条隧道的 FRPC 配置文件。
鉴权:JWT Bearer · 限流:无专用限流(全站 WAF 防护照常生效) · 缓存:NoCache(Cache-Control: no-store, no-cache, must-revalidate, private)
请求参数(Path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | number | 是 | 隧道 ID |
format | string | 是 | 配置格式:ini 或 toml,其他值报错 |
请求示例
curl -o frpc_web-ssh.ini https://api.hyperfrp.com/api/proxies/config/download/42/ini \
-H "Authorization: Bearer <token>"const res = await fetch('https://api.hyperfrp.com/api/proxies/config/download/42/ini', {
headers: {
'Authorization': 'Bearer <token>',
},
});
const blob = await res.blob(); // 浏览器可用 URL.createObjectURL(blob) 触发保存import requests
res = requests.get(
'https://api.hyperfrp.com/api/proxies/config/download/42/ini',
headers={'Authorization': 'Bearer <token>'},
)
with open('frpc_web-ssh.ini', 'wb') as f:
f.write(res.content)package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.hyperfrp.com/api/proxies/config/download/42/ini", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := os.Create('frpc_web-ssh.ini')
defer out.Close()
io.Copy(out, res.Body)
fmt.Println("saved frpc_web-ssh.ini")
}响应说明
成功时返回 200 + 配置文件附件:
Content-Type: application/octet-streamContent-Disposition: attachment; filename=frpc_<隧道名>.ini(或.toml)
错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| 隧道不存在或不属于当前用户 | 404 | 隧道未找到或您无权访问 |
format 不是 ini / toml | 400 | 不支持的配置文件格式 |
| 传输协议非法或节点未开启 QUIC | 400 | 同预览接口 |
| 隧道名称无法安全生成配置 | 500 | 隧道名称不符合配置格式要求 |
GET /api/proxies/config/download/merged/:nodeId/:format
下载本人指定节点下全部已启用隧道(enabledUser=true 且 statusAdmin=true)合并而成的单文件 FRPC 配置,[common] / 顶层段只出现一次,其后串联每条隧道的配置块。
鉴权:JWT Bearer · 限流:无专用限流(全站 WAF 防护照常生效) · 缓存:NoCache(Cache-Control: no-store, no-cache, must-revalidate, private)
请求参数(Path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
nodeId | number | 是 | 节点 ID |
format | string | 是 | 配置格式:ini 或 toml,其他值报错 |
请求示例
curl -o frpc_gd01_merged.toml https://api.hyperfrp.com/api/proxies/config/download/merged/2/toml \
-H "Authorization: Bearer <token>"const res = await fetch('https://api.hyperfrp.com/api/proxies/config/download/merged/2/toml', {
headers: {
'Authorization': 'Bearer <token>',
},
});
const blob = await res.blob(); // 浏览器可用 URL.createObjectURL(blob) 触发保存import requests
res = requests.get(
'https://api.hyperfrp.com/api/proxies/config/download/merged/2/toml',
headers={'Authorization': 'Bearer <token>'},
)
with open('frpc_gd01_merged.toml', 'wb') as f:
f.write(res.content)package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.hyperfrp.com/api/proxies/config/download/merged/2/toml", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := os.Create('frpc_gd01_merged.toml')
defer out.Close()
io.Copy(out, res.Body)
fmt.Println("saved frpc_gd01_merged.toml")
}响应说明
成功时返回 200 + 配置文件附件:
Content-Type: application/octet-streamContent-Disposition: attachment; filename=frpc_<节点名>_merged.ini(或.toml)
错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| 节点不存在 | 404 | 节点未找到 |
该节点下没有已启用的隧道(enabledUser 与 statusAdmin 均为 true 才计入) | 404 | 该节点下没有可用的隧道 |
| 各隧道传输协议不一致 | 400 | 隧道 "<名称>" 的传输协议与合并配置中其他隧道不一致(<A>/<B>),单个客户端只能使用一种协议 |
| 传输协议非法或节点未开启 QUIC | 400 | 当前节点未开启 QUIC,无法使用 QUIC 传输协议 等 |
| 合并配置生成结果为空 | 400 | 合并配置无效,请检查隧道配置 |
| 查询节点 / 隧道列表失败 | 500 | 查询节点失败 / 获取隧道列表失败 |
注意事项
- 单个 frpc 客户端的
transport.protocol是全局的,因此合并配置要求该节点下所有已启用隧道的传输协议完全一致(全部tcp或全部quic),混合即整体拒绝,不做静默降级。 - 管理员禁用(
statusAdmin=false)的隧道不会出现在合并配置中。