API — 许可证、访问与证书

通过 API 查询许可证状态、管理身份验证、读取整体系统状态,并下载根证书。

许可证

GET/api/v1/license/status需要鉴权

返回许可证是否处于激活状态,以及您的套餐、端口池权限 (pool) 和 Challenge Breaker 进程上限。此处不返回端口上限:实际生效的上限见 GET /api/v1/status 的 max_ports 字段。

示例 (curl)
curl -H "X-API-Key: YOUR_API_KEY" http://127.0.0.1:8891/api/v1/license/status
响应
{
  "activated": true,
  "email": "demo@blanktrail.pro",
  "plan": "pro",
  "period_end": "2027-02-15T00:00:00Z",
  "pool": true,
  "js_solver_max_procs": 4,
  "js_solver_procs": 4,
  "js_solver_live_procs": 1
}
  • label 与 allowed_domains 两个字段仅在服务(促销)套餐下返回,因此上面的示例中没有它们:label 是套餐名称的后缀,控制面板显示为 “Pro (WB)”;allowed_domains 是该套餐被限制到的域名列表,面板会在套餐名称下方显示“仅限以下域名:…”。
  • 🔴 allowed_domains 是限制而非提示:只要列表非空,端口就只放行这些域名。不带前缀的条目覆盖该域名及其全部子域;以 “=” 开头的条目只精确匹配该名称;裸 IP 地址永远不会匹配。列表为空(字段缺失)即不受限制。
  • 🔴 拒绝来自代理端口本身,而不是这个 API:HTTP CONNECT 与普通 HTTP 收到空响应体的 403 Forbidden——没有 JSON,也没有 error 字段;SOCKS5 收到应答码 0x02 “connection not allowed by ruleset”;在此类套餐下根本不会授予 UDP ASSOCIATE。这与上游代理无关——更换出口没有用,请把地址与列表核对。
  • 对列表之外的主机不会启动 Challenge Breaker。API 接口、端口测试以及控制面板的其余部分照常工作。

身份验证

此处的密码属于控制面板,而非 API:API 请求通过 X-API-Key 头中的密钥进行鉴权,无需会话 Cookie。这些接口服务于界面与初次配置脚本。

POST/api/v1/auth/login无需鉴权

使用密码登录控制面板;设置会话 Cookie。

参数类型必填说明
passwordstring是控制面板密码。
请求体
{ "password": "your-dashboard-password" }
示例 (curl)
curl -X POST -H "Content-Type: application/json" -c cookies.txt \
  -d '{"password":"your-dashboard-password"}' \
  http://127.0.0.1:8891/api/v1/auth/login
响应
{
  "status": "ok"
}
  • 401 “invalid password”;同一地址连续失败后返回 429 “too many attempts — try again later”。
  • 若该构建未配置控制面板登录,返回 501 “auth not configured”。
POST/api/v1/auth/logout需要鉴权

清除控制面板的会话 Cookie。

示例 (curl)
curl -X POST -H "X-API-Key: YOUR_API_KEY" \
  http://127.0.0.1:8891/api/v1/auth/logout
响应
{
  "status": "ok"
}
GET/api/v1/auth/status需要鉴权

登录与密码的状态:是否已登录、是否已设置密码,以及密码是否仍为初始值。

示例 (curl)
curl -H "X-API-Key: YOUR_API_KEY" \
  http://127.0.0.1:8891/api/v1/auth/status
响应
{
  "authenticated": true,
  "password_is_initial": false,
  "password_set": true
}
  • password_is_initial=true 表示密码仍是安装时下发的初始密码;这正是要求更换密码的理由。
POST/api/v1/auth/password需要鉴权

修改控制面板密码。需要提供当前密码。

参数类型必填说明
current_passwordstring是当前密码。🔴 字段名为 current_password,而不是 current。
new_passwordstring是新密码,至少 8 个字符。🔴 字段名为 new_password,而不是 new。
请求体
{ "current_password": "old-password", "new_password": "new-password" }
示例 (curl)
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"current_password":"old-password","new_password":"new-password"}' \
  http://127.0.0.1:8891/api/v1/auth/password
