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_sessionAuthorization: Bearer obxk_*Authorization: Bearer obx_dashboard_jit_v1.*Authorization: Bearer obx_*Authorization: Bearer obx_jit_v1.*
来源POST /api/v1/auth/loginPOST /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"
}
字段类型必填说明
usernamestring是账号名
passwordstring是密码

响应 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。

错误码:

状态码说明
400JSON 结构非法
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"
}
字段类型必填约束
usernamestring是trim 后非空,长度 ≤ 64,系统内大小写不敏感唯一
passwordstring是非空

响应 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_passwordstring是当前密码(用于身份验证)
new_passwordstring是新密码

响应:

状态码说明
204修改成功
400JSON 非法 / 字段缺失
401当前密码错误
500内部错误

查询账号列表(仅管理员)

GET /api/v1/accounts

查询参数:

参数类型默认值约束
pageint1正整数
page_sizeint20正整数,最大 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"
}
字段类型必填约束
namestring是trim 后非空,长度 ≤ 64,同账号内大小写不敏感唯一
tokenstring否省略时自动生成(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参数校验失败
409token 名称冲突或 token 值冲突
500内部错误

删除 Token

DELETE /api/v1/tokens/:token_id

路径参数:

参数说明
token_idToken ID(tok_*)

响应:

状态码说明
204删除成功
404token 不存在(含跨账号访问)

获取 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" }
字段类型必填约束
namestring是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 字符
401Cookie 会话缺失或无效(API Key Bearer 不被接受)
409名称冲突
503存储服务不可用
500内部错误

删除 API Key

DELETE /api/v1/api-keys/:api_key_id

鉴权:仅 Cookie 会话

路径参数:

参数说明
api_key_idAPI Key ID(apik_*)

响应:

状态码说明
204删除成功
400api_key_id 缺失
401Cookie 会话缺失或无效(API Key Bearer 不被接受)
404API 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

查询参数:

参数类型默认值可选值
pageint1正整数
page_sizeint20正整数,最大 100
statusstringallall / 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_secint30判定为过时的秒数阈值(上次心跳超过此时长即为 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"
}
字段类型必填可选值
typestring是normal / worker-sys

响应 201:

{
  "node_id": "2f51f8f9-77f2-4c1a-a4f5-2036fc9fcb9e",
  "type": "normal",
  "worker_secret": "a3f8c2..."
}

worker_secret 仅在本次响应中返回一次,后续无法再获取。

错误码:

状态码说明
400请求体非法 / type 值不合法
403普通用户尝试创建 normal 类型
503provisioning 服务不可用
500创建失败

删除 Worker

DELETE /api/v1/workers/:node_id

路径参数:

参数说明
node_idWorker 节点 ID(UUID)

响应:

状态码说明
204删除成功
400缺少 node_id
404worker 不存在(普通用户越权访问也返回 404)
503provisioning 服务不可用

获取 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 与 lease
  • reconciling:绑定的 Worker 正在重连并核对 session 资源

删除 session 会立即移除 Console 路由,以及该 session 的公开预览路由。Worker 上的沙箱在 lease 到期后回收。

查询 Session 列表

GET /api/v1/sessions

查询参数:

参数类型默认值约束
pageint1正整数
page_sizeint20正整数,最大 100
account_idstring可选。管理员用来过滤;非管理员只能传自己的账号

响应 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非管理员查询了其他账号
503session registry 不可用

查询单个 Session

GET /api/v1/sessions/:session_id?account_id=acc_xxx

成功 200:结构与列表中的单项相同。

错误码:

状态码说明
400管理员未提供 account_id
401控制台凭据缺失或无效
404session 不存在(含跨账号访问)
503session 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控制台凭据缺失或无效
404session 不存在(含跨账号访问)
502Worker 拒绝续租或返回了非法结果
503session registry 或绑定的 Worker 不可用
504续租超时

删除 Session

DELETE /api/v1/sessions/:session_id?account_id=acc_xxx

响应:

状态码说明
204删除成功
400管理员未提供 account_id
401控制台凭据缺失或无效
404session 不存在(含跨账号访问)
500删除失败
503session 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 或端口非法。
  • 404 Session 路由不存在。
  • 429 当前账号或 Session 达到 route 上限。
  • 503 Session 所在 Worker 没有可用代理入口。

GET /api/v1/proxy-routes?page=1&page_size=20&account_id=acc_xxx

作用域:

  • 非管理员只能列出自己的 route;将 account_id 指向其他账号会返回 404。
  • 管理员默认列出全部账号的 route,也可以通过 account_id 过滤单个账号。

查询参数:

参数类型默认值约束
pageint1正整数
page_sizeint20正整数,最大 100
account_idstring可选账号过滤条件,规则见上文

成功 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
}

