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。
2.5 Cookie 管理(admin 本地 Jar)
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·PUT /profiles/:id/cookie-filter — Cookie 同步黑白名单
// 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。