Sandboxes API
sandbox 是平台的核心资源,代表一个完全隔离的运行环境。
所有端点需要 developer 角色(GET 只读端点需要 viewer 及以上)。
Sandbox 能力速查
一张表看全 sandbox 的所有能力。
| 能力 | 字段 / 端点 | 说明 |
|---|---|---|
| 镜像 | image | 指定容器镜像(如 talon-alpine);空 = 使用默认镜像 |
| CPU | resources.cpu | 浮点 core 数,如 0.5、2;0 = 使用 worker 默认值 |
| 内存 | resources.memory | 字符串单位,如 "4GiB";空 = 使用 worker 默认值 |
| 磁盘 | resources.disk | 字符串单位,如 "20GiB";空 = 使用 worker 默认值 |
| 进程数上限 | resources.pids_limit | 最大并发进程数;0 = 使用 worker 默认值 |
| 空闲超时 | timeout | duration 字符串,如 "30m";sandbox 无操作超过此时长自动 pause;空 = 禁用 |
| 硬性 TTL | ttl | duration 字符串,如 "6h";从创建时间起超过此时长自动 destroy;空 = 禁用 |
| 网络策略 | network | open(全放行)/ allowlist(白名单)/ sealed(完全隔离);见网络策略 |
| 主机白名单 | network_allowed_hosts | []string;配合 allowlist 使用,支持域名 / IP / CIDR |
| 凭证注入 | secrets[] | 将已创建的 secret 以 env 或 file 方式注入 sandbox |
| 环境变量 | env | map[string]string;sandbox 启动时注入的环境变量 |
| 标签 | labels | map[string]string;创建时写定的自定义 KV 元数据,不可变,不注入容器;见下方labels 详解 |
| 可读名称 | name | 自定义名称;空时 UI 显示 id |
| 任务描述 | task | 本次任务的文字描述,显示在控制台概览 |
| 启动等待 | ?wait=running | query 参数;服务端阻塞直到 sandbox 进入 running 后再返回 |
| 执行命令 | POST /{id}/exec | 同步执行一次性命令,返回 stdout / stderr / exit_code |
| 长驻进程 | POST /{id}/processes | 启动后台进程,支持 env / cwd / 声明暴露端口 |
| 端口暴露 | POST /{id}/expose | 注册端口并获得 preview URL;支持自定义子域名和签名 token |
| 文件系统 | /v1/sandboxes/{id}/fs/* | 列目录、读文件、写文件 |
| 交互式终端 | WebSocket /{id}/pty | 交互式 PTY,全程录像为 asciicast v2 格式 |
| 浏览器 | POST /{id}/browser | 启动 Chromium,通过 CDP 协议控制 |
| Agent Run | POST /{id}/agent/run | 自动化 agent 步骤执行,最多 100 步 |
| 录制 | GET /{id}/recordings/* | 获取 PTY 会话录像(asciicast v2 格式) |
duration 字符串格式:30s / 5m / 2h / 1d / 1w(扩展支持 d / w 单位)。
size 字符串格式:512MiB / 4GiB / 20GB(binary KiB/MiB/GiB 和 decimal KB/MB/GB 均接受,大小写不敏感)。
POST /v1/sandboxes
创建 sandbox。
需要 developer 角色
POST /v1/sandboxes
Authorization: Bearer ask_...
Content-Type: application/json请求体(v2 推荐)
{
"image": "talon-alpine",
"resources": {
"cpu": 2,
"memory": "4GiB",
"disk": "10GiB"
},
"timeout": "30m",
"ttl": "6h",
"network": "allowlist",
"env": { "NODE_ENV": "development" },
"labels": { "project": "agent-x" },
"secrets": [
{
"secret_id": "sec_xxx",
"mount_type": "env",
"target": "OPENAI_API_KEY"
}
]
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
image | string | 否 | 镜像标签(如 talon-alpine);空 → worker 默认 |
resources.cpu | number | 否 | CPU 核数,支持小数(如 0.5);0 = worker 默认 |
resources.memory | string | 否 | 内存上限,字符串单位(512MiB / 4GiB / 2GB);空 = worker 默认 |
resources.disk | string | 否 | 磁盘上限,字符串单位;空 = worker 默认 |
timeout | string | 否 | 无操作自动 pause(30s / 5m / 2h / 1d);空 = 禁用 |
ttl | string | 否 | 绝对生存时间,同 duration 格式;空 = 禁用 |
network | string | 否 | sealed / allowlist / open(别名,见下);空 = 默认 |
env | object | 否 | 环境变量 dict,sandbox 启动时注入容器 |
labels | object | 否 | 自定义 KV 元数据,map[string]string;仅控制面可见,不注入容器;见下方labels 详解 |
secrets | array | 否 | 注入的凭证列表(见下方说明) |
network 别名:
| 别名 | 含义 |
|---|---|
sealed | 完全断网,只有 lo |
allowlist | DNS + 配置好的白名单域(生产推荐) |
open | 允许所有出站(开发调试) |
duration 字符串 — 30s / 5m / 2h / 1d / 1w,扩展 Go ParseDuration 加 d / w 单位。
size 字符串 — 512KiB / 4GiB / 2GB / 1TiB,binary(KiB/MiB/GiB) 和 decimal(KB/MB/GB)都接受,大小写不敏感。
secrets 元素字段:
| 字段 | 说明 |
|---|---|
secret_id | 已创建的 secret ID |
mount_type | file(挂载为 tmpfs 文件)或 env(注入为环境变量) |
target | file 模式:/run/secrets/<target>;env 模式:环境变量名 |
labels 详解
labels 是创建 sandbox 时附带的自定义 KV 元数据,类型为 map[string]string。
格式约束(后端校验,违反返回 400)
| 约束项 | 规则 |
|---|---|
| 最大条数 | 32 个 key-value 对 |
| key 字符集 | [a-zA-Z0-9_-],长度 1–64 字节 |
| value 长度 | 最长 256 字节;禁止控制字符(允许中文、邮箱等可见字符及 tab) |
不可变性:labels 只在创建时写定,创建后无法修改,也没有更新 labels 的端点。
安全边界:labels 是纯控制面元数据,不会注入到容器环境变量,容器内进程读不到。 这与 env(会注入容器)是刻意区分的——适合存放终端用户标识等不该让 sandbox 内代码感知的数据。
可见性:本租户成员可查看自己租户 sandbox 的 labels;平台超管可跨租户查看(用于运营归因)。
响应里的 labels:GET sandbox 详情和 list sandbox 均会原样返回 labels 字段。
服务端过滤:GET /v1/sandboxes 支持 label=key:value query 参数按 label 服务端过滤(见下方 按 label 过滤),大列表场景无需在客户端遍历。
典型用例:SaaS 二次分发
集成方用一个工作区 + 一个 API Key 代表整个平台,所有 sandbox 的 created_by 都是 同一个工作区账户。要归因"是哪个终端用户创建的",在创建时打入 labels:
{
"image": "talon-alpine",
"labels": {
"end_user_id": "u_8821",
"plan": "pro"
}
}后续可以:
- 通过
GET /v1/sandboxes?label=end_user_id:u_8821按用户过滤 sandbox 列表(见上方按 label 过滤) - 通过用量计量 API 按
end_user_id拆分资源用量,用于二次计费分账
响应
201 Created
响应体始终用规范化后的字段(v2 风格):
{
"id": "sbx_xxxxxxxxxxxxxxxxxxxxxxxxxx",
"state": "created",
"image": "talon-alpine",
"resources": {
"cpu": 2,
"memory": "4GiB",
"disk": "10GiB"
},
"timeout": "30m",
"ttl": "6h",
"network": "allowlist",
"created_at": 1716480000
}调用方便利
请求时也支持 ?wait=running(Spec 45)— 服务端 block 到 sandbox 进入 running 再返回,省一轮 polling。SDK Sandbox.create() 默认带上这个 query。
GET /v1/sandboxes
列出当前租户所有 sandbox。
需要 viewer 角色
GET /v1/sandboxes
Authorization: Bearer ask_...按 label 过滤
通过 label query 参数在服务端按 label 过滤,语法为 key:value(冒号分隔,因为 value 本身可能含等号):
GET /v1/sandboxes?label=end_user_id:u_8821&label=plan:pro
Authorization: Bearer ask_...| 行为 | 说明 |
|---|---|
| 可重复 | ?label=k1:v1&label=k2:v2 逻辑为 AND:全部命中才返回 |
| 分隔符 | 固定用 : 分隔 key 与 value(value 可含 =、/、空格等可见字符) |
| 服务端执行 | 过滤在数据库层完成,不受响应列表大小影响 |
| 客户端兜底 | SDK 在收到响应后仍会做一次客户端过滤,兼容老版本服务端 |
CLI 等效写法:
tsb list --label end_user_id=u_8821 --label plan=proCLI 用
key=value格式(等号)是命令行习惯;底层转换为 API 的key:value格式再发给服务端。
响应
200 OK
{
"sandboxes": [
{
"id": "sbx_xxx",
"state": "running",
"profile": "code-lite",
"created_at": 1716480000
}
]
}GET /v1/sandboxes/
获取单个 sandbox 详情。
需要 viewer 角色
GET /v1/sandboxes/{id}
Authorization: Bearer ask_...响应
200 OK — 返回 SandboxDTO(字段同 POST 响应)
404 Not Found
{ "error": "sandbox: not found" }POST /v1/sandboxes//start
启动 sandbox(created / stopped → running)。
需要 developer 角色
POST /v1/sandboxes/{id}/start
Authorization: Bearer ask_...响应
200 OK
{ "id": "sbx_xxx", "state": "running", ... }409 Conflict — 状态不允许 start
{ "error": "sandbox: invalid state transition" }POST /v1/sandboxes//stop
停止 sandbox(running → stopped)。所有进程会被 kill。
需要 developer 角色
POST /v1/sandboxes/{id}/stop
Authorization: Bearer ask_...响应
200 OK
{ "id": "sbx_xxx", "state": "stopped", ... }POST /v1/sandboxes//pause
暂停 sandbox(running → paused)。进程被冻结,内存保留。
需要 developer 角色
POST /v1/sandboxes/{id}/pause
Authorization: Bearer ask_...响应
200 OK
{ "id": "sbx_xxx", "state": "paused", ... }POST /v1/sandboxes//resume
恢复 sandbox(paused → running)。毫秒级恢复。
需要 developer 角色
POST /v1/sandboxes/{id}/resume
Authorization: Bearer ask_...响应
200 OK
{ "id": "sbx_xxx", "state": "running", ... }POST /v1/sandboxes//exec
在 sandbox 内执行一次性命令(同步,等待完成返回结果)。
需要 developer 角色
sandbox 必须处于 running 状态。
POST /v1/sandboxes/{id}/exec
Authorization: Bearer ask_...
Content-Type: application/json{
"command": ["bash", "-c", "echo $HOME && ls /workspace"]
}响应
200 OK
{
"stdout": "/root\napp.py\npackage.json\n",
"stderr": "",
"exit_code": 0
}同步阻塞
exec 是同步接口,会等待命令完成才返回。不适合长时间运行的命令(如服务器启动)——那类用 processes 端点。
DELETE /v1/sandboxes/
销毁 sandbox(任意 alive 状态 → destroyed)。所有数据永久删除。
需要 developer 角色
DELETE /v1/sandboxes/{id}
Authorization: Bearer ask_...响应
204 No Content — 销毁成功
404 Not Found — sandbox 不存在
POST /v1/sandboxes//expose
显式暴露 sandbox 内一个端口,返回 preview URL(Spec 50)。
需要 developer 角色
POST /v1/sandboxes/{id}/expose
Authorization: Bearer ask_...
Content-Type: application/json请求体
{
"port": 5173,
"subdomain": "my-app",
"sign": true,
"ttl": "1h"
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
port | int | 是 | 容器端口(1–65535) |
subdomain | string | 否 | 自定义 subdomain;空 → 用 sb-{id}-{port} |
sign | bool | 否 | 是否生成签名 token(Spec 48);默认 false |
ttl | string | 否 | 签名 token 有效期(duration string);默认 1h,最大 24h |
响应
201 Created
{
"port": 5173,
"url": "http://sb-xxx-5173.preview.example.com",
"source": "explicit",
"signed": false
}签名时 URL 带 ?token= query:
{
"port": 5173,
"url": "http://sb-xxx-5173.preview.example.com/?token=eyJ...",
"source": "explicit",
"signed": true,
"expires_at": "2026-05-24T15:04:05Z"
}DELETE /v1/sandboxes//expose/{port}
取消显式暴露。注意:动态发现源(Spec 39)暴露的端口无法 unexpose,要关掉 端口需要 kill 持有该端口的进程。
需要 developer 角色
DELETE /v1/sandboxes/{id}/expose/{port}
Authorization: Bearer ask_...响应
- 204 No Content — 取消成功
- 404 Not Found — 该端口没被显式 expose 过
GET /v1/sandboxes//expose
列出 sandbox 当前所有暴露的端口(显式 + 动态发现)。
需要 viewer 角色
GET /v1/sandboxes/{id}/expose
Authorization: Bearer ask_...响应
200 OK
{
"ports": [
{
"port": 5173,
"url": "http://sb-xxx-5173.preview.example.com",
"source": "explicit",
"signed": false
},
{
"port": 3000,
"url": "http://sb-xxx-3000.preview.example.com",
"source": "dynamic",
"signed": false
}
]
}| 字段 | 说明 |
|---|---|
source | explicit(通过 POST /expose 显式注册)或 dynamic(port-watcher sidecar 自动发现,Spec 39) |
signed | 是否带签名 token |
详见 端口暴露概念。
Sandbox DTO 字段说明(响应)
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | sandbox ID(sbx_ + 24 hex) |
state | string | 当前状态,见生命周期 |
image | string | 镜像标签 |
resources.cpu | number | CPU 核数 |
resources.memory | string | 内存上限(如 4GiB) |
resources.disk | string | 磁盘上限 |
resources.pids_limit | int64 | PID 数量上限 |
timeout | string | 空闲自动 pause(duration string) |
ttl | string | 绝对生存时间(duration string) |
network | string | 网络策略别名(sealed / allowlist / open) |
env | object | 环境变量 dict(注入容器) |
labels | object | 自定义 KV 元数据(map[string]string);创建时写定,不可变,不注入容器;详见labels 详解 |
last_active_at | int64 | 最后活跃时间(Unix 秒) |
created_at | int64 | 创建时间(Unix 秒) |
secrets | array | 绑定的凭证(元数据,不含 value) |
Signed Preview Token
Issue a short-lived token that lets anyone holding it access the preview proxy for a specific port — no account required.
See Signed Preview URL for the full guide.
POST /v1/sandboxes/{id}/preview-token
Requires developer or owner role
POST /v1/sandboxes/{id}/preview-token
Authorization: Bearer ask_...
Content-Type: application/json
{
"port": 5173,
"ttl_seconds": 3600
}Request body
| Field | Type | Required | Description |
|---|---|---|---|
port | int | yes | Container port to authorise (1–65535) |
ttl_seconds | int64 | no | Token lifetime in seconds (default 3600, max 86400) |
Response — 201 Created
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2026-05-24T15:04:05Z"
}Use the token by appending ?token=<value> to the preview URL:
https://api.example.com/v1/sandboxes/sbx_xxx/preview/5173/?token=eyJ...The token is stripped before forwarding to the upstream app and cannot be used on any other endpoint.
Error responses
| Code | Meaning |
|---|---|
| 400 | port out of range or request body invalid |
| 401 | Not authenticated |
| 403 | Insufficient role (viewer) |
| 404 | Sandbox not found |