Files
SerpMax/docs/API.md
T

41 KiB

SerpMax API 文档

版本:2026-09-24(依据源码 admin/src/server.ts 与 node/src/api/ 整理) 架构:admin(后台,默认端口 5310)与 node(Profile Manager 节点,默认端口 3100,容器宿主映射常见为 3106)。 admin 的浏览器操作类接口大多是中继:admin 收到请求后按 profile.node → nodes.base_url 转发到对应节点,并自动附带 admin API Token 与节点密钥。


目录


1. 通用约定

1.1 统一响应信封

所有 JSON 接口(除 /metrics、图片、SSE、脚本下载外)使用统一信封:

成功:

{ "success": true, "data": { ... } }

失败:

{
  "success": false,
  "error": {
    "code": "PROFILE_NOT_FOUND",
    "message": "Profile 不存在",
    "requestId": "req_xxxxxxxxxxxx",
    "details": {}
  }
}
  • 每个响应都带 x-request-id 响应头;请求可自带 x-request-id 头,否则自动生成。
  • admin 的部分错误信封为 { success:false, error:{ code, message } }(无 requestId 字段),语义一致。

1.2 认证

Admin API

方式 说明
会话 Cookie POST /api/auth/login 后下发 smax_session(HttpOnly,默认 7 天)
API Token Authorization: Bearer smax_xxx...(在 设置页 或 GET /api/settings 获取,可轮换)

免会话的例外端点:

端点 凭证
POST /api/nodes/register 无需鉴权(key 即身份,须与 provision 下发的密钥一致)
POST /api/nodes/heartbeat 会话/API Token,或该节点自身的通信密钥(x-node-key / Bearer <NODE_KEY>)
GET /node/install、GET /node/package query 中的节点密钥 ?key=
GET /node/version 公开

Node API

  • 配置了环境变量 NODE_KEY 后,所有路由要求携带:
    • x-node-key: <NODE_KEY>,或
    • Authorization: Bearer <NODE_KEY>
  • 未配置 NODE_KEY 时全部放行(本机/内网模式)。
  • 例外(在节点密钥之外额外支持一次性直连票据,供前端跨源直连):
    • POST /profiles/:id/browser/actions、GET /profiles/:id/browser/events(SSE)、GET /profiles/:id/vnc/ws(WebSocket)
    • 票据来源:POST /profiles/:id/direct-ticket(仅持节点密钥者可调)→ { ticket, expiresInMs },绑定 Profile、5 分钟有效;actions 用后即焚,events/VNC 在 TTL 内可复用。携带方式:?ticket= 或 x-direct-ticket 头。

1.3 公共请求头

头 说明
x-request-id 可选。请求追踪 ID,缺省自动生成,原样回传于响应头与错误信封
idempotency-key 可选。POST /profiles(node)创建 Profile 幂等键
x-lease-id 可选。node 浏览器操作 / schema run 时声明 Lease 持有者(也可在 body 传 leaseId)

跨源直连:node 对所有接口返回 CORS 头(access-control-allow-origin: *),OPTIONS 预检统一应答 204。

1.4 错误码表

node 侧规范错误码(node/src/types/common.ts,括号内为默认 HTTP 状态):

类别 错误码
Profile PROFILE_NOT_FOUND(404)、PROFILE_BUSY(409)、PROFILE_INVALID(400)、PROFILE_CREATING(409)、PROFILE_DELETING(409)、PROFILE_DISK_LIMIT(413)
Lease LEASE_NOT_FOUND(404)、LEASE_EXPIRED(410)、LEASE_CONFLICT(409)
Browser BROWSER_NOT_RUNNING(409)、BROWSER_START_FAILED(500)、BROWSER_CRASHED(500)、BROWSER_TIMEOUT(504)、PROFILE_NOT_RUNNING(409)、BROWSER_SESSION_NOT_FOUND(409)
Action INVALID_ACTION(400)、INVALID_ACTION_PARAMETER(400)、INVALID_SELECTOR(400)、ELEMENT_NOT_FOUND(404)、ELEMENT_NOT_VISIBLE(400)、ELEMENT_NOT_INTERACTABLE(400)、NAVIGATION_FAILED(502)、ACTION_TIMEOUT(504)、SCREENSHOT_FAILED(500)
Schema SCHEMA_NOT_FOUND(404)、SCHEMA_INVALID(400)、SCHEMA_VERSION_NOT_FOUND(404)、SCHEMA_EXECUTION_FAILED(500)、SCHEMA_TIMEOUT(504)
Fetch FETCH_FAILED(502)、FETCH_TIMEOUT(504)、PROXY_FAILED(502)
Cache CACHE_CLEAR_FAILED(500)
通用 VALIDATION_FAILED(400)、UNAUTHORIZED(401)、NOT_FOUND(404)、CONFLICT(409)、INTERNAL_ERROR(500)

admin 侧常用错误码:UNAUTHORIZED(401)、INVALID_CREDENTIALS(401)、VALIDATION_FAILED(400)、PROFILE_NOT_FOUND(404)、FINGERPRINT_CONFLICT(409)、PROXY_NOT_FOUND(404)、COOKIE_NOT_FOUND(404)、NODE_NOT_FOUND(404)、NODE_NOT_AVAILABLE(503)、NODE_RELAY_FAILED(502)、DIRECT_UNAVAILABLE(502/503)、PACKAGE_FAILED(500)。


2. Admin API(端口 5310)

2.1 认证 /api/auth

POST /api/auth/login — 登录

