# 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.1 统一响应信封](#11-统一响应信封) - [1.2 认证](#12-认证) - [1.3 公共请求头](#13-公共请求头) - [1.4 错误码表](#14-错误码表) - [2. Admin API(端口 5310)](#2-admin-api-端口-5310) - [2.1 认证 /api/auth/*](#21-认证-apiauth) - [2.2 仪表盘 /api/dashboard](#22-仪表盘-apidashboard) - [2.3 Profile 管理 /api/profiles](#23-profile-管理-apiprofiles) - [2.4 浏览器操作中继(浏览器/VNC/打码)](#24-浏览器操作中继) - [2.5 Cookie 管理(admin 本地 Jar)](#25-cookie-管理admin-本地-jar) - [2.6 代理管理 /api/proxies](#26-代理管理-apiproxies) - [2.7 节点管理 /api/nodes](#27-节点管理-apinodes) - [2.8 节点代码包与自更新](#28-节点代码包与自更新) - [2.9 日志 /api/logs](#29-日志-apilogs) - [2.10 系统设置 /api/settings](#210-系统设置-apisettings) - [2.11 事件上报 /api/events](#211-事件上报-apievents) - [3. Node API(端口 3100)](#3-node-api-端口-3100) - [3.1 Profile](#31-profile) - [3.2 Lease(租约)](#32-lease租约) - [3.3 浏览器生命周期](#33-浏览器生命周期) - [3.4 浏览器操作(Action + SSE)](#34-浏览器操作action--sse) - [3.5 HTTP 请求执行(fetch / browse / browser-fetch)](#35-http-请求执行) - [3.6 Cookie(node 运行时)](#36-cookienode-运行时) - [3.7 缓存管理](#37-缓存管理) - [3.8 Schema 与 Execution](#38-schema-与-execution) - [3.9 reCAPTCHA 打码](#39-recaptcha-打码) - [3.10 VNC 与直连](#310-vnc-与直连) - [3.11 系统 /health /ready /metrics 等](#311-系统) - [4. 附录](#4-附录) - [4.1 Browser Action 类型一览](#41-browser-action-类型一览) - [4.2 SSE 事件格式](#42-sse-事件格式) - [4.3 典型调用流程](#43-典型调用流程) --- # 1. 通用约定 ## 1.1 统一响应信封 所有 JSON 接口(除 `/metrics`、图片、SSE、脚本下载外)使用统一信封: 成功: ```json { "success": true, "data": { ... } } ``` 失败: ```json { "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 `) | | `GET /node/install`、`GET /node/package` | query 中的节点密钥 `?key=` | | `GET /node/version` | 公开 | ### Node API - 配置了环境变量 `NODE_KEY` 后,所有路由要求携带: - `x-node-key: `,或 - `Authorization: Bearer ` - 未配置 `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 — 登录 ```json // 请求 { "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 — 当前登录状态 ```json { "authenticated": true, "username": "admin" } // 未登录也返回 200:{ "authenticated": false, "username": null } ``` ## 2.2 仪表盘 `/api/dashboard` ### GET /api/dashboard — 概览数据一次返回 ```json // 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` ```json // 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 ```json // 请求(全部可选) { "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 — 详情 ```json // 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 浏览器操作中继](#24-浏览器操作中继)。) ### POST /api/profiles/:id/fetch — 通过 Profile 运行时执行请求(转发到节点) ```json // 请求 { "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)。 ```json // data(RunResult,归一化后) { "ok": true, "status": 200, "headers": {}, "bodyText": "...", "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 均不出服务端。 ```json // 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 形状: ```json { "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 — 创建 ```json { "name": "p1", "host": "10.0.0.10", "port": 1080, "type": "socks5", "username": "", "password": "" } // type 仅支持 socks5 | http | https,缺省 socks5 → 201 ``` ### POST /api/proxies/import — 批量导入 ```json { "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 归属) ```json // 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`): ```json { "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 — 预置节点(生成/轮换通信密钥) ```json // 请求 { "name": "node-2" } // data { "name": "node-2", "key": "pmk_...", "installCmd": "wget -qO- https:///node/install?key=..&name=node-2 | bash" } ``` ### POST /api/nodes/register — 节点安装完成后自注册(免会话) ```json // 请求 { "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 — 节点心跳上报(注册 + 指标更新) ```json // 请求 { "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 — 编辑节点 ```json { "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 — 批量触发节点自更新 ```json // 请求 { "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 侧节点代码版本(公开) ```json { "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` ```json // 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 — 当前设置 ```json // data { "username": "admin", "apiToken": "smax_...", "settings": { "theme": "system", "defaultRuntime": "fetch", "autoTestProxy": true, "defaultTimeout": 30000, "systemName": "SerpMax Admin" } } ``` ### PUT /api/settings — 更新(白名单键) ```json { "theme": "dark", "defaultRuntime": "browser", "autoTestProxy": false, "defaultTimeout": 60000, "systemName": "..." } // → data { changed: n };无可更新项 → 400 ``` ### POST /api/settings/password — 修改管理员密码 ```json { "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 或指纹数字(自动解析)。 ```json // 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](#12-认证)。`data` 字段以下均指成功信封中的内容。 ## 3.1 Profile ### POST /profiles — 创建(支持 Idempotency-Key 幂等) ```json // 请求(全部可选;对象字段只校验"必须是对象",结构由指纹导入约定决定) { "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 概要) ```json // 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 — 获取 ```json // 请求 { "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 — 续租 ```json // 请求 { "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`。 ```json // 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:` 短期接管(120s),完成即释放。响应同 `start`。 ### GET /profiles/:id/browser/status — 状态 ```json // 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 — 执行动作批次(按序,任一失败即停) ```json // 请求 { "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](#41-browser-action-类型一览))。 ```json // data { "actionId": "act_...", "results": [ /* 每个动作的产出,按序 */ ], "clipboardText": "..." } ``` - 浏览器未运行(如空闲自动关闭后)自动重启 —— "操作即连接"。 - Lease:已持有则复用;无 Lease 时短期接管(`admin:action:`,批次期间持有,完成即释放)。 - 每个动作的进度经 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 可复用浏览器焐热的同一条出站连接,连接层指纹与浏览器一致。 ```json // 请求 { "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;未运行按需启动(复用真实指纹/代理)。两种模式: ```json // 请求 { "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 — 浏览器渲染一次目标页面(同步返回) ```json // 请求 { "url": "https://example.com", "timeoutMs": 30000 } // timeoutMs 1000~120000 // data { "ok": true, "status": 200, "headers": {}, "bodyText": "...", "truncated": false, "timingMs": 900, "runtime": "chromium", "captcha": { "type": "recaptcha", "kind": "...", "website": "...", "url": "..." } } ``` - Lease:已持有则复用;否则临时获取(`admin:browse:`),结束释放,并新开页渲染后关闭。 - 检出验证码时发布 `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 同步黑白名单 ```json // 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 — 磁盘用量 ```json // data { "profileId": "...", "totalBytes": 10485760, "cacheBytes": 8388608, "storageBytes": 2097152, "downloadsBytes": 0 } ``` ### POST /profiles/:id/cache/clear — 清理 ```json // 请求 { "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 ```json // 请求 { "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 粘贴 ```json // 请求 { "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](#14-错误码表)。 ## 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":"","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 典型调用流程 **自动化消费方(节点直连):** ```bash 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: " -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/ -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:///profiles/<指纹>/vnc/ws?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`。