错误码:

状态码说明
400page 或 page_size 非法
401管理凭据缺失或无效
404非管理员请求了其他账号的 route

DELETE /api/v1/proxy-routes/:route_key

  • 204 删除成功。
  • 404 route 不存在、已过期或属于其他账号。

返回的预览 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
}
字段类型必填约束
messagestring是trim 后非空
timeout_msint否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"
}
字段类型必填约束
commandstring是trim 后非空
session_idstring否已有会话 ID;省略时自动创建新会话
create_if_missingbool否session_id 不存在时是否自动创建,默认 false
lease_ttl_secint否会话租约时长(秒),会被 worker 配置的 [min, max] 范围截断(默认 min/default: 60,默认 max: 1800)
timeout_msint否1..600000,默认 60000
request_idstring否幂等键,按账号隔离去重

当 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 毫秒时间戳)

错误码:

状态码错误码说明
400invalid_payload请求参数非法
404session_not_foundsession_id 不存在且 create_if_missing=false
409session_busy会话被另一请求占用,或任务已取消
429no_capacity选中的 worker 对应 capability 并发额度耗尽
429session_capacity_exceeded选中的 sandbox worker 已达到 WORKER_TERMINAL_MAX_ACTIVE_SESSIONS;已有 session 仍可使用,但本次请求无法创建新 session
503session_unavailablesession 绑定的 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_idstring否当前账号的 worker-sys;省略时进入列表模式
commandstring条件必填提供 worker_id 时必须为非空命令
timeout_msint否1..600000,默认 60000
request_idstring否幂等键,账号维度去重

响应 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 宿主机执行,无容器隔离。

错误码:

状态码错误码说明
400invalid_payload请求参数非法
409session_busyworker 正忙,或任务已取消
429no_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 可用性

错误码:

状态码说明
401Bearer token 缺失或无效
503worker 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"
}
字段类型必填约束
capabilitystring是非空,对应 worker 声明的能力名(大小写不敏感;存储和响应中统一规范化为小写)
inputobject否合法 JSON 对象,省略时默认 {}
modestring否sync / async / auto,默认 auto
wait_msint否1..60000,默认 1500;auto 模式下等待完成的最长毫秒数
timeout_msint否1..600000,默认 60000;任务总超时
request_idstring否幂等键,账号维度去重

各 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、MCP readImage/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参数非法 / 模式非法 / 请求体非法
409request_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 JITobx_jit_v1.CONSOLE_JIT_SIGNING_KEYiss、sub;可选 exp/api/v1/commands/*、/api/v1/tasks*、/mcpDashboard 管理 API
Dashboard JITobx_dashboard_jit_v1.CONSOLE_DASHBOARD_JIT_SIGNING_KEYiss、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* 端点

签名流程

  1. 选择通道和签名密钥:MCP JIT 或 Dashboard JIT。
  2. 将对应通道的 JSON payload 编码为无填充 Base64url。
  3. 使用该通道的密钥对 <prefix><payload> 做 HMAC-SHA256 签名。
  4. 将生成的 token 放入 Authorization: Bearer ... 请求头。
  5. Console 校验前缀、签名、必需 claim 与可选过期时间。
  6. 首次使用时,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>

另请参阅