// 请求
{ "username": "admin", "password": "******" }   // username 缺省为 admin
// 200 → data
{ "username": "admin" }
// 失败 401 INVALID_CREDENTIALS

成功时 Set-Cookie: smax_session=...; HttpOnly; SameSite=Lax。

POST /api/auth/logout — 登出 → data: null,清除 Cookie

GET /api/auth/me — 当前登录状态

{ "authenticated": true, "username": "admin" }
// 未登录也返回 200:{ "authenticated": false, "username": null }

2.2 仪表盘 /api/dashboard

GET /api/dashboard — 概览数据一次返回

// data
{
  "statistics": {
    "totalProfiles": 10, "runningProfiles": 3, "errorProfiles": 0,
    "totalProxies": 8, "captcha": 12, "recaptcha": 5, "requests": 1200
  },
  "activity": [ { "id": "12345678", "name": "...", "status": "running",
      "browserType": "browser", "proxyName": "...", "totalRequests": 100,
      "lastUsedAt": "...", "updatedAt": "..." } ],
  "captchaSeries": [ /* 近 7 天验证码统计 */ ],
  "requestSeries": [ /* 近 7 天请求统计 */ ],
  "recentLogs": [ /* 最近 12 条日志 */ ]
}

2.3 Profile 管理 /api/profiles

:id 统一接受 Profile UUID 或指纹数字(6~8 位);节点侧运行 ID 一律为指纹数字。

GET /api/profiles — 列表(联表统计)

Query:q(搜索)、status、proxyId、node、sort、order、page、limit

// data
{
  "total": 10, "page": 1, "limit": 20,
  "items": [ {
    "id": "12345678", "name": "...", "fingerprint": "12345678",
    "node": "node-1", "status": "running", "browserType": "browser",
    "browser": { /* 指纹快照 */ }, "tlsPreset": "...",
    "proxyId": "uuid", "proxyName": "...", "proxyStatus": "connected", "proxyType": "socks5",
    "statistics": {
      "totalRequests": 0, "successfulRequests": 0, "failedRequests": 0,
      "captchaCount": 0, "recaptchaCount": 0, "recaptchaSolved": 0, "lastCaptchaAt": null
    },
    "lastUsedAt": null, "createdAt": "2026-09-01T00:00:00.000Z"
  } ]
}

POST /api/profiles — 创建 Profile

// 请求(全部可选)
{
  "name": "my-profile",
  "fingerprint": "12345678",        // 6~8 位数字(100000~99999999),缺省自动生成;冲突 → 409 FINGERPRINT_CONFLICT
  "browserType": "browser",         // 仅支持 "browser"
  "proxyId": "uuid",                // 绑定代理
  "node": "node-1",                 // 指派运行节点(随分配自动注册节点)
  "notes": "备注"
}
// 201 → data:Profile 详情(同 GET /api/profiles/:id 的 profile 部分)

GET /api/profiles/nodes — 已有 Profile 的运行节点列表(去重,用于筛选)→ data: ["node-1", ...]

GET /api/profiles/:id — 详情

// data
{
  /* Profile 详情(指纹、代理、网络、统计等) */
  "cookieCount": 12,
  "captchaEvents": [ { "id": "1", "type": "recaptcha", "website": "...", "url": "...", "createdAt": "...", "metadata": {} } ],
  "logs": [ { "id": "1", "time": "...", "level": "info", "type": "...", "message": "..." } ]
}

PUT /api/profiles/:id — 编辑基础信息

请求体(字段均可选):{ name?, browserType?, proxyId?(null 解绑), node?(空串回退 "local"), notes? } → data: Profile 详情。 变更节点/代理会自动写系统日志,节点随分配自动注册。

DELETE /api/profiles/:id — 删除 → data: null

POST /api/profiles/:id/duplicate — 复制(克隆指纹)

请求体 { name? }(缺省 <原名>-copy)→ 201,data 为新 Profile 详情。

POST /api/profiles/:id/start · /stop — 启动/停止(状态标记 running/stopped)

→ data: Profile 详情。(真实浏览器启停走 2.4 浏览器操作中继。)

POST /api/profiles/:id/fetch — 通过 Profile 运行时执行请求(转发到节点)

// 请求
{
  "runtime": "fetch",        // fetch | browser | page;缺省取 Profile 的 browserType
  "url": "https://example.com",
  "method": "GET",           // GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS
  "headers": {},             // 透传
  "body": "...",             // 透传(字符串)
  "timeoutMs": 30000         // 1000~120000
}

转发规则:runtime=fetch → 节点 POST /profiles/:id/fetch(指纹 UA/头序 + Cookie Jar + impersonate 代理链);browser / page → 节点 POST /profiles/:id/browser/fetch(真实浏览器渲染 / 页面上下文 fetch)。

// data(RunResult,归一化后)
{
  "ok": true, "status": 200, "headers": {}, "bodyText": "<html>...",
  "truncated": false,
  "timingMs": 812, "runtime": "browser",
  "captcha": { "type": "recaptcha", "kind": "cloudflare", "website": "...", "url": "..." }  // 检出时才有
}

bodyText 完整返回;仅当响应体超过节点 FETCH_BODY_LIMIT(默认 5MB,环境变量可调)时被截断,此时 truncated: true(非文本 content-type 时 bodyText 为 base64,bodyBase64 见节点侧对应接口)。响应命中 CF 挑战时节点自动回退浏览器过盾(见 3.5),此时 runtime 可能为 browser-fallback 且 browserSolved: true。

结果自动落库:请求统计、验证码事件。

2.4 浏览器操作中继

