Console API 参考
Console 节点的 HTTP 服务监听在 :8089,所有 REST 端点以 /api/v1 为前缀。
Base URL: http://<console-host>:8089
REST 前缀: /api/v1
内容类型: application/json
时间格式: RFC3339
ID 格式: 不透明字符串(acc_*、tok_*、worker UUID、task ID)
错误响应统一格式:
{ "error": "message" }鉴权模型
Console 提供五种鉴权凭据,覆盖不同使用场景:
| 项目 | Cookie 会话 | API Key(Bearer) | Dashboard JIT(Bearer) | 执行 Token(Bearer) | MCP JIT(Bearer) |
|---|---|---|---|---|---|
| 凭据类型 | Cookie onlyboxes_console_session | Authorization: Bearer obxk_* | Authorization: Bearer obx_dashboard_jit_v1.* | Authorization: Bearer obx_* | Authorization: Bearer obx_jit_v1.* |
| 来源 | POST /api/v1/auth/login | POST /api/v1/api-keys(仅 Cookie) | 外部签名(HMAC-SHA256,CONSOLE_DASHBOARD_JIT_SIGNING_KEY) | POST /api/v1/tokens(仅 Cookie) | 外部签名(HMAC-SHA256,CONSOLE_JIT_SIGNING_KEY) |
| 有效期 | 12 小时(内存态,console 重启后全部失效) | 持久,直到手动删除 | 无状态;通过 payload exp 过期或轮换签名密钥吊销 | 持久,直到手动删除 | 无状态;轮换签名密钥即可吊销 |
| 适用端点 | 所有管理端点 | 大多数管理端点(只读及部分写) | 用于 worker-sys 配置的部分管理 API | /api/v1/commands/*、/api/v1/tasks*、/mcp | /api/v1/commands/*、/api/v1/tasks*、/mcp |
| 主要用途 | Web UI 管理操作 | 程序化管理操作 | 不授予 MCP 访问权的服务端 Dashboard 自动化 | 命令执行与 MCP | 无需预创建账户的第三方执行集成 |
以下端点仅接受 Cookie 会话,拒绝 API Key Bearer 与 Dashboard JIT:
POST /api/v1/auth/password、POST /api/v1/api-keys、DELETE /api/v1/api-keys/:api_key_id,以及所有/api/v1/tokens*端点。如果系统中没有任何执行 Token,命令执行端点及
/mcp的 trusted-token 鉴权会返回401;配置CONSOLE_JIT_SIGNING_KEY后,有效 MCP JIT token 仍可鉴权。MCP JIT token(
obx_jit_v1.*)在所有 Dashboard/管理路由上会被明确拒绝。Dashboard JIT token(obx_dashboard_jit_v1.*)会被/mcp明确拒绝。
REST API
Web UI 与第三方集成使用同一套资源式端点,每个端点分别执行下文标注的管理凭据或执行凭据要求。
已经发布的 /api/v1/console/* 身份认证、账号、API Key 和 Token 路径继续作为兼容别名。新集成应使用下文列出的资源式主路径。Terminal session 管理没有 /api/v1/console/sessions* 别名,因为它首次发布时使用的路径就是 /api/v1/sessions*。
身份认证与账号
登录
POST /api/v1/auth/login
请求体:
{
"username": "admin",
"password": "secret"
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
username | string | 是 | 账号名 |
password | string | 是 | 密码 |
响应 200:
{
"authenticated": true,
"account": {
"account_id": "acc_xxx",
"username": "admin",
"is_admin": true
},
"registration_enabled": false,
"console_version": "v0.0.0",
"console_repo_url": "https://..."
}同时在响应中设置 Cookie onlyboxes_console_session。
错误码:
| 状态码 | 说明 |
|---|---|
400 | JSON 结构非法 |
401 | 用户名或密码错误 |
500 | 会话创建失败 |
查询会话
GET /api/v1/auth/session
接受控制台 Cookie、Console API Key 或 Dashboard JIT 鉴权。返回结构与登录响应一致。
错误码:
| 状态码 | 说明 |
|---|---|
401 | 未登录或会话已过期 |
登出
POST /api/v1/auth/logout
清理响应 Cookie 并销毁内存会话(若存在)。
响应: 204 No Content
注册账号(仅管理员)
POST /api/v1/accounts
注册一个新的普通账号。需要管理员身份鉴权,且系统注册开关(CONSOLE_ENABLE_REGISTRATION)须为 true。
请求体:
{
"username": "dev-user",
"password": "strong-password"
}| 字段 | 类型 | 必填 | 约束 |
|---|---|---|---|
username | string | 是 | trim 后非空,长度 ≤ 64,系统内大小写不敏感唯一 |
password | string | 是 | 非空 |
响应 201:
{
"account": {
"account_id": "acc_xxx",
"username": "dev-user",
"is_admin": false
},
"created_at": "2026-02-21T00:00:00Z",
"updated_at": "2026-02-21T00:00:00Z"
}错误码:
| 状态码 | 说明 |
|---|---|
400 | 用户名为空 / 长度超 64 / 密码为空 |
403 | 注册开关关闭(CONSOLE_ENABLE_REGISTRATION=false)或当前账号非管理员 |
409 | 用户名冲突 |
500 | 数据库或内部错误 |
修改当前账号密码
POST /api/v1/auth/password
修改当前登录账号的密码。密码更新后会轮换该账号的所有活动会话。
请求体:
{
"current_password": "old-password",
"new_password": "new-password"
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
current_password | string | 是 | 当前密码(用于身份验证) |
new_password | string | 是 | 新密码 |
响应:
| 状态码 | 说明 |
|---|---|
204 | 修改成功 |
400 | JSON 非法 / 字段缺失 |
401 | 当前密码错误 |
500 | 内部错误 |
查询账号列表(仅管理员)
GET /api/v1/accounts
查询参数:
| 参数 | 类型 | 默认值 | 约束 |
|---|---|---|---|
page | int | 1 | 正整数 |
page_size | int | 20 | 正整数,最大 100 |
响应 200:
{
"items": [
{
"account_id": "acc_xxx",
"username": "admin",
"is_admin": true,
"created_at": "2026-02-21T00:00:00Z",
"updated_at": "2026-02-21T00:00:00Z"
}
],
"total": 1,
"page": 1,
"page_size": 20
}错误码:
| 状态码 | 说明 |
|---|---|
400 | 查询参数非法 |
403 | 当前账号非管理员 |
500 | 数据库或内部错误 |
删除账号(仅管理员)
DELETE /api/v1/accounts/:account_id
路径参数:
| 参数 | 说明 |
|---|---|
account_id | 账号 ID(acc_*) |
响应:
| 状态码 | 说明 |
|---|---|
204 | 删除成功 |
403 | 禁止删除当前登录账号 / 禁止删除管理员账号 |
404 | 账号不存在 |
500 | 内部错误 |
Token 管理
Token 按账号隔离,每个账号只能管理自己的 token。
所有 Token 管理端点都要求 Cookie 会话。API Key 与 Dashboard JIT token 会被拒绝,避免 Dashboard Bearer 凭据创建或管理 MCP trusted token。
查询 Token 列表
GET /api/v1/tokens
响应 200:
{
"items": [
{
"id": "tok_xxx",
"name": "default-token",
"token_masked": "obx_******abcd",
"created_at": "2026-02-21T00:00:00Z",
"updated_at": "2026-02-21T00:00:00Z"
}
],
"total": 1
}
token_masked仅显示后四位,token 明文只在创建时返回一次。
创建 Token
POST /api/v1/tokens
请求体:
{
"name": "ci-prod",
"token": "optional-manual-token"
}| 字段 | 类型 | 必填 | 约束 |
|---|---|---|---|
name | string | 是 | trim 后非空,长度 ≤ 64,同账号内大小写不敏感唯一 |
token | string | 否 | 省略时自动生成(obx_<hex>);手动提供时不含空白字符,长度 ≤ 256 |
响应 201:
{
"id": "tok_xxx",
"name": "ci-prod",
"token": "obx_plaintext_or_manual",
"token_masked": "obx_******abcd",
"generated": true,
"created_at": "2026-02-21T00:00:00Z",
"updated_at": "2026-02-21T00:00:00Z"
}
token字段(明文)仅在本次响应中返回,之后无法再次获取,请妥善保存。
错误码:
| 状态码 | 说明 |
|---|---|
400 | 参数校验失败 |
409 | token 名称冲突或 token 值冲突 |
500 | 内部错误 |
删除 Token
DELETE /api/v1/tokens/:token_id
路径参数:
| 参数 | 说明 |
|---|---|
token_id | Token ID(tok_*) |
响应:
| 状态码 | 说明 |
|---|---|
204 | 删除成功 |
404 | token 不存在(含跨账号访问) |
获取 Token 明文
GET /api/v1/tokens/:token_id/value
此端点永远返回 410 Gone,token 明文仅在创建时返回一次。
{
"error": "token value is only returned at creation time; delete and recreate the token to obtain a new value"
}API Key 管理
API Key 用于程序化访问大多数管理端点(与执行 Token 不同,不可用于命令执行端点或 /mcp)。每个账号只能管理自己的 API Key。
查询 API Key 列表
GET /api/v1/api-keys
鉴权: Cookie 会话或 API Key Bearer
响应 200:
{
"items": [
{
"id": "apik_xxx",
"name": "my-key",
"key_masked": "obxk_******abcd",
"created_at": "2026-02-21T00:00:00Z",
"updated_at": "2026-02-21T00:00:00Z"
}
],
"total": 1
}创建 API Key
POST /api/v1/api-keys
鉴权:仅 Cookie 会话
请求体:
{ "name": "ci-management" }| 字段 | 类型 | 必填 | 约束 |
|---|---|---|---|
name | string | 是 | trim 后非空,长度 ≤ 64,同账号内大小写不敏感唯一 |
响应 201:
{
"id": "apik_xxx",
"name": "ci-management",
"key": "obxk_plaintext",
"key_masked": "obxk_******abcd",
"created_at": "2026-02-21T00:00:00Z",
"updated_at": "2026-02-21T00:00:00Z"
}
key字段(明文)仅在本次响应中返回一次,请立即保存。
错误码:
| 状态码 | 说明 |
|---|---|
400 | 请求体非法 / 名称为空 / 名称超 64 字符 |
401 | Cookie 会话缺失或无效(API Key Bearer 不被接受) |
409 | 名称冲突 |
503 | 存储服务不可用 |
500 | 内部错误 |
删除 API Key
DELETE /api/v1/api-keys/:api_key_id
鉴权:仅 Cookie 会话
路径参数:
| 参数 | 说明 |
|---|---|
api_key_id | API Key ID(apik_*) |
响应:
| 状态码 | 说明 |
|---|---|
204 | 删除成功 |
400 | api_key_id 缺失 |
401 | Cookie 会话缺失或无效(API Key Bearer 不被接受) |
404 | API Key 不存在(含跨账号访问) |
500 | 内部错误 |
Worker 管理
权限矩阵
| 操作 | 管理员 | 普通用户 |
|---|---|---|
| 查询列表 | 所有 worker | 仅本人 worker-sys |
| 查询统计 | 所有 worker | 仅本人 worker-sys |
| 查询并发占用 | 所有 worker | 仅本人 worker-sys |
创建 normal | 是 | 否 |
创建 worker-sys | 是(每账号可创建多个) | 是(每账号可创建多个) |
| 删除任意 worker | 是 | 否 |
删除本人 worker-sys | 是 | 是 |
Worker 类型:normal(对应 worker-docker/worker-boxlite)、worker-sys(对应 worker-sys)。
查询 Worker 列表
GET /api/v1/workers
查询参数:
| 参数 | 类型 | 默认值 | 可选值 |
|---|---|---|---|
page | int | 1 | 正整数 |
page_size | int | 20 | 正整数,最大 100 |
status | string | all | all / online / offline |
响应 200:
{
"items": [
{
"node_id": "worker-1",
"node_name": "node-a",
"executor_kind": "docker",
"capabilities": [
{ "name": "echo", "max_inflight": 4 }
],
"labels": {
"region": "us",
"obx.owner_id": "acc_xxx",
"obx.worker_type": "normal"
},
"version": "v0.1.0",
"status": "online",
"registered_at": "2026-02-21T00:00:00Z",
"last_seen_at": "2026-02-21T00:00:00Z"
}
],
"total": 1,
"page": 1,
"page_size": 20
}错误码:
| 状态码 | 说明 |
|---|---|
400 | 查询参数非法 |
Worker 统计
GET /api/v1/workers/stats
查询参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
stale_after_sec | int | 30 | 判定为过时的秒数阈值(上次心跳超过此时长即为 stale) |
响应 200:
{
"total": 5,
"online": 4,
"offline": 1,
"stale": 1,
"stale_after_sec": 30,
"generated_at": "2026-02-21T00:00:00Z"
}普通用户的统计数据仅包含本人
worker-sys。
Worker 并发占用
GET /api/v1/workers/inflight
响应 200:
{
"workers": [
{
"node_id": "worker-1",
"active_session_count": 3,
"terminal_session_capacity": {
"known": true,
"max_active_sessions": 4
},
"capabilities": [
{ "name": "pythonExec", "inflight": 1, "max_inflight": 4 }
]
}
],
"generated_at": "2026-02-21T00:00:00Z"
}普通用户的结果仅包含本人
worker-sys。
active_session_count是 worker 最近一次上报的 terminal reservation 数。terminal_session_capacity.known=false表示旧 worker 未声明最大值,不能解释为不限。known=true,max_active_sessions=0明确表示 active terminal session 数不限。
创建 Worker 凭据
POST /api/v1/workers
请求体:
{
"type": "normal"
}| 字段 | 类型 | 必填 | 可选值 |
|---|---|---|---|
type | string | 是 | normal / worker-sys |
响应 201:
{
"node_id": "2f51f8f9-77f2-4c1a-a4f5-2036fc9fcb9e",
"type": "normal",
"worker_secret": "a3f8c2..."
}
worker_secret仅在本次响应中返回一次,后续无法再获取。
错误码:
| 状态码 | 说明 |
|---|---|
400 | 请求体非法 / type 值不合法 |
403 | 普通用户尝试创建 normal 类型 |
503 | provisioning 服务不可用 |
500 | 创建失败 |
删除 Worker
DELETE /api/v1/workers/:node_id
路径参数:
| 参数 | 说明 |
|---|---|
node_id | Worker 节点 ID(UUID) |
响应:
| 状态码 | 说明 |
|---|---|
204 | 删除成功 |
400 | 缺少 node_id |
404 | worker 不存在(普通用户越权访问也返回 404) |
503 | provisioning 服务不可用 |
获取 Worker 启动命令
GET /api/v1/workers/:node_id/startup-command
此端点永远返回 410 Gone,启动命令(含 secret)只在创建 worker 时返回一次。
{
"error": "worker secret is returned only when creating the worker; delete and recreate to get a new startup command"
}Terminal Session API
这些端点需要管理鉴权(Cookie、Console API Key 或 Dashboard JIT)。它们列出、查询、续租并删除已确认的 terminal session。尚未完成首次 terminalExec 的 session 不可见。
作用域:
- 非管理员:只能看到自己的 session;
account_id指向其他账号时返回404 - 管理员:默认全站;
account_id用于过滤列表。查询、续租和删除单条必须带account_id,因为session_id只在账号内唯一
status 取值:
ready:绑定的 Worker 可调度unavailable:绑定的 Worker 已断开;session 仍保留原 Worker 与 leasereconciling:绑定的 Worker 正在重连并核对 session 资源
删除 session 会立即移除 Console 路由,以及该 session 的公开预览路由。Worker 上的沙箱在 lease 到期后回收。
查询 Session 列表
GET /api/v1/sessions
查询参数:
| 参数 | 类型 | 默认值 | 约束 |
|---|---|---|---|
page | int | 1 | 正整数 |
page_size | int | 20 | 正整数,最大 100 |
account_id | string | 可选。管理员用来过滤;非管理员只能传自己的账号 |
响应 200:
{
"items": [
{
"account_id": "acc_xxx",
"session_id": "sess_xxx",
"worker_id": "worker-1",
"status": "ready",
"lease_expires_at": "2026-02-21T00:01:00Z",
"last_used_at": "2026-02-21T00:00:30Z",
"created_at": "2026-02-21T00:00:00Z"
}
],
"total": 1,
"page": 1,
"page_size": 20
}错误码:
| 状态码 | 说明 |
|---|---|
400 | 查询参数非法 |
401 | 控制台凭据缺失或无效 |
404 | 非管理员查询了其他账号 |
503 | session registry 不可用 |
查询单个 Session
GET /api/v1/sessions/:session_id?account_id=acc_xxx
成功 200:结构与列表中的单项相同。
错误码:
| 状态码 | 说明 |
|---|---|
400 | 管理员未提供 account_id |
401 | 控制台凭据缺失或无效 |
404 | session 不存在(含跨账号访问) |
503 | session registry 不可用 |
续租 Session
POST /api/v1/sessions/:session_id/renew?account_id=acc_xxx
Console 会请求持有该 session 的 Worker 延长租约,然后持久化 Worker 确认的绝对到期时间。续租是单调的:较短的 TTL 不会缩短当前租约。续租 session 不会延长任何公开预览 route。
{
"lease_ttl_sec": 300
}lease_ttl_sec 必须为正数,并位于所属 Worker 配置的 terminal lease 范围内。
成功 200:结构与列表中的单项相同,其中 lease_expires_at 和 last_used_at 已更新。
错误码:
| 状态码 | 说明 |
|---|---|
400 | 请求体非法、管理员未提供 account_id,或 TTL 超出 Worker 的租约范围 |
401 | 控制台凭据缺失或无效 |
404 | session 不存在(含跨账号访问) |
502 | Worker 拒绝续租或返回了非法结果 |
503 | session registry 或绑定的 Worker 不可用 |
504 | 续租超时 |
删除 Session
DELETE /api/v1/sessions/:session_id?account_id=acc_xxx
响应:
| 状态码 | 说明 |
|---|---|
204 | 删除成功 |
400 | 管理员未提供 account_id |
401 | 控制台凭据缺失或无效 |
404 | session 不存在(含跨账号访问) |
500 | 删除失败 |
503 | session registry 不可用 |
公开预览路由
这些管理 API 接受 Dashboard Cookie、Console API Key 或 Dashboard JIT 鉴权。路由按账号隔离并持久化到 SQLite。完整部署流程参考公开预览代理。
POST /api/v1/proxy-routes
{
"session_id": "sess_xxx",
"port": 8080
}成功 201:
{
"route_key": "ceirceirceirceirceirceirce",
"account_id": "acc_xxx",
"session_id": "sess_xxx",
"port": 8080,
"url": "https://ceirceirceirceirceirceirce.public-preview.example.com",
"created_at": "2026-02-21T00:00:00Z",
"expires_at": "2026-02-22T00:00:00Z"
}规则与错误:
session_id必须属于当前账号,并且已有一条指向在线、已启用代理的 Docker、Boxlite 或 E2B Worker 的确认路由。port范围为1..65535。- route URL 由
CONSOLE_PROXY_PUBLIC_SCHEME(默认https)和CONSOLE_PROXY_PUBLIC_BASE_DOMAIN组成;仅在可信的本地开发环境使用http。 - routeKey 使用小写 Base32,默认长度为
26。CONSOLE_PROXY_ROUTE_KEY_LENGTH可控制新 routeKey 的长度,范围8..26;修改后已有 route 仍然有效。低于16位会降低 URL 的抗猜测能力,仅适合可信本地或低风险环境。 - 每个账号默认最多保留 16 条有效 route,可通过
CONSOLE_PROXY_ROUTE_MAX_PER_ACCOUNT修改。 - 单个 Session 默认最多保留 2 条有效 route,可通过
CONSOLE_PROXY_ROUTE_MAX_PER_SESSION修改。 - route 默认 TTL 为
86400秒,可通过CONSOLE_PROXY_ROUTE_TTL_SEC修改,最大为604800秒(7 天)。 400请求体、Session ID 或端口非法。404Session 路由不存在。429当前账号或 Session 达到 route 上限。503Session 所在 Worker 没有可用代理入口。
GET /api/v1/proxy-routes?page=1&page_size=20&account_id=acc_xxx
作用域:
- 非管理员只能列出自己的 route;将
account_id指向其他账号会返回404。 - 管理员默认列出全部账号的 route,也可以通过
account_id过滤单个账号。
查询参数:
| 参数 | 类型 | 默认值 | 约束 |
|---|---|---|---|
page | int | 1 | 正整数 |
page_size | int | 20 | 正整数,最大 100 |
account_id | string | 可选账号过滤条件,规则见上文 |
成功 200:
{
"items": [
{
"route_key": "ceirceirceirceirceirceirce",
"account_id": "acc_xxx",
"session_id": "sess_xxx",
"port": 8080,
"url": "https://ceirceirceirceirceirceirce.public-preview.example.com",
"created_at": "2026-02-21T00:00:00Z",
"expires_at": "2026-02-22T00:00:00Z"
}
],
"total": 1,
"page": 1,
"page_size": 20
}错误码:
| 状态码 | 说明 |
|---|---|
400 | page 或 page_size 非法 |
401 | 管理凭据缺失或无效 |
404 | 非管理员请求了其他账号的 route |
DELETE /api/v1/proxy-routes/:route_key
204删除成功。404route 不存在、已过期或属于其他账号。
返回的预览 URL 是匿名地址:任何持有者都能访问 Sandbox 服务,不需要 Onlyboxes Cookie 或 Bearer Token。每个新 HTTP 请求都由 Nginx 调用受保护的 Console 内部接口解析。Docker 与 Boxlite 链路取得全新 15 秒 Route Token;E2B 链路取得当前 sandbox origin 与内部 traffic token,数据面为"用户 → Nginx → E2B"。Token 过期不会终止已接受的 HTTP/SSE/WebSocket 连接。访问 route 不会续租 Sandbox lease。有效 route 及其原 URL 可跨 Console 重启恢复;在所属 Worker 重连并完成 terminal session 恢复前,新请求暂不可用。
命令执行 API
以下端点使用 Bearer Token 鉴权(Authorization: Bearer <token>),供外部程序、AI Agent 或 MCP 客户端调用。
仅 /mcp 支持给无法设置自定义请求头的客户端使用 URL 查询参数兜底:POST /mcp?token=<token>。Bearer header 仍是推荐方式,且存在时优先;命令执行和任务 REST 端点不接受 query-token 鉴权。查询参数名默认是 token,可通过 CONSOLE_MCP_TOKEN_QUERY_PARAM 修改。
命令执行
Echo
POST /api/v1/commands/echo
用于测试连通性,验证 echo worker 可用。
请求体:
{
"message": "hello",
"timeout_ms": 5000
}| 字段 | 类型 | 必填 | 约束 |
|---|---|---|---|
message | string | 是 | trim 后非空 |
timeout_ms | int | 否 | 1..60000,默认 5000 |
响应 200:
{ "message": "hello" }错误码:
| 状态码 | 说明 |
|---|---|
400 | 请求体错误 / message 缺失 / timeout 超范围 |
429 | 无可用并发容量 |
503 | 无在线 echo worker |
504 | 执行超时 |
502 | 执行或内部异常 |
Terminal 执行
POST /api/v1/commands/terminal
在持久化 terminal 会话中执行 shell 命令,支持跨请求的会话状态复用。
请求体:
{
"command": "pwd",
"session_id": "optional-session",
"create_if_missing": false,
"lease_ttl_sec": 60,
"timeout_ms": 60000,
"request_id": "optional-idempotency-key"
}| 字段 | 类型 | 必填 | 约束 |
|---|---|---|---|
command | string | 是 | trim 后非空 |
session_id | string | 否 | 已有会话 ID;省略时自动创建新会话 |
create_if_missing | bool | 否 | session_id 不存在时是否自动创建,默认 false |
lease_ttl_sec | int | 否 | 会话租约时长(秒),会被 worker 配置的 [min, max] 范围截断(默认 min/default: 60,默认 max: 1800) |
timeout_ms | int | 否 | 1..600000,默认 60000 |
request_id | string | 否 | 幂等键,按账号隔离去重 |
当 create_if_missing=true 时,以保留 Computer Use 前缀(默认 CU:)开头的 session_id 会被拒绝;当 create_if_missing=false 时,仍按普通 sandbox session 查找处理。
响应 200:
{
"session_id": "sess_xxx",
"created": true,
"stdout": "...",
"stderr": "...",
"exit_code": 0,
"stdout_truncated": false,
"stderr_truncated": false,
"lease_expires_unix_ms": 1770000000000
}| 字段 | 说明 |
|---|---|
session_id | 本次使用的会话 ID(新建或已有) |
created | 是否为本次新建的会话 |
stdout / stderr | 命令输出 |
exit_code | 命令退出码 |
stdout_truncated / stderr_truncated | 输出是否被截断 |
lease_expires_unix_ms | 会话租约到期时间(Unix 毫秒时间戳) |
错误码:
| 状态码 | 错误码 | 说明 |
|---|---|---|
400 | invalid_payload | 请求参数非法 |
404 | session_not_found | session_id 不存在且 create_if_missing=false |
409 | session_busy | 会话被另一请求占用,或任务已取消 |
429 | no_capacity | 选中的 worker 对应 capability 并发额度耗尽 |
429 | session_capacity_exceeded | 选中的 sandbox worker 已达到 WORKER_TERMINAL_MAX_ACTIVE_SESSIONS;已有 session 仍可使用,但本次请求无法创建新 session |
503 | session_unavailable | session 绑定的 Worker 离线或重启后正在核对资源;客户端应使用同一 session_id 重试,Console 不会将其改派 |
503 | — | 无可用 worker |
504 | — | 执行超时 |
502 | — | 其他执行失败 |
Computer Use 执行
POST /api/v1/commands/computer-use
列出调用账号的 Worker System,或在选定的 worker-sys 宿主机上执行 shell 命令。执行固定投递到请求的 Worker,不会回退。
请求体:
{
"command": "pwd",
"worker_id": "worker-id",
"timeout_ms": 60000,
"request_id": "optional-idempotency-key"
}| 字段 | 类型 | 必填 | 约束 |
|---|---|---|---|
worker_id | string | 否 | 当前账号的 worker-sys;省略时进入列表模式 |
command | string | 条件必填 | 提供 worker_id 时必须为非空命令 |
timeout_ms | int | 否 | 1..600000,默认 60000 |
request_id | string | 否 | 幂等键,账号维度去重 |
响应 200:
未提供 worker_id 时,Console 不产生 Worker dispatch,并返回当前账号全部在线与离线 Worker System:
{"worker_list":[{"worker_id":"worker-id","node_name":"laptop","status":"online","capabilities":[]}]}提供 worker_id 时响应为:
{
"stdout": "...",
"stderr": "...",
"exit_code": 0,
"stdout_truncated": false,
"stderr_truncated": false
}与 Terminal 执行不同,Computer Use 不维护会话(无
session_id),每次命令均直接在选定的worker-sys宿主机执行,无容器隔离。
错误码:
| 状态码 | 错误码 | 说明 |
|---|---|---|
400 | invalid_payload | 请求参数非法 |
409 | session_busy | worker 正忙,或任务已取消 |
429 | no_capacity | 无可用并发容量 |
503 | — | 指定 Worker 离线或缺少目标 capability |
504 | — | 执行超时 |
502 | — | 其他执行失败 |
Sandbox 元数据 API
Sandbox 元数据按 Bearer token 对应账号隔离。执行 Token 与 MCP JIT token 均可调用。
获取 Sandbox 元数据
GET /api/v1/sandbox/metadata
响应 200:
{
"provider": "onlyboxes",
"api_version": "2026-05-25",
"console": { "version": "0.6.1" },
"limits": {
"max_task_timeout_ms": 600000,
"max_task_wait_ms": 60000,
"max_terminal_timeout_ms": 600000,
"default_terminal_lease_sec": 60,
"max_terminal_lease_sec": 86400
},
"capabilities": [
{
"name": "terminalExec",
"available": true,
"online_nodes": 1,
"max_inflight": 4
}
],
"workers": {
"total": 1,
"online": 1,
"offline": 0,
"stale": 0
}
}能力范围:
| 能力 | 范围 |
|---|---|
computerUse、readImage | 仅统计调用账号自有 worker-sys |
terminalExec、terminalResource、pythonExec、echo | 全局在线 worker 可用性 |
错误码:
| 状态码 | 说明 |
|---|---|
401 | Bearer token 缺失或无效 |
503 | worker registry 不可用 |
任务 API
Task 适合需要异步执行或结果轮询的场景。所有权按 token 对应账号严格隔离。
提交任务
POST /api/v1/tasks
请求体:
{
"capability": "pythonExec",
"input": { "code": "print(1)" },
"mode": "auto",
"wait_ms": 1500,
"timeout_ms": 60000,
"request_id": "optional-idempotency-key"
}| 字段 | 类型 | 必填 | 约束 |
|---|---|---|---|
capability | string | 是 | 非空,对应 worker 声明的能力名(大小写不敏感;存储和响应中统一规范化为小写) |
input | object | 否 | 合法 JSON 对象,省略时默认 {} |
mode | string | 否 | sync / async / auto,默认 auto |
wait_ms | int | 否 | 1..60000,默认 1500;auto 模式下等待完成的最长毫秒数 |
timeout_ms | int | 否 | 1..600000,默认 60000;任务总超时 |
request_id | string | 否 | 幂等键,账号维度去重 |
各 capability 的路由规则与 MCP 和专用命令接口一致:
computerUse从input.worker_id读取目标;省略时任务在 Console 内直接完成,result.worker_list返回全部自有 Worker System,不产生 Worker dispatch。readImage使用input.session_id="CU:<worker_id>"指定自有 Worker System;其他值作为 sandbox terminal session ID。terminalExec在create_if_missing=true且input.session_id使用保留 Computer Use 前缀时拒绝请求。
CU:是默认且大小写敏感的 Computer Use session ID 前缀。可通过环境变量CONSOLE_COMPUTER_USE_SESSION_ID_PREFIX或 TOML 配置项computer_use_session_id_prefix修改;上述示例使用默认值。Console 会使用同一配置解释任务 API、MCPreadImage/exportFile和terminalExec中的 session ID。
terminal 容量改派期间,同一任务保持
task_id与request_id不变;command_id表示当前或最后一次 worker 派发,因此任务运行中可能更新。
mode 字段说明:
| mode | 行为 |
|---|---|
sync | 同步等待,任务完成后才返回 |
async | 立即返回 202,不等待 |
auto | 等待最多 wait_ms 毫秒,未完成则返回 202 |
响应 — 任务未完成 202:
{
"task_id": "task_xxx",
"request_id": "req-1",
"command_id": "cmd_xxx",
"capability": "pythonexec",
"status": "running",
"created_at": "2026-02-21T00:00:00Z",
"updated_at": "2026-02-21T00:00:01Z",
"deadline_at": "2026-02-21T00:01:00Z",
"status_url": "/api/v1/tasks/task_xxx"
}响应 — 任务成功完成 200:
{
"task_id": "task_xxx",
"capability": "pythonexec",
"status": "succeeded",
"result": {
"output": "1\n",
"stderr": "",
"exit_code": 0
},
"created_at": "2026-02-21T00:00:00Z",
"updated_at": "2026-02-21T00:00:01Z",
"deadline_at": "2026-02-21T00:01:00Z",
"completed_at": "2026-02-21T00:00:01Z"
}任务错误字段(终态失败时携带):
{
"error": {
"code": "execution_failed",
"message": "..."
}
}终态响应码:
| 状态码 | 说明 |
|---|---|
200 | 任务成功完成 |
409 | 任务已被取消 |
504 | 任务执行超时 |
429 | 失败,error.code=no_capacity 或 error.code=session_capacity_exceeded |
503 | 失败,error.code=no_worker |
502 | 失败(其他 error code) |
提交阶段错误码:
| 状态码 | 说明 |
|---|---|
400 | 参数非法 / 模式非法 / 请求体非法 |
409 | request_id 已在处理中 |
429 | 无可用并发容量 |
503 | 无匹配能力的 worker |
504 | 请求超时 |
502 | 提交失败 |
查询任务
GET /api/v1/tasks/:task_id
路径参数:
| 参数 | 说明 |
|---|---|
task_id | 任务 ID |
响应:
| 状态码 | 说明 |
|---|---|
200 | 返回任务快照(结构见「提交任务」响应) |
404 | 任务不存在(含跨账号访问) |
取消任务
POST /api/v1/tasks/:task_id/cancel
路径参数:
| 参数 | 说明 |
|---|---|
task_id | 任务 ID |
响应:
| 状态码 | 说明 |
|---|---|
200 | 取消成功(或已受理 best-effort 取消) |
404 | 任务不存在(含跨账号访问) |
409 | 任务已终态,返回当前任务快照 |
500 | 取消失败 |
JIT Token 认证
JIT(Just-in-Time)Token 允许可信上游系统代表某个用户发起请求,无需提前在 Onlyboxes 中创建账户。Console 会校验 HMAC 签名,根据 (iss, sub) 确定性派生账号,并在首次使用时创建一个禁用 Dashboard 密码登录的账号。
Onlyboxes 有两条 JIT 通道,分别使用不同前缀和签名密钥:
| 通道 | 前缀 | 签名密钥 | 必需 Payload | 接受方 | 拒绝方 |
|---|---|---|---|---|---|
| MCP JIT | obx_jit_v1. | CONSOLE_JIT_SIGNING_KEY | iss、sub;可选 exp | /api/v1/commands/*、/api/v1/tasks*、/mcp | Dashboard 管理 API |
| Dashboard JIT | obx_dashboard_jit_v1. | CONSOLE_DASHBOARD_JIT_SIGNING_KEY | iss、sub、scope:"dashboard";可选 exp | 部分管理 API,例如 worker-sys 配置 | /mcp 与 cookie-only 接口 |
两把签名密钥必须不同。这样即使两条通道共享账号派生逻辑,MCP 执行访问权与 Dashboard 自动化访问权仍然彼此隔离。
共享账号映射
两条通道会把相同的 (iss, sub) 映射到同一个非管理员 JIT 账号。这意味着上游身份可以先用 Dashboard JIT 创建 worker-sys,之后再用 MCP JIT 在同一个账号所有权下执行任务,同时两条通道不共享签名材料。
规则:
iss与sub归一化后必须是非空字符串。- 派生账号 ID 是确定性的,相同组合会复用同一个账号。
- JIT 创建的账号不能通过 Dashboard 密码登录。
- JIT 创建的账号不是管理员。
MCP JIT
MCP JIT 适用于执行客户端和 MCP 客户端,不需要预先创建持久 trusted token。
前提条件: 配置 CONSOLE_JIT_SIGNING_KEY。未设置时 MCP JIT token 会被拒绝。
Token 格式:
obx_jit_v1.<base64url-payload>.<base64url-signature>| 部分 | 内容 |
|---|---|
| 前缀 | obx_jit_v1. — 字面字符串,包含在被签名消息中 |
| Payload | 包含 iss、sub 与可选 exp 的 JSON 的 Base64url 编码(无填充) |
| Signature | 对 obx_jit_v1.<payload> 做 HMAC-SHA256 后 Base64url 编码(无填充) |
用法: Authorization: Bearer obx_jit_v1.<payload>.<signature>
Payload 示例:
{ "iss": "my-platform", "sub": "user-42", "exp": 1770000000000 }接受端点: /api/v1/commands/*、/api/v1/tasks*、/mcp
拒绝端点: Dashboard 管理 API,包括 worker 与 token 管理路由。
Dashboard JIT
Dashboard JIT 用于服务端到服务端的 Dashboard 自动化,典型场景是为用户配置 worker-sys,但不授予 MCP 执行访问权。
前提条件: 配置 CONSOLE_DASHBOARD_JIT_SIGNING_KEY,且必须与 CONSOLE_JIT_SIGNING_KEY 不同。
Token 格式:
obx_dashboard_jit_v1.<base64url-payload>.<base64url-signature>| 部分 | 内容 |
|---|---|
| 前缀 | obx_dashboard_jit_v1. — 字面字符串,包含在被签名消息中 |
| Payload | 包含 iss、sub、scope:"dashboard" 与可选 exp 的 JSON 的 Base64url 编码(无填充) |
| Signature | 对 obx_dashboard_jit_v1.<payload> 做 HMAC-SHA256 后 Base64url 编码(无填充) |
Payload 示例:
{
"iss": "my-platform",
"sub": "user-42",
"scope": "dashboard",
"exp": 1770000000000
}用法: Authorization: Bearer obx_dashboard_jit_v1.<payload>.<signature>
接受端点: 适合非管理员账号级自动化的部分管理 API,例如在派生账号下创建与查询 worker-sys。
拒绝端点:
/mcp- MCP/执行 REST API
- admin-only 管理 API
- cookie-only 管理 API,包括所有
/api/v1/tokens*端点
签名流程
- 选择通道和签名密钥:MCP JIT 或 Dashboard JIT。
- 将对应通道的 JSON payload 编码为无填充 Base64url。
- 使用该通道的密钥对
<prefix><payload>做 HMAC-SHA256 签名。 - 将生成的 token 放入
Authorization: Bearer ...请求头。 - Console 校验前缀、签名、必需 claim 与可选过期时间。
- 首次使用时,Console 派生并创建非管理员 JIT 账号,然后在该账号所有权下处理请求。
吊销
- 轮换
CONSOLE_JIT_SIGNING_KEY可吊销所有 MCP JIT token。 - 轮换
CONSOLE_DASHBOARD_JIT_SIGNING_KEY可吊销所有 Dashboard JIT token。 - 建议尽量为 Dashboard JIT token 设置较短的
exp,以降低重放风险。
参考实现
以下示例生成不带 exp 的 MCP JIT token;加入 Unix 毫秒字段 exp 后即可让 token 过期。若要生成 Dashboard JIT token,请改用 obx_dashboard_jit_v1. 前缀、使用 CONSOLE_DASHBOARD_JIT_SIGNING_KEY 签名,并在 payload 中加入 scope:"dashboard" 和可选 exp:
{ "iss": "my-platform", "sub": "user-42", "scope": "dashboard", "exp": 1770000000000 }Python
import hmac, hashlib, base64, json
def make_jit_token(signing_key: str, issuer: str, subject: str) -> str:
payload_bytes = json.dumps({"iss": issuer, "sub": subject}, separators=(",", ":")).encode()
payload_b64 = base64.urlsafe_b64encode(payload_bytes).rstrip(b"=").decode()
message = f"obx_jit_v1.{payload_b64}"
sig = hmac.new(signing_key.encode(), message.encode(), hashlib.sha256).digest()
sig_b64 = base64.urlsafe_b64encode(sig).rstrip(b"=").decode()
return f"{message}.{sig_b64}"
token = make_jit_token("your-signing-key", "my-platform", "user-42")
# 用法:Authorization: Bearer <token>TypeScript / Node.js
import { createHmac } from "crypto";
function makeJitToken(signingKey: string, issuer: string, subject: string): string {
const payload = Buffer.from(JSON.stringify({ iss: issuer, sub: subject }))
.toString("base64url");
const message = `obx_jit_v1.${payload}`;
const sig = createHmac("sha256", signingKey).update(message).digest("base64url");
return `${message}.${sig}`;
}
const token = makeJitToken("your-signing-key", "my-platform", "user-42");
// 用法:Authorization: Bearer <token>Go
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
)
func makeJITToken(signingKey, issuer, subject string) (string, error) {
payload, err := json.Marshal(map[string]string{"iss": issuer, "sub": subject})
if err != nil {
return "", err
}
payloadB64 := base64.RawURLEncoding.EncodeToString(payload)
message := "obx_jit_v1." + payloadB64
mac := hmac.New(sha256.New, []byte(signingKey))
mac.Write([]byte(message))
sigB64 := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
return message + "." + sigB64, nil
}
// token, _ := makeJITToken("your-signing-key", "my-platform", "user-42")
// 用法:Authorization: Bearer <token>Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public static String makeJitToken(String signingKey, String issuer, String subject) throws Exception {
String payloadJson = String.format("{\"iss\":\"%s\",\"sub\":\"%s\"}", issuer, subject);
String payloadB64 = Base64.getUrlEncoder().withoutPadding()
.encodeToString(payloadJson.getBytes());
String message = "obx_jit_v1." + payloadB64;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(signingKey.getBytes(), "HmacSHA256"));
String sigB64 = Base64.getUrlEncoder().withoutPadding()
.encodeToString(mac.doFinal(message.getBytes()));
return message + "." + sigB64;
}
// String token = makeJitToken("your-signing-key", "my-platform", "user-42");
// 用法:Authorization: Bearer <token>