响应
{
  "status": "ok"
}
  • 🔴 使用 current 与 new 作为字段名时,两者都会被解析为空字符串,接口返回 401 “current password is incorrect”——看上去像是密码错了,实则是字段名错了。
  • 新密码过短时返回 400 “password must be at least 8 characters”。
  • 若该构建未配置控制面板登录,返回 501 “auth not configured”。
POST/api/v1/auth/apikey/rotate需要鉴权

签发新的 API 密钥并返回。旧密钥立即失效。

示例 (curl)
curl -X POST -H "X-API-Key: YOUR_API_KEY" \
  http://127.0.0.1:8891/api/v1/auth/apikey/rotate
响应
{
  "api_key": "a1b2c3d4e5f6a7b8c9d0"
}
  • 该请求使用旧密钥发出,而响应携带新密钥——没有其他途径可以获知它。
  • 若无法保存新密钥,返回 500 “could not rotate key”;若未配置登录,返回 501。

系统状态

GET/api/v1/status需要鉴权

返回整体状态:版本、已开启端口数与端口上限(max_ports 为生效上限,即配置上限与许可证上限中的较小值)、身份数量、运行时长等信息。

示例 (curl)
curl -H "X-API-Key: YOUR_API_KEY" http://127.0.0.1:8891/api/v1/status
响应
{
  "version": "1.3.2",
  "commit": "a1b2c3d",
  "commit_time": "2026-09-01T10:00:00Z",
  "open_ports": 2,
  "max_ports": 1000,
  "idle_timeout_seconds": 1800,
  "ports": [
    "…"
  ],
  "profile_count": 143900,
  "profile_counts": {
    "chrome_152+windows": 41000,
    "chrome_152+macos": 22000,
    "firefox_152+windows": 17400,
    "safari_18+ios": 9800
  },
  "uptime": "3d14h22m",
  "domain_routing": {
    "domains": [],
    "proxy": ""
  },
  "domain_rules_count": 3,
  "pack_loaded": true,
  "solver_bundle_state": "ready",
  "solver_bundle_total": 1,
  "vision_bundle_state": "ready",
  "vision_bundle_total": 1,
  "font_bundle_state": "ready",
  "font_bundle_total": 1,
  "build_state": "ok",
  "device_bound": true
}
响应(全新安装:包仍在下载,文件不匹配)
{
  "pack_loaded": true,
  "solver_bundle_state": "downloading",
  "vision_bundle_state": "dormant",
  "font_bundle_state": "failed",
  "font_bundle_reason": "macos: connection refused",
  "build_state": "mismatch",
  "build_reason": "the program file does not match the published build — please reinstall",
  "device_bound": true,
  "device_rebound_at": "2026-09-01T10:00:00Z"
}
  • 第一个示例是成功的情况。solver_bundle_state、vision_bundle_state 和 font_bundle_state 各有四种取值:dormant——尚未开始下载(对 vision 而言这是正常的:模型在首次遇到验证码时才下载)、downloading——正在下载、ready——已安装、failed——下载失败。
  • 🔴 不要把就绪判断写成“等于 ready,其余都是故障”:全新安装首先返回 downloading,这样的脚本无法区分“仍在下载”和“已失败”。
  • 每个状态都有配对的原因字段——solver_bundle_reason、vision_bundle_reason、font_bundle_reason:一段不含机密的简短说明。状态为 ready 或 dormant 时该字段不出现。font_bundle_reason 会列出所有失败的操作系统(macos: connection refused; windows: 404),因为该状态取各操作系统字体池中最差的一个。
  • 🔴 vision_bundle_state 为 ready 但 vision_bundle_reason 非空并不代表正常:包已下载,但内容密钥获取失败(许可证没有解算器权限、令牌过旧、无法连接 Cabinet),此时 reCAPTCHA 会静默地始终无法通过。
  • pack_loaded 为 false 表示未加载真实的指纹配方;pack_reason 会同时返回原因(not yet attempted、无法连接 Cabinet)。false 且原因为空,表示许可证不包含配方包权限。
  • build_state 表示程序文件与授权服务器发布的构建是否一致。取值:ok、mismatch、unregistered。🔴 该字段可能完全不出现在响应中——那表示“尚无信号”,不得当作 ok。为 mismatch 或 unregistered 时,build_reason 会给出可直接展示的说明文本:mismatch——文件被损坏或被篡改,需要重新安装;unregistered——该构建从未由授权服务器发布,重装同一版本无济于事。
  • device_bound 为 false 表示该安装未绑定到其设备密钥。device_rebound_at(最近一次重新绑定的时间)只有在发生过重新绑定时才出现。