以下端点均需 admin 会话/API Token;admin 负责:解析 Profile → 定位节点(nodes.base_url)→ 自动幂等导入 Profile 到节点(按指纹,节点侧缺失时以 admin 声明的指纹/代理创建)→ 附 x-node-key 转发。请求/响应与节点侧同名接口一致,细节见第 3 章对应小节。

Admin 端点 方法 转发到节点 说明
/api/profiles/:id/browser-status GET GET /browser/status 浏览器/VNC 状态
/api/profiles/:id/browser-actions POST POST /browser/actions 执行动作批次(4MB 上限)
/api/profiles/:id/browser-events?quality= GET GET /browser/events SSE 中继(state/screenshot/action/error)
/api/profiles/:id/browser-restart POST POST /browser/restart 重启浏览器
/api/profiles/:id/browser-stop POST POST /browser/stop 关闭浏览器
/api/profiles/:id/browser-window POST POST /browser/window 新建窗口
/api/profiles/:id/cf-solver POST POST /cf-solver 自动 CF 挑战开关,{ enabled: bool }
/api/profiles/:id/vnc/restart POST POST /vnc/restart 重启/恢复 x11vnc
/api/profiles/:id/vnc/paste POST POST /vnc/paste VNC 粘贴,{ text }(1MB 上限)
/api/profiles/:id/vnc/ws GET(WS) WS /vnc/ws noVNC WebSocket 中继(鉴权后 upgrade)
/api/profiles/:id/browser-cookies GET / PUT GET·PUT /cookies 读取/覆盖导入节点浏览器 Cookie(8MB 上限)
/api/profiles/:id/cookie-filter GET / PUT GET·PUT /cookie-filter Cookie 同步黑白名单
/api/profiles/:id/captcha/state GET GET /captcha/state 打码状态
/api/profiles/:id/captcha/events GET GET /captcha/events 打码状态 SSE
/api/profiles/:id/captcha/image GET GET /captcha/image 主图(jpeg 原样透传)
/api/profiles/:id/captcha/tile/:idx GET GET /captcha/tile/:idx 格子图
/api/profiles/:id/captcha/:action GET / POST GET·POST /captcha/:action action ∈ start·adopt·click·verify·reload·action·autosolve·status·detach,未知 action → 404
/api/profiles/:id/direct-session GET POST /direct-ticket 签发直连会话,见下

GET /api/profiles/:id/direct-session — 直连会话

前端 VNC/浏览器操作直连节点的前置:admin 以节点密钥向节点索取一次性票据,密钥与 API Token 均不出服务端。

// data
{ "nodeUrl": "https://node.example.com", "profileId": "12345678", "ticket": "dtk_...", "expiresInMs": 300000 }
  • 节点未配置公开地址(nodes.public_url)→ 503 DIRECT_UNAVAILABLE,前端回退 admin 中继。
  • profileId 须为指纹数字或 UUID,否则 400 VALIDATION_FAILED。

admin 数据库中按 Profile 存储的 Cookie(与节点浏览器同步的"源头")。Cookie 形状:

{ "id": "uuid", "profileId": "uuid", "name": "k", "value": "v", "domain": "example.com",
  "path": "/", "expiresAt": "2026-12-31T00:00:00.000Z", "secure": false, "httpOnly": false,
  "sameSite": "lax", "createdAt": "...", "updatedAt": "..." }
端点 说明
GET /api/profiles/:id/cookies 列出 Cookie → data: Cookie[]
POST /api/profiles/:id/cookies 新增单条:{ name, value, domain, path?, expiresAt?, secure?, httpOnly?, sameSite?(strict|lax|none) } → 201
PUT /api/profiles/:id/cookies 批量导入:数组或 { cookies:[...] }(按 name+domain+path upsert)→ { imported: n }
DELETE /api/profiles/:id/cookies 清空该 Profile → { cleared: n }
PUT /api/cookies/:id 编辑单条(部分更新)
DELETE /api/cookies/:id 删除单条 → data: null

2.6 代理管理 /api/proxies

代理形状(serializeProxy):{ id, name, host, port, type, username, enabled, tags[], status, checkMessage, exitIp, exitCountry, exitCity, timingMs, createdAt, updatedAt }。

GET /api/proxies — 列表

Query:q、tag、country、sort、order → data: Proxy[]

POST /api/proxies — 创建

{ "name": "p1", "host": "10.0.0.10", "port": 1080, "type": "socks5", "username": "", "password": "" }
// type 仅支持 socks5 | http | https,缺省 socks5 → 201

POST /api/proxies/import — 批量导入

{ "text": "socks5://user:pass@1.2.3.4:1080\n5.6.7.8:1080" }
// → data { created, skipped, failed: [ { line, reason } ] }

一行一个,支持 protocol://user:pass@host:port 与 host:port。

GET /api/proxies/:id — 详情

PUT /api/proxies/:id — 更新(部分字段;password: "" 表示清除;tags 为字符串数组)

DELETE /api/proxies/:id — 删除 → data: null

POST /api/proxies/:id/test — 连通性测试(真实走代理出口,geoip 归属)

// data
{ "ok": true, "message": "...", "exitIp": "1.2.3.4", "exitCountry": "US", "exitCity": "...",
  "timingMs": 320, "proxy": { /* 更新后的代理 */ } }

POST /api/proxies/:id/toggle — 启用/禁用切换 → data: Proxy

2.7 节点管理 /api/nodes

节点形状(serializeNode):

