01概览
chromebot 为 AI agent 提供可自动启停、抗风控的浏览器实例。每个实例 = 一个固定身份 + 独立指纹 + 持久 profile 卷 + 自带代理,身份跨重启稳定。
两种用法:
- 控制台 UI —— GitHub 登录后在
/app点选创建 / 启动 / 实时取景操作。 - 编程 API —— 用
Authorization: Bearer <token>调 REST,拿到 CDP 端点后用 patchright 经wss://驱动真实 Chrome。
基址
| 用途 | 地址 |
|---|---|
| REST API | https://at.ci/v1/... |
| CDP 网关 | wss://at.ci/cdp/{id}?token=... |
| 控制台 | https://at.ci/app |
约定:id为 32 位十六进制;token为cb_前缀 opaque 串,明文仅签发时返回一次,服务端只存 SHA-256。
02快速开始
1 · 登录拿 token
浏览器打开 https://at.ci 点「用 GitHub 登录」,成功后跳 /app 并种 httponly cookie。右上「API token → 重新生成」复制明文 cb_...(只显示一次,旧 token 立即失效)。
2 · 创建实例
curl -s https://at.ci/v1/instances \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name":"my-account-1","locale":"en-US","tz":"America/New_York"}'
3 · 启动,拿 CDP 端点
curl -s -X POST https://at.ci/v1/instances/$ID/start \
-H "Authorization: Bearer $TOKEN"
# → {"status":"running","cdp_endpoint":"wss://at.ci/cdp/...?token=...","cdp_token":"cb_..."}
cdp_endpoint 已含 token,只返回一次;stop 后失效,running 时再调 start 会轮换。
4 · 驱动
from patchright.async_api import async_playwright
async with async_playwright() as pw:
b = await pw.chromium.connect_over_cdp(CDP_ENDPOINT)
pg = b.contexts[0].pages[0]
await pg.goto("https://abrahamjuliot.github.io/creepjs/")
await b.close() # 只断开 CDP,实例继续运行
5 · 停止 / 删除
POST /stop 保留身份与 profile;DELETE 永久销毁卷,不可恢复。
03鉴权
两条等价路径落到同一用户:
- httponly cookie
cb_token—— 浏览器 UI,GitHub 登录自动种,30 天有效。 - Bearer token —— 编程 API,放
Authorization: Bearer cb_...。
解析顺序:先 Authorization 头,无则回落 cookie。都没有 → 401 missing token;查不到 → 401 invalid token。
每用户同时只有一个有效 token;POST /v1/token/regenerate 签发新明文、旧 token(含旧 cookie)立即失效。服务端只存 sha256(token),丢了再 regenerate。
| token 类型 | 前缀 | 用途 | 有效期 |
|---|---|---|---|
| 账户 API token | cb_ | 调所有 /v1/* | 长期,至下次 regenerate |
| 实例 CDP token | cb_ | 仅连 /cdp/{id} | start 返回一次;stop 即失效;再 start 轮换 |
04API · 账户
/v1/me当前用户与配额 → 200 {login, email, instances_used, instances_max}(email 可能为 null,instances_max 默认 10)。
/v1/token/regenerate无请求体。签发新明文 token,旧的立即失效 → 200 {"token":"cb_..."}(明文仅一次)。未鉴权 → 401。
05API · 实例
两种投影:Summary(id, name, status, platform, locale, tz, proxy_set, pending_restart, updated_at)、Detail(Summary + container_name, cdp_endpoint, fingerprint_committed, mem_rss_bytes, cpu_pct, last_started_at, last_error)。
status ∈ created · starting · running · stopping · stopped · error · deleting。
/v1/instances创建实例(不启动)→ 201 Summary。
| 字段 | 类型 | 说明 |
|---|---|---|
name | string 必填 | 1–128 字符 |
platform | string | 默认 windows(MVP 仅此) |
locale | string? | 如 en-US,留空用 profile 默认 |
tz | string? | IANA,如 America/New_York |
proxy | string? | scheme 须 socks5/socks5h/http/https |
错误:429 配额满、422 校验失败。
/v1/instances列出本人实例 → 200 Summary[]。
/v1/instances/{id}详情 → 200 Detail(running 时附 cdp_endpoint(不含 token)、mem_rss_bytes、cpu_pct)。404 = 不存在或越权(不暴露他人实例)。
/v1/instances/{id}停 + 删容器 + 销毁 profile 卷 + 删记录,不可恢复 → 200 {"status":"deleted"}。
06API · 生命周期 & 代理
/v1/instances/{id}/start从 created/stopped/error 起容器、首启固化身份、签发新 cdp_token 转 running;对已 running 再调幂等,不重启容器、只轮换 cdp_token → 200 {id, status:running, cdp_endpoint, cdp_token}(明文仅一次)。错误:409 cannot start from status=...、500 start failed(转 error)。
/v1/instances/{id}/stop停容器转 stopped、清除 cdp_token(已建 CDP 连接失效),身份 / profile 保留可再 start。对 stopped/created 幂等 → 200 {status:stopped}。
/v1/instances/{id}/proxy请求体 {"proxy":"socks5://user:pass@host:1080"} 或 {"proxy":null} 清除。代理进程级:running 改不热生效,需下次 start(此时 pending_restart=true)。scheme 非法 → 422 → 200 {proxy_set, pending_restart}。
07CDP 驱动 (patchright)
CDP 网关是实例唯一对外暴露点。用 start 返回的 cdp_endpoint(已含 ?token=)直喂 connect_over_cdp,网关校验 token + 确认 running + 双向转发到容器内真实 Chrome。
import asyncio
from patchright.async_api import async_playwright
CDP = "wss://at.ci/cdp/<id>?token=<cdp_token>"
async def main():
async with async_playwright() as pw:
browser = await pw.chromium.connect_over_cdp(CDP)
ctx = browser.contexts[0]
page = ctx.pages[0] if ctx.pages else await ctx.new_page()
await page.goto("https://example.com")
print(await page.title())
await browser.close() # 仅断开 CDP,不停实例
asyncio.run(main())
注意
- 用
connect_over_cdp,不是launch。 - 端点是 browser 级 flat CDP;
browser.close()只断 CDP 不停实例,释放资源调/stop。 - token 失效场景:stop 后 / running 再 start 轮换后,旧端点立即不可连。
WS 关闭码
| 码 | 含义 |
|---|---|
4401 | token 无效 / 越权 |
4409 | 实例非 running |
4404 | 容器无 IP |
4502 | 上游连接失败 |
08代理设置
每实例绑一个独立出口代理,凭据 Fernet 密文落库,API 永不回显明文,日志按 scheme://***:***@host:port 脱敏。
支持:socks5://、socks5h://(远端解析 DNS,推荐)、http://、https://(均可带或不带 user:pass),其余 → 422。
两种时机:创建时带 proxy,或事后 PUT /proxy。生效为进程级,running 改返回 pending_restart:true,stop→start 后生效。
geo 对齐:建议把 locale/tz pin 到代理出口的真实地理(德国代理配de-DE/Europe/Berlin),避免「IP 在德国但浏览器声称美东」的矛盾。
09配额 & 生命周期
默认每用户 10 个实例(instances_max),超额 429 instance quota reached (max 10)。按实例数计,不分启停;腾配额需 DELETE。
created ──start──▶ starting ──▶ running ──stop──▶ stopped
│ │ 失败 │
│ └──▶ error └──start──▶ running
└──delete──▶ deleting ──▶ (销毁) stopped ──delete──▶ (销毁)
- 持久化:每实例一个 profile 卷(磁盘持久),固化身份存卷里。stop 保留、DELETE 销毁。
- 同实例反复 start/stop 身份稳定不变;DELETE 后无法复现。
- 资源上限:每容器有内存 / CPU / PID 上限,
mem_rss_bytes/cpu_pct可观测。
10抗检测说明
- 固化身份:首启一次性固化指纹写入持久 profile(
fingerprint_committed:true),跨重启 navigator / 屏幕 / 字体 / 硬件一致,删实例才丢。 - 真实分辨率:按 profile 屏幕(如 1920×1080)开窗,window/screen 自洽,不再是 headless 默认的过小视口。
- locale/tz pin:与代理 geo 一致,消除地理矛盾。
- per-profile canvas/audio 噪声:实例间互不相同(不可聚类关联),同实例每次一致(不暴露被改)。
- 驱动层 patchright:规避
navigator.webdriver与 CDP 痕迹。
自测:连上后访问 https://abrahamjuliot.github.io/creepjs/,关注多次重启指纹是否稳定、IP geo 与 tz/locale 是否对齐、有无 automation 红旗。
11错误码速查
REST 错误体 {"detail":"..."}:
| 码 | 含义 | 处理 |
|---|---|---|
401 | 缺 / 无效 token | 重新 regenerate |
404 | 不存在或越权 | 核对 id / 归属 |
409 | 状态冲突(对 starting/stopping 调 start) | 轮询 GET /v1/instances/{id} 至稳定再操作 |
422 | 校验失败(name 空/超长、platform、proxy scheme) | 修正请求体 |
429 | 配额满 | DELETE 不用的实例 |
500 | 容器启动失败转 error | 看 last_error,重试 start |
503 | GitHub OAuth 未配置(仅登录接口) | — |
CDP WS 关闭码 4401/4409/4404/4502 见 CDP 驱动。遇 4401/4409 重新 POST /start 拿最新带 token 端点再连。