GET/api/v1/health无需鉴权

一个始终返回 OK 的简单健康检查。

示例 (curl)
curl http://127.0.0.1:8891/api/v1/health
响应
{
  "status": "ok"
}

无需密钥即可访问的路径恰好有四个:本健康检查、POST /api/v1/auth/login、GET /crl(拉取吊销列表的是操作系统的证书校验栈,而不是人)以及 GET /export/{token}/…(地址中的令牌本身即为访问凭据)。其余一律返回 401。监控系统或容器 shell 用健康检查确认进程存活;它不提供端口或许可的任何状态信息。

根证书(CA)

应用使用自有根证书拆解 TLS,系统必须信任该证书——否则浏览器会在每个站点上显示证书错误。三个接口以三种形式提供该证书,第四个负责安装它。

GET/api/v1/ca需要鉴权

以 PEM 文本形式返回根证书。

示例 (curl)
curl -H "X-API-Key: YOUR_API_KEY" -o blanktrail-ca.pem \
  http://127.0.0.1:8891/api/v1/ca
  • 若未配置证书路径,返回 404 “CA certificate path not configured”;若文件不可读,返回 500 “failed to read CA certificate”。此处不做 PEM 解析——文件按原样返回;“不是 PEM”的拒绝属于需要解析证书的接口,即 /ca.crt 与 /ca.mobileconfig。
GET/api/v1/ca.crt需要鉴权

同一证书的二进制 DER 形式——macOS 与 iOS 的系统安装程序可识别该格式。

示例 (curl)
curl -H "X-API-Key: YOUR_API_KEY" -o blanktrail-ca.crt \
  http://127.0.0.1:8891/api/v1/ca.crt
GET/api/v1/ca.mobileconfig需要鉴权

Apple 的 .mobileconfig 配置文件:在设备上打开后会自行安装该证书。

示例 (curl)
curl -H "X-API-Key: YOUR_API_KEY" -o blanktrail.mobileconfig \
  http://127.0.0.1:8891/api/v1/ca.mobileconfig
  • 在 iPhone 上,仅安装描述文件还不够:还需在“设置 → 通用 → 关于本机 → 证书信任设置”中将该证书标记为受信任。
POST/api/v1/ca/install需要鉴权

把根证书安装到用户的受信任根存储中。

示例 (curl)
curl -X POST -H "X-API-Key: YOUR_API_KEY" \
  http://127.0.0.1:8891/api/v1/ca/install
响应
{
  "status": "installed"
}
  • 仅适用于 Windows:在其他系统上返回 501,需手动下载并添加证书。
  • 安装后必须完全重启浏览器——关闭所有窗口,而不是只开新窗口:它在启动时读取信任存储。
  • 若系统存储拒绝该证书,返回 500 “install failed: …”。

Challenge Breaker

这些接口显示挑战解题器当前的工作情况,以及它被允许占用的机器资源量。任何套餐都可使用:没有 Challenge Breaker 时求解计数保持为零,但仍能看到遇到的验证——由此可了解您的流量遭遇了什么。

GET/api/v1/solver/stats需要鉴权

自启动以来的计数:按验证家族统计的尝试与成功、遇到的验证,以及占用的资源。

示例 (curl)
curl -H "X-API-Key: YOUR_API_KEY" \
  http://127.0.0.1:8891/api/v1/solver/stats
响应
{
  "families": [ { "family": "recaptcha", "attempts": 41, "solved": 38 } ],
  "total_attempts": 41,
  "total_solved": 38,
  "since_start": true,
  "resources": { "live_procs": 2, "busy_procs": 1, "cap": 4,
                 "constrained": false, "reason": "", "last_error": "",
                 "launch_failures": 0, "proc_deaths": 0, "mem_recycles": 0 },
  "seen": { "total": 47, "since_unix_ms": 1757116800000,
            "vendors": [ { "vendor": "recaptcha", "count": 41 },
                         { "vendor": "turnstile", "count": 6 } ] }
}
  • 🔴 在没有 Challenge Breaker 的套餐上,只有 seen 非空:没有求解,但确实遇到了验证。该字段正是为此而设。