{ "name": "node-1", "ip": "1.2.3.4", "country": "US", "status": "online", "cpu": "12%", "memory": "40%",
  "baseUrl": "http://1.2.3.4:3100", "publicUrl": "https://node.example.com", "version": "20260924.1010",
  "maxProfiles": null, "maxMemoryPct": null, "maxCpuPct": null, "hasKey": true,
  "totalProfiles": 5, "runningProfiles": 2, "createdAt": "...", "updatedAt": "..." }

节点不提供手动添加 —— 随 Profile 分配(POST /api/profiles 的 node 字段)或安装自注册自动创建。

GET /api/nodes — 列表 → data: Node[]

POST /api/nodes/provision — 预置节点(生成/轮换通信密钥)

// 请求 { "name": "node-2" }
// data
{ "name": "node-2", "key": "pmk_...", "installCmd": "wget -qO- https://<admin>/node/install?key=..&name=node-2 | bash" }

POST /api/nodes/register — 节点安装完成后自注册(免会话)

// 请求
{ "name": "node-2", "key": "pmk_...", "baseUrl": "http://1.2.3.4:3100", "ip": "1.2.3.4" }
// name 与 key 必填;key 须与 provision 下发的一致 → data: Node

POST /api/nodes/heartbeat — 节点心跳上报(注册 + 指标更新)

// 请求
{ "name": "node-1", "ip": "1.2.3.4", "country": "US", "status": "online",
  "cpu": "12%", "memory": "40%", "baseUrl": "http://1.2.3.4:3100", "version": "20260924.1010" }
  • 认证:admin 会话/API Token,或该节点自己的密钥(x-node-key / Bearer,且须与 name 匹配)。
  • status 仅支持 unknown|online|offline;version 为节点代码版本(v0.4 起落库)。

PUT /api/nodes/:name — 编辑节点

{ "ip": "...", "country": "US", "publicUrl": "node.example.com",   // 无协议自动补 https://;空串清除(回退中继)
  "status": "online", "maxProfiles": 20, "maxMemoryPct": 80, "maxCpuPct": 90 }   // 配置项传 null 清除
// → data: Node

DELETE /api/nodes/:name — 删除(节点上仍有 Profile 时拒绝)→ data: null

POST /api/nodes/update-batch — 批量触发节点自更新

// 请求 { "names": ["node-1", "node-2"] }
// data
{ "results": [ { "name": "node-1", "ok": true, "detail": "已触发" },
               { "name": "node-2", "ok": false, "detail": "节点未注册地址或无密钥" } ] }

逐个串行中继 POST {节点}/system/update(15s 超时);节点后台拉包替换后自行退出,由容器/pm2 重启生效。

2.8 节点代码包与自更新

GET /node/install?key=&name= — 下发一键安装脚本

返回 bash 脚本(text/plain):生成 docker-compose.yml → 拉镜像 → 启动容器 → 健康检查 → 自注册。支持环境变量覆盖:NODE_KEY、NODE_NAME、IMAGE、INSTALL_DIR,参数 -n/--name -k/--key -i/--image -b/--base-url -d/--dir。

GET /node/package?key= — 下载节点代码包

  • 凭节点密钥;tar.gz(src/ + package.json + bun.lock + tsconfig.json + version)。
  • 响应头:x-package-version: <版本>、content-disposition: attachment; filename="serpmax-node-<版本>.tgz"。
  • 无效 key → 401 UNAUTHORIZED。

GET /node/version — admin 侧节点代码版本(公开)

{ "success": true, "data": { "version": "20260924.1010" } }

版本号 = 源码内容指纹,YYYYMMDD.HHMM 日期格式,缓存 30s。节点页用 GET /node/version 与各节点 GET /system/version 比对判断是否需要更新。

2.9 日志 /api/logs

GET /api/logs — 日志列表

Query:q、profileId、proxyId、level、type、from、to、sort、order、page、limit

// data
{ "total": 100, "page": 1, "limit": 20, "items": [ { "id": "1", "time": "...", "level": "info",
    "type": "profile.started", "message": "...", "profileId": "uuid", "proxyId": null, "metadata": {} } ] }

GET /api/logs/filters — 现存的级别与类型(筛选下拉)→ data: { levels: [...], types: [...] }

2.10 系统设置 /api/settings

GET /api/settings — 当前设置

// data
{ "username": "admin", "apiToken": "smax_...",
  "settings": { "theme": "system", "defaultRuntime": "fetch", "autoTestProxy": true,
                "defaultTimeout": 30000, "systemName": "SerpMax Admin" } }

PUT /api/settings — 更新(白名单键)

{ "theme": "dark", "defaultRuntime": "browser", "autoTestProxy": false, "defaultTimeout": 60000, "systemName": "..." }
// → data { changed: n };无可更新项 → 400

POST /api/settings/password — 修改管理员密码

{ "currentPassword": "...", "newPassword": "..." }   // 新密码 ≥6 位;当前密码错 → 401 INVALID_CREDENTIALS

POST /api/settings/api-token/rotate — 重置 API Token → data: { apiToken: "smax_..." }(旧 Token 立即失效)

2.11 事件上报 /api/events

节点(或任意持 Token 方)向 admin 上报事件;profileId 可为 UUID 或指纹数字(自动解析)。

// POST /api/events
{
  "event": "captcha.detected",        // 见下表
  "profileId": "12345678",            // 按事件类型必填
  "proxyId": "uuid",
  "type": "recaptcha", "kind": "cloudflare", "website": "...", "url": "...",
  "level": "info", "message": "...", "status": 200, "ok": true,
  "metadata": {}
}
// data 按类别:{ "recorded": "captcha" | "captcha-solved" | "browser" | "proxy" | "request" | "log" }
event 副作用
captcha.detected / recaptcha.detected / challenge.detected 记验证码事件 + 统计(type=recaptcha 判定:type 字段或事件名)
captcha.solved / recaptcha.solved recaptcha 解题成功计数
browser.started / browser.stopped / browser.crashed Profile 状态 → running / stopped / error
proxy.connected / proxy.failed 代理检测状态(必须带 proxyId)
request.completed 请求统计(ok 优先,缺省按 status 2xx/3xx 判定)
其他 仅按 level(debug/info/success/warn/error)记日志

3. Node API(端口 3100)

Profile Manager 节点:负责指纹浏览器(Chromium/CloakBrowser)生命周期、Lease、fetch/render、打码、VNC。 鉴权见 1.2。data 字段以下均指成功信封中的内容。

3.1 Profile

POST /profiles — 创建(支持 Idempotency-Key 幂等)

// 请求(全部可选;对象字段只校验"必须是对象",结构由指纹导入约定决定)
{
  "id": "12345678",              // 外部 ID(字母数字与 -_,≤64 位);admin 导入时用指纹数字
  "name": "my-profile",
  "fingerprint": { "import": { /* admin BrowserFingerprint:UA/画布/字体等,导入身份以声明值为准 */ } },
  "network": { "tls": { "preset": "chrome-146" }, "proxy": { /* ... */ } },
  "proxy": { "id": "...", "protocol": "socks5", "host": "1.2.3.4", "port": 1080, "username": "", "password": "" },
  "browser": { /* 指纹快照(同 fingerprint.import) */ },
  "metadata": {}
}
// 201 → data:Profile 对象

Profile 状态机:READY → IN_USE → READY,→ DELETING,ERROR →(隐式恢复)READY。

GET /profiles — 列表 → data: { profiles: [...], total: n }

GET /profiles/:id — 详情(含当前 Lease 概要)

// data
{ "id": "...", "name": "...", "state": "READY", "updatedAt": "...", /* ... */
  "lease": { "leaseId": "...", "consumerId": "...", "expiresAt": "..." } }   // 有 Lease 时

DELETE /profiles/:id?force=true|false — 删除

流程:停浏览器 → 清 Runtime → 删数据 → 删 Profile。force=1/true 强制(如忽略 Lease);→ data: { deleted: true, profileId }。

3.2 Lease(租约)

同一 Profile 同时只允许一个 Consumer;浏览器操作要求持有 Lease(部分接口无 Lease 时会短期自动接管)。

POST /profiles/:id/lease — 获取

// 请求
{ "consumerId": "my-worker-1", "ttlMs": 60000 }   // ttlMs ≥1000
// 201 → data
{ "leaseId": "ls_...", "profileId": "12345678", "expiresAt": "2026-09-24T10:00:00.000Z" }
  • ⚠️ 此端点特例:参数校验失败与「正在删除」时 HTTP 状态码为 400/409,但信封仍是 success:true,错误在 data 内 —— data: { error: "VALIDATION_FAILED", message: "ttlMs 必须 ≥1000" } 或 data: { error: "PROFILE_DELETING" }。
  • ERROR 状态自动隐式恢复为 READY 后发放。

POST /profiles/:id/lease/:leaseId/heartbeat — 续租

// 请求 { "ttlMs": 60000 }   // 可选,重新计时
// data { "leaseId": "...", "profileId": "...", "expiresAt": "...", "lastHeartbeat": "..." }

DELETE /profiles/:id/lease/:leaseId — 释放 → data: { released: true, profileId, leaseId }(Profile 回 READY,浏览器可能仍在运行)

3.3 浏览器生命周期

POST /profiles/:id/browser/start — 启动浏览器(需 Lease)

请求头 x-lease-id(或 body leaseId)须匹配当前 Lease,否则 409 LEASE_CONFLICT。

// data
{ "profileId": "12345678", "browserId": "...", "reused": false,
  "cdp": { "endpoint": "http://127.0.0.1:9222", "wsEndpoint": "ws://...", "port": 9222 },
  "processId": 1234 }

POST /profiles/:id/browser/stop — 停止(不要求 Lease;验收流程 = 先释放 Lease 再 stop)→ data: { profileId, status: "STOPPED" }

POST /profiles/:id/browser/restart — 重启

有 Lease 时校验持有者;无 Lease 时以 admin:restart:<requestId> 短期接管(120s),完成即释放。响应同 start。

GET /profiles/:id/browser/status — 状态

// data
{ "profileId": "...", "status": "RUNNING",            // RUNNING | STOPPED
  "browserId": "...", "cdpEndpoint": "http://...", "processId": 1234, "startedAt": "...",
  "vncPort": 6100, "vncDisplay": ":100",              // VNC 启用时
  "vncAvailable": true,
  "idleCloseInMs": 118000 }                            // 空闲自动关闭剩余毫秒;未启用为 null

POST /profiles/:id/browser/window — 新建空白窗口(浏览器须运行中)→ data: { url: "about:blank", windows: 3 }

GET /profiles/:id/browser/cdp — CDP 端点 → data: { profileId, cdp: { endpoint, wsEndpoint, port } };未运行 → 409 BROWSER_NOT_RUNNING

GET·POST /profiles/:id/cf-solver — 自动 CF 挑战开关

  • GET → data: { enabled: true }(缺省启用,随节点进程保留)
  • POST { "enabled": false } → data: { enabled: false }

3.4 浏览器操作(Action + SSE)