GET/api/v1/solver/queue需要鉴权

实时队列:当前等待数与求解中数量、队列上限,以及每个请求一行的明细。

示例 (curl)
curl -H "X-API-Key: YOUR_API_KEY" \
  http://127.0.0.1:8891/api/v1/solver/queue
响应
{
  "queued": 2,
  "running": 1,
  "max_len": 16,
  "items": [ { "port": 20134, "host": "example.com", "vendor": "recaptcha",
               "class": "chrome-152-win", "state": "solving",
               "waiters": 1, "age_ms": 4120 } ]
}
  • 它是独立接口而非 /solver/stats 中的字段:队列的变化速度比计数快几个数量级,面板按自己的节奏轮询它。此处没有队列控制——只读。
PUT/api/v1/solver/procs需要鉴权

设置同时运行的解题进程数量。无需重启即可生效,并可跨重启保留。

参数类型必填说明
procsint是进程数量;0 表示关闭求解。
请求体
{ "procs": 4 }
示例 (curl)
curl -X PUT -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"procs":4}' \
  http://127.0.0.1:8891/api/v1/solver/procs
响应
{
  "js_solver_procs": 4
}
  • 400 “procs must be >= 0”;若请求数量超过许可允许值,返回 409——该上限可在 GET /api/v1/license/status 的响应中以 js_solver_max_procs 查看。
  • 未设置该值时按许可上限执行:无需手动分配进程。

设置与密钥

GET/api/v1/settings/network需要鉴权

网络设置:代理端口是否接受来自局域网的连接、是否要求登录,以及证书吊销列表可从哪个地址访问。

示例 (curl)
curl -H "X-API-Key: YOUR_API_KEY" \
  http://127.0.0.1:8891/api/v1/settings/network
响应
{
  "allow_lan": false,
  "crl_public_host": "",
  "proxy_auth_enabled": true,
  "proxy_auth_user": "proxy"
}
  • 响应中永远不会返回代理密码——它只能被设置。
PUT/api/v1/settings/network需要鉴权

修改网络设置。只需发送要改动的项:请求体中未出现的字段保持原样。

参数类型必填说明
allow_lanbool否允许代理端口接受来自局域网的连接,而不仅限本机。
crl_public_hoststring否从另一台机器访问证书吊销列表所用的地址:host 或 host:port,不含协议与路径。回环地址会被拒绝——必须是本机在外部可达的地址。
proxy_auth_enabledbool否要求代理端口进行用户名与密码验证。
proxy_auth_userstring否代理用户名。
proxy_auth_passstring否代理密码。
请求体
{ "allow_lan": true, "proxy_auth_enabled": true,
  "proxy_auth_user": "proxy", "proxy_auth_pass": "…" }
示例 (curl)
curl -X PUT -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"allow_lan":true}' \
  http://127.0.0.1:8891/api/v1/settings/network
响应
{
  "allow_lan": true,
  "crl_public_host": "",
  "proxy_auth_enabled": true,
  "proxy_auth_user": "proxy"
}
  • 400 “proxy_auth_user and proxy_auth_pass are required to enable proxy auth”:没有用户名与密码就无法开启该验证。
  • crl_public_host 无效时返回 400 并附说明;503 “auth store not configured”。请求体上限为 8 KiB。
GET/api/v1/auth/apikey需要鉴权

返回 API 密钥——即在 X-API-Key 头中传输的那一个。

示例 (curl)
curl -H "X-API-Key: YOUR_API_KEY" \
  http://127.0.0.1:8891/api/v1/auth/apikey
响应
{
  "api_key": "a1b2c3d4e5f6a7b8c9d0"
}
  • 若不存在可取回的密钥副本,响应为空:仅以哈希形式保存的密钥无法显示。
PUT/api/v1/auth/apikey需要鉴权

以自定义的 API 密钥替换自动生成的密钥。旧密钥立即失效。

参数类型必填说明
api_keystring是12–128 个可打印 ASCII 字符且不含空格:该密钥会原样出现在请求头中。
请求体
{ "api_key": "my-own-api-key-2026" }
示例 (curl)
curl -X PUT -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"api_key":"my-own-api-key-2026"}' \
  http://127.0.0.1:8891/api/v1/auth/apikey