设计:HTTP 负责控制、SSE 负责状态、截图负责视觉反馈;协议细节见 node/docs/Browser-Control.md。

POST /profiles/:id/browser/actions — 执行动作批次(按序,任一失败即停)

// 请求
{
  "actions": [
    { "type": "goto", "url": "https://www.google.com/recaptcha/api2/demo?hl=en" },
    { "type": "fill", "selector": "#email", "text": "a@b.c" },
    { "type": "click", "selector": "button.submit" },        // 或 { "x": 100, "y": 200 } 坐标点击
    { "type": "press", "key": "Control+c" },
    { "type": "wait", "ms": 1000 }                            // 或 { "selector": "...", "ms": 5000 }
  ],
  "screenshotQuality": 30,   // 1-100,默认 30
  "timeout": 30000           // 单动作超时,默认 30000,上限 120000
}

支持 16 种 action:goto · reload · back · forward · click · dblclick · hover · fill · type · press · select · check · uncheck · scroll · scrollIntoView · wait(单次最多 30 个,详见 附录 4.1)。

// data
{ "actionId": "act_...", "results": [ /* 每个动作的产出,按序 */ ], "clipboardText": "..." }
  • 浏览器未运行(如空闲自动关闭后)自动重启 —— "操作即连接"。
  • Lease:已持有则复用;无 Lease 时短期接管(admin:action:<requestId>,批次期间持有,完成即释放)。
  • 每个动作的进度经 SSE action 事件推送({ actionId, status: running|completed|failed })。

GET /profiles/:id/browser/events?quality=30 — SSE 事件流

  • 鉴权:节点密钥或直连票据(EventSource 无法自定义头,票据在 TTL 内可复用)。
  • 事件类型:state(url/title/loading/viewport)、screenshot(jpeg base64,动态 FPS 调度)、action(动作进度)、error(错误)、browser({ status:"STOPPED", reason:"browser-closed" },浏览器关闭时终态)。
  • 15s : ping 保活;连接期间自动持有 Lease 并心跳,断开释放。
  • 浏览器随需启动;quality 控制截图质量。

3.5 HTTP 请求执行

POST /profiles/:id/fetch — Profile 上下文 HTTP 请求(不走浏览器)

指纹 UA/头序 + Cookie Jar + impersonate-proxy(→socks5)链路。适配器两级:优先 go-fetch(Go 原生客户端,GO_FETCH_BIN 或节点根 bin/go-fetch;原生 CONNECT 隧道与 MITM 完全兼容),回落系统 curl。MITM 出站连接按 (上游, 域名) 池化复用(空闲 120s/寿命 10min,借出前 PING 探活),一次性 fetch 可复用浏览器焐热的同一条出站连接,连接层指纹与浏览器一致。

// 请求
{ "url": "https://httpbin.org/get", "method": "GET", "headers": { "x-demo": "1" },
  "body": "...", "timeoutMs": 30000, "browserFallback": true, "rawEncoding": false }   // timeoutMs 100~300000
// data:RunResult 形状(ok/status/headers/bodyText/timingMs/runtime:"fetch" 等)
// 响应体超过 FETCH_BODY_LIMIT(默认 5MB)时截断并置 truncated:true

rawEncoding 压缩直通(默认 false;v0.42.0 新增,serp-api 使用):请求强制 accept-encoding: gzip, go-fetch 以 --keep-encoding 透传上游原始 gzip 字节——响应 body 为 base64、bodyBase64:true、 bodyEncoding:"gzip",全链(Google→go-fetch→node→调用方)不解压不重压缩;上游未压缩(identity)时仍按文本返回。 仅 impersonate(go-fetch/curl)链路支持,其余链路自动降级为解压文本;此模式下不做 CAPTCHA 检测与 CF 浏览器回退。

CF 挑战自动回退(默认开启,仅 GET;browserFallback:false 关闭):响应被 Cloudflare/Turnstile 挑战拦截时,节点自动借同一 Profile 的浏览器过盾(CF 自动解题钩子生效)→ 吸收过盾 Cookie 后重试一次纯 HTTP;若站点免挑战放行浏览器、从不发 clearance(wethrift 类),则直接返回浏览器渲染内容,此时 runtime:"browser-fallback"、browserSolved:true、status:200。

POST /profiles/:id/browser/fetch — 浏览器运行时请求

已运行直接 goto;未运行按需启动(复用真实指纹/代理)。两种模式:

// 请求
{
  "url": "https://example.com/api/data",
  "runtime": "page",          // 缺省 = 整页导航(browser 模式)
  "method": "GET", "headers": {}, "body": "...",   // 仅 page 模式使用
  "timeoutMs": 45000          // 钳制 5000~120000
}
  • runtime:"page":导航到目标源(等 CF 解题放行)后在页面 JS 里执行 fetch,复用浏览器原生 TLS/连接池/Cookie/cf_clearance —— 适合 API 类请求。
  • 默认(整页导航):返回渲染后 HTML + title。
  • 响应含 restarted: true|false(本次是否触发了浏览器启动)、finalUrl、timingMs。

POST /profiles/:id/browse — 浏览器渲染一次目标页面(同步返回)

// 请求 { "url": "https://example.com", "timeoutMs": 30000 }   // timeoutMs 1000~120000
// data
{ "ok": true, "status": 200, "headers": {}, "bodyText": "<html>...", "truncated": false,
  "timingMs": 900, "runtime": "chromium",
  "captcha": { "type": "recaptcha", "kind": "...", "website": "...", "url": "..." } }
  • Lease:已持有则复用;否则临时获取(admin:browse:<requestId>),结束释放,并新开页渲染后关闭。
  • 检出验证码时发布 captcha.detected / challenge.detected / blocked.detected 事件。
  • 超时 → 504 FETCH_TIMEOUT;证书/代理错误 → 502 PROXY_FAILED。

3.6 Cookie(node 运行时)

操作浏览器中的 Cookie(运行中经 Playwright Context),并与 fetch 链路的 Cookie Jar 双向同步;浏览器未运行时仅操作 Jar。形状:{ id, profileId, name, value, domain, path, expiresAt, secure, httpOnly, sameSite }。

端点 说明
GET /profiles/:id/cookies 浏览器运行中 → context.cookies()(过滤同步黑名单);未运行 → Jar
PUT /profiles/:id/cookies 覆盖导入:数组或 {cookies:[...]};运行中 addCookies + Jar,未运行仅 Jar → { imported, browser, deferred }(deferred:true 表示浏览器侧留待下次启动)
DELETE /profiles/:id/cookies 浏览器 clearCookies + 清 Jar → { cleared: true }

expiresAt 兼容 ISO 字符串与秒/毫秒时间戳;sameSite ∈ strict|lax|none。

// GET → data { "whitelist": [], "blacklist": ["ads.example.com"] }   // 空 = 不限
// PUT 请求同形状 → data { whitelist, blacklist, pruned: 3 }   // 立即剔除 Jar 中不允许同步的存量(浏览器 USER_DATA 不动)

只影响"浏览器 ↔ Jar / admin 视图"的同步方向,浏览器本地 Cookie 不变。

3.7 缓存管理

GET /profiles/:id/cache — 磁盘用量

// data
{ "profileId": "...", "totalBytes": 10485760, "cacheBytes": 8388608, "storageBytes": 2097152, "downloadsBytes": 0 }

POST /profiles/:id/cache/clear — 清理

// 请求 { "mode": "soft" }   // soft=清缓存目录(cache);hard=整目录重置(含 storage,浏览器须停止)
// soft → data { profileId, mode: "soft", bytesFreed: 8388608 }
// hard → data { profileId, mode: "hard", bytesFreed: null }
// 其他值 → 400 VALIDATION_FAILED

3.8 Schema 与 Execution

Schema = 声明式自动化流程(版本化,禁止覆盖只能递增)。

POST /schemas — 创建新版本 → 201,data:Schema 对象

GET /schemas/:id — 读取

  • ?version=N 指定版本(缺省最新);?versions=1 → data: { id, versions: [...] }

DELETE /schemas/:id — 删除(全部版本;运行中的版本阻止删除)→ data: { deleted: true, schemaId }

POST /profiles/:id/schema/:schemaId/run — 执行 Schema

// 请求
{ "version": 3, "variables": { "keyword": "test" }, "leaseId": "ls_..." }   // 均可选;leaseId 也可用 x-lease-id 头
// 202 → data
{ "executionId": "exec_...", "profileId": "...", "schemaId": "...", "version": 3, "status": "RUNNING" }

Schema 内 captcha.autoSolve 声明会自动触发 reCAPTCHA 看门狗注入。

GET /executions/:id — 查询执行记录(状态/步骤结果)

POST /executions/:id/cancel — 取消 → data: { executionId, cancelRequested: true|false }

3.9 reCAPTCHA 打码

打码会话由显式端点(答题面板 / autosolve)创建;state 是只读端点,不会触发注入。

端点 方法 说明
/profiles/:id/captcha/state GET 组合状态:手动 mapper 状态 + 自动求解器摘要;未连接会话 → { status:"absent", message, solver }
/profiles/:id/captcha/events GET SSE:状态推送(连接即创建会话,15s ping)
/profiles/:id/captcha/image GET 主图 jpeg(无图 → 404 no image)
/profiles/:id/captcha/tile/:idx GET 第 idx 格子图 jpeg
/profiles/:id/captcha/start POST 点击 checkbox 拉起挑战
/profiles/:id/captcha/adopt POST 采纳页面上已打开的挑战
/profiles/:id/captcha/click POST { "index": 3 } 点格子,或 { "nx": 0.5, "ny": 0.5 } 归一化坐标
/profiles/:id/captcha/verify POST 提交验证
/profiles/:id/captcha/reload POST 换一题
/profiles/:id/captcha/action POST { "kind": "next" | "skip" | "verify" }(缺省 verify)
/profiles/:id/captcha/autosolve POST 触发自动求解(立即返回,结果轮询 status);页面无挑战 → { started:false, reason:"no-challenge" };求解中幂等忽略
/profiles/:id/captcha/status GET 自动求解器状态:{ solving, result, solvedToken }
/profiles/:id/captcha/detach POST 丢弃会话 → { disposed: true }

3.10 VNC 与直连

WS /profiles/:id/vnc/ws — noVNC WebSocket 桥(→ 本机 x11vnc TCP)

  • 握手即升级;鉴权:节点密钥或一次性直连票据(?ticket=)。
  • Query cfAuto=1|0:连接时设置「自动 CF 挑战」开关(缺省不动)。
  • 浏览器未运行或 VNC 未启用 → 404 信封(升级前)。
  • 客户端真实操作(RFB KeyEvent / 带按键 PointerEvent / ClientCutText)会重置浏览器空闲计时。

POST /profiles/:id/direct-ticket — 签发直连票据(仅节点密钥持有者)

→ data: { ticket: "dtk_...", expiresInMs: 300000 }。票据绑定 Profile、一次性(actions)短时可复用(events/VNC)、5 分钟过期。