响应
{
  "api_key": "my-own-api-key-2026"
}
  • 400 “api key must be 12-128 characters” 或 “api key must be printable ASCII without spaces”。请求体上限为 8 KiB。
GET/api/v1/settings/integration-key需要鉴权

报告是否已保存集成密钥——它用于以集成商身份激活许可。

示例 (curl)
curl -H "X-API-Key: YOUR_API_KEY" \
  http://127.0.0.1:8891/api/v1/settings/integration-key
响应
{
  "integration_key": "",
  "set": false
}
PUT/api/v1/settings/integration-key需要鉴权

保存或清除集成密钥。无需重启即可生效。

参数类型必填说明
integration_keystring是密钥,最长 512 个字符。空值将清除已保存的密钥。
请求体
{ "integration_key": "…" }
示例 (curl)
curl -X PUT -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"integration_key":""}' \
  http://127.0.0.1:8891/api/v1/settings/integration-key
响应
{
  "ok": true
}
  • 400 “integration_key must be at most 512 characters”;503 “auth store not configured”。

通过代码激活

激活通常在控制面板中完成,但也可以通过请求完成——例如用脚本部署机器时。凭据会直接发送到 Cabinet;应用不会保存它们,只保存已签名的许可。

POST/api/v1/license/enroll需要鉴权

使用 blanktrail.com 账户开始激活。

参数类型必填说明
emailstring是账户邮箱。
passwordstring是账户密码。
请求体
{ "email": "you@example.com", "password": "…" }
示例 (curl)
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"…"}' \
  http://127.0.0.1:8891/api/v1/license/enroll
响应
{
  "choose": false,
  "activated": true,
  "plan": "pro"
}
  • 若账户下有多个许可,响应会带 choose=true、许可列表以及一次性票据——由下一个接口完成选择。
  • 400 “email and password are required”;若 Cabinet 不可达,返回 502 且状态包含在响应体中;503 “license manager not configured”。
POST/api/v1/license/enroll/confirm需要鉴权

当账户下有多个许可时,选择其中一个。

参数类型必填说明
ticketstring是上一步响应中的一次性票据。
license_idint是同一响应中所选许可的标识符。
请求体
{ "ticket": "…", "license_id": 42 }
示例 (curl)
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"ticket":"…","license_id":42}' \
  http://127.0.0.1:8891/api/v1/license/enroll/confirm
响应
{
  "activated": true,
  "plan": "pro"
}
  • 400 “ticket and license_id are required”;若 Cabinet 不可达,返回 502。
POST/api/v1/auth/onboard-password需要鉴权

在首屏设置控制面板密码——凭 POST /api/v1/auth/onboard-activate 响应中返回的一次性令牌 bootstrap_token,而非旧密码。

参数类型必填说明
bootstrap_tokenstring是首次运行的一次性令牌,来自 POST /api/v1/auth/onboard-activate 的响应;仅可使用一次,有效期 15 分钟。
new_passwordstring是新密码,至少 8 个字符。
请求体
{ "bootstrap_token": "…", "new_password": "new-password" }
示例 (curl)
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"bootstrap_token":"…","new_password":"new-password"}' \
  http://127.0.0.1:8891/api/v1/auth/onboard-password
响应
{
  "status": "ok"
}
  • 401 “invalid or expired activation token”;若密码短于八个字符,返回 400 并附密码规则;若未配置登录,返回 501。
POST/api/v1/auth/onboard-activate需要鉴权

在同一首屏完成许可激活,无需单独登录控制面板。

参数类型必填说明
emailstring是账户邮箱。
passwordstring是账户密码。
ticketstring否存在多个许可时的选择票据。
license_idint否所选许可的标识符。
请求体
{ "email": "you@example.com", "password": "…" }
示例 (curl)
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"…"}' \
  http://127.0.0.1:8891/api/v1/auth/onboard-activate
响应
{
  "activated": true,
  "plan": "pro",
  "bootstrap_token": "…"
}
  • bootstrap_token 仅在此响应中返回,且仅当 activated 为 true;一次性,15 分钟后过期,请保存并传给 POST /api/v1/auth/onboard-password。没有其他来源——安装程序不会签发它。
  • 400 “email and password are required”;同一地址多次尝试后返回 429;若 Cabinet 不可达,返回 502;503 “license manager not configured”。