POST /profiles/:id/vnc/paste — VNC 粘贴

// 请求 { "text": "hello" }
// 纯 X 路径:xsel 写 CLIPBOARD + x11vnc 注入真实 Ctrl+V,落点为 VNC 当前焦点
// data { "pasted": 5, "mode": "x11" }

VNC 未启用 → 409 BROWSER_NOT_RUNNING;缺 xsel → 500 INTERNAL_ERROR。

POST /profiles/:id/vnc/restart — VNC 重启/恢复

x11vnc 重启(色深切换清残留);实例丢失时自动整组重建(浏览器不动)。 → data: { restarted: true, recovered: false, port: 6100 };无法恢复 → 409 BROWSER_NOT_RUNNING。

3.11 系统

端点 方法 说明 / data
/health GET { status:"ok", uptimeMs }
/ready GET 就绪检查:{ ready, checks: { profileCache, runtimeManager, chromiumBinary, recoveryDone } };未就绪整体 503
/metrics GET Prometheus 文本(text/plain; version=0.0.4),非 JSON 信封
/events?limit=&profileId= GET 最近事件环形缓冲:{ events: [...], total }(调试)
/browsers GET { running: n } 当前运行浏览器数(调试)
/node/info GET 节点自述(与心跳同构):{ name, ip, country, status:"online", version, admin:{enabled,url,eventForward,heartbeatInterval}, profiles:{total,running}, browsersRunning }
/system/version GET { version: "20260924.1010" }(30s 缓存)
/system/update POST 自更新:立即 { started:true, from:"<旧版本>" };后台拉包(admin /node/package)→ 替换 /app → bun install → process.exit(0) → 容器 restart: unless-stopped 载入新代码。未配置 ADMIN_URL → 400 { started:false, reason:"ADMIN_URL 未配置" }

4. 附录

4.1 Browser Action 类型一览

单次请求最多 30 个 action;按数组顺序执行,任一失败即停止后续。前置校验快速失败:

type 必填/常用参数 说明
goto url(http/https) 导航
reload — 刷新
back / forward — 历史前进/后退
click selector 或 x+y(视口 CSS 像素) 点击
dblclick selector 双击
hover selector 悬停
fill selector、text 清空并填入
type selector、text 逐键输入
press key(如 Enter、Control+c) 按键;Control+c 的剪贴板结果经 clipboardText 返回
select selector、value(s) 下拉选择
check / uncheck selector 勾选/取消
scroll x/y 或 selector 滚动
scrollIntoView selector 滚动到元素
wait ms 或 selector 等待时长/元素出现

请求级参数:screenshotQuality(1-100,默认 30)、timeout(单动作超时 ms,默认 30000,上限 120000)。

错误码:INVALID_ACTION(类型非法/超数)、INVALID_ACTION_PARAMETER(参数缺失)、ELEMENT_NOT_FOUND、ELEMENT_NOT_VISIBLE、ACTION_TIMEOUT 等,见 1.4。

4.2 SSE 事件格式

GET /profiles/:id/browser/events 每条事件:

event: state
data: {"url":"https://...","title":"...","loading":false,"viewport":{"w":1440,"h":900,"dpr":1}}

event: screenshot
data: {"jpeg":"<base64>","w":1440,"h":900}

event: action
data: {"actionId":"...","status":"completed"}

event: browser
data: {"status":"STOPPED","reason":"browser-closed"}
  • 注释行 : ping(15s)保活;空闲超时 120s(服务端)。
  • GET /profiles/:id/captcha/events 同为 SSE,data 为打码状态对象(带 solver 摘要)。

4.3 典型调用流程

自动化消费方(节点直连):

BASE=http://127.0.0.1:3100
K="x-node-key: $NODE_KEY"

# 1. 创建 Profile(幂等)
curl -s -X POST $BASE/profiles -H "$K" -H "idempotency-key: job-42" \
  -H "content-type: application/json" -d '{"id":"12345678","name":"demo"}'

# 2. 取 Lease
curl -s -X POST $BASE/profiles/12345678/lease -H "$K" -H "content-type: application/json" \
  -d '{"consumerId":"worker-1","ttlMs":60000}'
# → data.leaseId

# 3. 启动浏览器(拿 CDP)
curl -s -X POST $BASE/profiles/12345678/browser/start -H "$K" -H "x-lease-id: <leaseId>" -d '{}'

# 4. 执行动作
curl -s -X POST $BASE/profiles/12345678/browser/actions -H "$K" \
  -H "content-type: application/json" \
  -d '{"actions":[{"type":"goto","url":"https://example.com"}],"screenshotQuality":60}'

# 5. 释放 Lease、停浏览器
curl -s -X DELETE $BASE/profiles/12345678/lease/<leaseId> -H "$K"
curl -s -X POST $BASE/profiles/12345678/browser/stop -H "$K"

admin 中继(前端): 登录(POST /api/auth/login)→ GET /api/profiles/:id/direct-session 拿 { nodeUrl, ticket } → 前端直连 wss://<nodeUrl>/profiles/<指纹>/vnc/ws?ticket=<ticket>(或全程走 admin 中继 /api/profiles/:id/vnc/ws、/browser-actions、/browser-events)。

节点部署: admin 节点页 POST /api/nodes/provision → 复制 installCmd 到目标机执行 → 脚本自动 docker compose 启动 + POST /api/nodes/register → 之后节点定时 POST /api/nodes/heartbeat;升级时 admin 调 POST /api/nodes/update-batch。