

本文档介绍如何通过 RESTful API 或 Python SDK 接入沙箱服务,包括沙箱的创建、连接、执行命令、生命周期管理,以及模板(沙箱运行环境)的创建与构建。
占位符约定
文档中出现的以下占位符需要替换成你自己的实际值:
占位符 | 说明 |
{{DOMAIN}} | 服务的 API 域名(control-plane,管理沙箱/模板元数据用) |
{{SANDBOX_DOMAIN}} | 沙箱内部服务(envd,执行命令/文件读写用)的代理域名,跟 {{DOMAIN}} 是两个不同的域名,不能混用 |
{{API_KEY}} | 你的 API Key |
{{TEMPLATE_ID}} | 沙箱模板 ID |
这四个值可以在控制台的「沙箱服务-沙箱接入」页面中,选择 API Key、模板后统一获取,示例代码可以直接复制使用。
认证方式
所有接口统一使用 X-API-Key 请求头传递 API Key:
X-API-Key: {{API_KEY}}
通用响应格式
{
"code": 400,
"message": "具体错误描述"
}
字段 | 类型 | 说明 |
code | integer | 错误码,与 HTTP 状态码一致 |
message | string | 人类可读的错误描述 |
常见状态码含义:
状态码 | 含义 |
200 | 请求成功 |
201 | 资源创建成功 |
202 | 请求已接受,正在异步处理(如构建、快照上传) |
204 | 请求成功,无响应体 |
400 | 请求参数不合法(字段缺失、格式错误、超出限制等) |
401 | 未认证或 API Key 无效 |
403 | 无权限访问该资源 |
404 | 资源不存在 |
409 | 资源状态冲突,暂时无法处理(详见对应接口说明) |
410 | 资源已永久失效,需要重新创建(详见对应接口说明) |
429 | 请求过于频繁,触发限流 |
500 | 服务端内部错误 |
503 | 服务暂时不可用(如并发配额已满) |
[POST] /sandboxes
基于指定模板创建一个新的沙箱实例。
字段 | 类型 | 必填 | 说明 |
templateID | string | 是 | 模板 ID 或别名 |
timeout | int32 | 否 | 存活时长(秒),从创建时刻起计时。不传时沙箱没有显式过期时间(仅受账号套餐允许的最长时长约束,见下方说明);传了则不能超过账号套餐允许的最长时长,超过会返回 400 |
autoPause | boolean | 否,默认 false | 超时后是否自动暂停(而不是销毁) |
secure | boolean | 否 | 是否启用安全访问。启用后,连接沙箱内部服务(执行命令等)需要额外携带创建响应里的 envdAccessToken |
allow_internet_access | boolean | 否 | 是否允许沙箱访问公网。传 false 效果等同于在 network.denyOut 中封禁 0.0.0.0/0 |
network | object | 否 | 网络策略配置,见下方 network 字段说明。不传时,如果模板配置了默认网络策略会自动套用,否则不限制 |
metadata | object | 否 | 自定义键值对(均为字符串),创建响应及查询接口会原样返回 |
envVars | object | 否 | 沙箱内的环境变量(键值对,均为字符串)。不传时,如果模板配置了默认环境变量会自动套用 |
mcp | object | 否 | MCP(Model Context Protocol)相关配置,自由格式对象 |
nodeName | string | 否 | 指定调度到某个具体节点,跳过默认调度策略;该节点不存在或不可用时创建直接失败,不会自动回退到其它节点 |
volumeMounts | array | 否 | 需要挂载的存储卷列表,见下方 volumeMounts 字段说明。不传时,如果模板配置了默认存储挂载会自动套用 |
name | string | 否 | 沙箱的展示名称 |
desc | string | 否 | 沙箱的展示描述 |
projectId | string | 否 | 调用方自己业务系统里的项目 ID,原样存储、透传,不做校验 |
streamLakeProjectId | string | 否 | 计费/统计系统使用的项目标识,原样存储,不会在任何查询接口中返回 |
idlePolicy | object | 否 | 空闲自动处理策略,见下方 idlePolicy 字段说明。不传时,如果模板配置了默认策略会自动套用 |
📌 关于「不传字段回退到模板默认值」:envVars / network / volumeMounts / idlePolicy 这几个字段,只有请求体里完全不出现这个字段(或显式传 null)才会触发「套用模板默认配置」;如果传了一个空对象/空数组(如 "envVars": {}),会被当作「我就是要传空值」,不会去看模板配置了什么。
network 对象:
字段 | 类型 | 说明 |
allowPublicTraffic | boolean,默认 true | 沙箱对外暴露的地址是否允许匿名访问(false 则需要鉴权) |
allowOut | array<string> | 允许的出站 CIDR/IP 列表,优先级高于 denyOut |
denyOut | array<string> | 禁止的出站 CIDR/IP 列表 |
maskRequestHost | string | 沙箱内所有出站请求统一使用的 Host 头 |
mode | enum | denyAll / allowAll / whitelist,粗粒度出网策略,与 allowOut/denyOut 叠加生效(不是互斥关系):denyAll 等价于 allow_internet_access=false;allowAll 是默认行为;whitelist 需要同时提供 whitelistDomains |
whitelistDomains | array<string> | 域名白名单,mode=whitelist 时必填 |
volumeMounts 数组,每项对象:
字段 | 类型 | 必填 | 说明 |
name | string | 是 | 存储卷名称(需已在你的账号下注册) |
path | string | 是 | 挂载到沙箱内的绝对路径 |
subPath | string | 否 | 只挂载存储卷内的某个子目录,而不是整个卷;相对路径,不能包含 .. |
readOnly | boolean,默认 false | 否 | 是否只读挂载 |
hiddenGlobs | array<string> | 否 | 对沙箱隐藏的文件名 glob 匹配规则(如 *.png),命中的文件在沙箱内不可见 |
type | enum | 否 | kfs / oss,直接内联指定存储卷类型(不依赖预先注册),设置后下方对应字段生效 |
sharedStorageId / kfsPath | string | type=kfs 时必填 | 共享存储实例 ID / 存储内的路径 |
credentialId / bucket | string | type=oss 时必填 | 已创建的存储凭证 ID / 存储桶名称 |
endpoint / region | string | 否 | type=oss 时的可选覆盖项,不传则使用账号默认配置 |
idlePolicy 对象:
字段 | 类型 | 必填 | 说明 |
autoSleep | boolean | 是 | 是否启用空闲自动处理 |
maxIdleTime | integer | autoSleep=true 时必填 | 空闲多少秒后触发处理 |
maxSleepTime | integer | autoSleep=true 时必填 | 处理后的最长保留时长(秒) |
curl -X POST "https://{{DOMAIN}}/sandboxes" \
-H "X-API-Key: {{API_KEY}}" \
-H "Content-Type: application/json" \
-d '{
"templateID": "{{TEMPLATE_ID}}",
"timeout": 300,
"envVars": {"MY_VAR": "hello"},
"metadata": {"app": "demo"}
}'
{
"sandboxID": "ix0it63ws9rx20wbwp56c",
"templateID": "glsf46v0atv98q90g3or",
"clientID": "6532622b",
"alias": "my-template",
"envdVersion": "0.2.0",
"envdAccessToken": null,
"domain": "kwai-sandbox-proxy-commercial.corp.kuaishou.com",
"envVars": {"MY_VAR": "hello"},
"volumeMounts": [],
"network": {
"allowPublicTraffic": true
}
}
字段 | 说明 |
sandboxID | 沙箱唯一标识,后续所有操作都基于这个 ID |
templateID | 创建该沙箱所用的模板 ID |
clientID | 已弃用字段,为兼容保留 |
alias | 模板别名(如果有) |
envdVersion | 沙箱内部服务版本号 |
envdAccessToken | 仅当请求体 secure: true 时返回,是访问沙箱内部服务需要的令牌。这个值只在创建/恢复响应里出现一次,后续查询接口不会再返回,需要自行妥善保存 |
domain | 沙箱流量可访问的基础域名 |
envVars | 实际生效的环境变量(回显) |
volumeMounts | 实际生效的存储挂载(回显) |
network | 实际生效的网络策略(回显) |
[GET] /sandboxes/{sandboxID}
参数 | 说明 |
sandboxID | 沙箱 ID |
无请求体、无 query 参数。
curl "https://{{DOMAIN}}/sandboxes/{sandboxID}" \
-H "X-API-Key: {{API_KEY}}"
{
"sandboxID": "ix0it63ws9rx20wbwp56c",
"templateID": "glsf46v0atv98q90g3or",
"clientID": "6532622b",
"alias": "my-template",
"startedAt": "2026-09-14T10:00:00Z",
"endAt": "2026-09-14T10:05:00Z",
"envdVersion": "0.2.0",
"domain": "kwai-sandbox-proxy-commercial.corp.kuaishou.com",
"cpuCount": 2,
"memoryMB": 1024,
"diskSizeMB": 1024,
"metadata": {"app": "demo"},
"state": "running",
"envVars": {"MY_VAR": "hello"},
"volumeMounts": [],
"network": {"allowPublicTraffic": true}
}
字段 | 说明 |
sandboxID / templateID / alias / clientID | 同创建响应 |
startedAt | 沙箱本次启动时间 |
endAt | 沙箱预计过期时间;如果创建/恢复该沙箱时始终没有指定过存活时长(timeout),此字段为 null,表示没有一个明确的到期时间点 |
cpuCount / memoryMB / diskSizeMB | 沙箱的规格 |
metadata | 创建时传入的自定义元数据 |
state | 沙箱状态,running(运行中)或 paused(已暂停) |
createdBy | 创建者信息(仅部分接入方式下返回,否则为 null) |
envVars / volumeMounts / network | 同创建响应,反映当前实际生效的配置 |
[GET] /sandboxes/{sandboxID}/status
沙箱创建/恢复后,内部服务需要一定时间完成初始化;建议在执行命令前先轮询这个接口到 ready,避免过早发起请求失败。
curl "https://{{DOMAIN}}/sandboxes/{sandboxID}/status" \
-H "X-API-Key: {{API_KEY}}"
{
"state": "ready",
"createElapsedMs": 850
}
字段 | 说明 |
state | initializing(初始化中)/ ready(就绪,可以执行命令)/ broken(初始化失败,沙箱即将被回收,不要重试) |
createElapsedMs | 从沙箱开始创建到现在经过的毫秒数 |
errorMessage | 仅 state=broken 时有意义,说明失败原因 |
状态码 | 说明 |
200 | 查询成功 |
401 | 未认证 |
404 | 沙箱不存在 |
500 | 服务端错误 |
[POST] /sandboxes/{sandboxID}/pause
保存沙箱当前的文件系统和内存状态,之后可以通过「恢复沙箱」接口还原现场;暂停后的沙箱不会因为超时被自动销毁。
参数 | 类型 | 必填 | 说明 |
wait_upload | boolean | 否,默认 false | 传 true 时接口会阻塞到状态数据完全持久化后才返回(耗时可能达到数十秒,视沙箱内存大小而定),保证紧接着的恢复操作一定能成功;默认 false 时接口在本地状态保存完成后即返回(约几百毫秒),持久化在后台继续进行,此时立即恢复有极小概率失败,需要按第 6 节的方式重试 |
curl -X POST "https://{{DOMAIN}}/sandboxes/{sandboxID}/pause" \
-H "X-API-Key: {{API_KEY}}"
成功无响应体。
状态码 | 说明 |
204 | 暂停成功 |
401 | 未认证 |
404 | 沙箱不存在 |
409 | 沙箱当前状态不支持暂停 |
500 | 服务端错误 |
[POST] /sandboxes/{sandboxID}/connect〔推荐〕
将一个已暂停的沙箱恢复运行;如果沙箱本来就在运行中,效果等同于续期。
字段 | 类型 | 必填 | 说明 |
timeout | int32 | 是 | 从当前时刻起的存活时长(秒) |
curl -X POST "https://{{DOMAIN}}/sandboxes/{sandboxID}/connect" \
-H "X-API-Key: {{API_KEY}}" \
-H "Content-Type: application/json" \
-d '{"timeout": 300}'
响应体结构同「创建沙箱」接口。
状态码 | 说明 |
200 | 沙箱本来就在运行中,已直接续期 |
201 | 沙箱已从暂停状态恢复 |
400 | 请求参数不合法 |
401 | 未认证 |
404 | 沙箱不存在 |
409 | 暂停时的状态数据仍在后台持久化中,稍后重试即可(一般数秒到数十秒内完成),可以参考文末 Python 示例第 6 步的重试写法 |
410 | 暂停时的状态数据持久化失败或长时间未完成,无法恢复,需要重新创建沙箱 |
500 | 服务端错误 |
[POST] /sandboxes/{sandboxID}/resume〔不推荐〕
功能与第 5 节的 connect 相同,额外支持「钉」到指定节点恢复;connect 是当前推荐的恢复方式,这个接口仍可正常使用,但后续可能被移除。
字段 | 类型 | 必填 | 说明 |
timeout | int32 | 否 | 存活时长(秒) |
nodeName | string | 否 | 指定恢复到某个具体节点;该节点不存在或不可用时直接失败,不会自动回退 |
autoPause | boolean | 否 | 已弃用字段 |
curl -X POST "https://{{DOMAIN}}/sandboxes/{sandboxID}/resume" \
-H "X-API-Key: {{API_KEY}}" \
-H "Content-Type: application/json" \
-d '{"timeout": 300}'
同第 5 节 connect(201 表示恢复成功,不会返回 200)。
[POST] /sandboxes/{sandboxID}/timeout
以当前请求时刻为起点重新计时,不是在原有剩余时间上累加。
字段 | 类型 | 必填 | 说明 |
timeout | int32 | 是 | 从当前时刻起,多少秒后过期 |
curl -X POST "https://{{DOMAIN}}/sandboxes/{sandboxID}/timeout" \
-H "X-API-Key: {{API_KEY}}" \
-H "Content-Type: application/json" \
-d '{"timeout": 7200}'
成功无响应体。
状态码 | 说明 |
204 | 设置成功 |
401 | 未认证 |
404 | 沙箱不存在 |
500 | 服务端错误 |
[POST] /sandboxes/{sandboxID}/refreshes
在沙箱当前剩余存活时间的基础上续期,语义上和「设置超时」接近,但字段名不同(duration),且请求体本身是可选的。
字段 | 类型 | 必填 | 说明 |
duration | integer | 否 | 续期秒数,最大 3600(1 小时) |
curl -X POST "https://{{DOMAIN}}/sandboxes/{sandboxID}/refreshes" \
-H "X-API-Key: {{API_KEY}}" \
-H "Content-Type: application/json" \
-d '{"duration": 300}'
成功无响应体。
状态码 | 说明 |
204 | 续期成功 |
401 | 未认证 |
404 | 沙箱不存在 |
[POST] /sandboxes/{sandboxID}/snapshots
对正在运行的沙箱做一次不中断运行的时间点快照,可以作为新的 templateID 用于创建沙箱(数据回滚点/分支/检查点)。这条接口不接受请求体。
curl -X POST "https://{{DOMAIN}}/sandboxes/{sandboxID}/snapshots" \
-H "X-API-Key: {{API_KEY}}"
{
"snapshotId": "sn1x2y3z4a5b6c7d8e9f0",
"buildId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"createdAt": "2026-09-14T10:10:00Z",
"envVars": {"MY_VAR": "hello"},
"volumeMounts": [],
"network": {"allowPublicTraffic": true}
}
字段 | 说明 |
snapshotId | 快照 ID,可直接作为 templateID 用于创建沙箱 |
buildId | 快照对应的构建 ID,配合 snapshotId 轮询上传进度(见下方说明) |
createdAt | 快照捕获时刻(不是上传完成时刻) |
envVars / volumeMounts / network | 快照捕获那一刻源沙箱的配置 |
📌 注意:202 只表示本地状态捕获完成,数据持久化上传仍在后台异步进行(通常需要数十秒)。上传完成前,用这个 snapshotId 创建沙箱会失败。需要轮询第 15 节「查询构建状态」接口(把 templateID 换成这里的 snapshotId,buildID 换成这里的 buildId)直到状态变为 ready 才能使用。
状态码 | 说明 |
202 | 已接受,正在处理 |
401 | 未认证 |
404 | 沙箱不存在 |
500 | 服务端错误 |
[DELETE] /sandboxes/{sandboxID}
curl -X DELETE "https://{{DOMAIN}}/sandboxes/{sandboxID}" \
-H "X-API-Key: {{API_KEY}}"
成功无响应体。
状态码 | 说明 |
204 | 销毁成功 |
401 | 未认证 |
404 | 沙箱不存在 |
500 | 服务端错误 |
在沙箱内执行命令(exec)走的不是上面这套接口,而是沙箱内部服务({{SANDBOX_DOMAIN}},与管理沙箱元数据用的 {{DOMAIN}} 是两个不同的域名,不能互相替代),协议也不是普通的 JSON REST,而是基于 HTTP 的流式协议(Connect 协议(https://connectrpc.com/),请求体和响应体都需要按固定格式包一层帧头,不能直接发送裸JSON。
生产环境建议直接使用 SDK(如 sandbox.commands.run(...)),SDK 内部已经处理好了这些细节,本节的 curl 示例仅用于临时调试或没有可用 SDK 的场景。
[POST] /process.Process/Start
执行命令(Connect 协议,走 {{SANDBOX_DOMAIN}})
请求需要携带以下两个请求头,用于告知网关把请求转发给哪个沙箱:
请求头 | 说明 |
E2b-Sandbox-Id | 沙箱 ID |
E2b-Sandbox-Port | 沙箱内部服务端口,固定为 49983 |
如果沙箱是以 secure: true 创建的,还需要额外携带创建响应里返回的 envdAccessToken:
请求头 | 说明 |
X-Access-Token | 创建/恢复该沙箱响应中的 envdAccessToken 值 |
# 1. 拼一个符合协议要求的请求体:5 字节头(1 字节 flag=0x00 + 4 字节大端长度)
# 加上 JSON 内容。真正要执行的命令放进 args 的 -c 参数里。
CMD="echo hello && whoami"
BODY=$(printf '{"process":{"cmd":"/bin/bash","args":["-l","-c","%s"]},"stdin":false}' "$CMD")
LEN=$(printf '%s' "$BODY" | wc -c | tr -d ' ')
{ printf '%02x%08x' 0 "$LEN" | xxd -r -p; printf '%s' "$BODY"; } > /tmp/start_req.bin
# 2. 发送请求。Content-Type 必须是 application/connect+json。
curl -sS --max-time 15 \
-H "X-API-Key: {{API_KEY}}" \
-H "E2b-Sandbox-Id: {sandboxID}" \
-H "E2b-Sandbox-Port: 49983" \
-H "Content-Type: application/connect+json" \
-H "Connect-Protocol-Version: 1" \
--data-binary @/tmp/start_req.bin \
"https://{{SANDBOX_DOMAIN}}/process.Process/Start"
响应同样是分帧格式的一串 JSON 事件(下面省略帧头只展示内容,实际收到的是二进制流,不建议直接打印到终端,建议先落盘再过滤不可打印字符查看):
{"event":{"start":{"pid":413}}}
{"event":{"data":{"stdout":"aGVsbG8Kcm9vdAo="}}}
{"event":{"end":{"exited":true,"status":"exit status 0"}}}
事件 | 说明 |
start | 命令已启动,pid 是进程号 |
data | 命令的标准输出/标准错误片段,stdout/stderr 均为 base64 编码,需要自行解码 |
end | 命令已结束,exited 表示是否正常退出,status 是退出状态描述 |
流正常结束时,末尾还会有一个空的结束标记帧,表示这次调用本身(不是命令本身)没有出错。
状态码 | 说明 |
200 | 请求已成功建立连接(命令本身是否成功以响应流里的 end.exited/status 为准) |
401 | 未认证,或沙箱是 secure 创建但未携带/携带了错误的 X-Access-Token |
404 | 沙箱不存在 |
模板(template)定义了沙箱的运行环境(基础镜像、预装依赖、启动命令等)。
[GET] /templates
参数 | 类型 | 说明 |
keyword | string | 按模板 ID/名称/别名模糊匹配(最长 128 字符) |
status | array<string> | 按构建状态过滤,可选值见「查询构建状态」接口的状态说明,多个值用逗号分隔 |
sandboxCategory | array<string> | 按沙箱类型过滤:code / browser / custom,多个值用逗号分隔 |
type | array<string> | 按模板来源过滤:system(平台预置)/ custom(自建),多个值用逗号分隔 |
sortBy | string,默认 createdAt | 排序字段:createdAt / updatedAt |
sortOrder | string,默认 asc | 排序方向:asc / desc |
curPage | integer | 页码(从 1 开始)。与 pageSize 一起使用才会分页,都不传则返回全部匹配结果 |
pageSize | integer | 每页数量。设置后响应会带上 X-Total-Count 响应头,表示分页前的总匹配数 |
includeDeleted | boolean,默认 false | 是否包含已删除的模板(通过响应里的 deletedAt 字段区分) |
available | boolean | 按「当前是否有可用构建」过滤,不传表示都要 |
curl "https://{{DOMAIN}}/templates?keyword=my-python-tpl" \
-H "X-API-Key: {{API_KEY}}"
[
{
"templateID": "glsf46v0atv98q90g3or",
"buildID": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"cpuCount": 2,
"memoryMB": 1024,
"diskSizeMB": 1024,
"public": false,
"name": "my-python-tpl",
"sandboxCategory": "code",
"type": "custom",
"aliases": ["my-python-tpl"],
"createdAt": "2026-09-01T00:00:00Z",
"updatedAt": "2026-09-01T00:10:00Z",
"lastSpawnedAt": null,
"spawnCount": 0,
"buildCount": 1,
"envdVersion": "0.2.0",
"buildStatus": "ready",
"available": true,
"sharedStorageCount": 0
}
]
字段 | 说明 |
templateID | 模板 ID |
buildID | 最近一次成功构建的 ID |
cpuCount / memoryMB / diskSizeMB | 沙箱规格 |
public | 是否为公开模板(团队外也可使用) |
name / description | 展示名称/描述 |
sandboxCategory | 沙箱类型:code / browser / custom |
type | 模板来源:system(平台预置)/ custom(自建) |
tags | 自定义标签 |
aliases | 别名列表,可代替 templateID 使用 |
createdAt / updatedAt | 创建/更新时间 |
lastSpawnedAt | 最近一次被用于创建沙箱的时间 |
deletedAt | 软删除时间,正常模板为 null |
spawnCount / buildCount | 被创建沙箱次数 / 构建次数 |
envdVersion | 内置的沙箱内部服务版本 |
buildStatus | 最近一次构建尝试的状态:building / waiting / ready / error |
errorMessage | 构建失败原因,仅 buildStatus=error 时有值 |
available | 当前是否有可用的成功构建(即用这个模板创建沙箱现在能否成功) |
effectiveImageId / effectiveCpuCount / effectiveMemoryMB | 当前实际生效的构建对应的镜像/规格(可能与最近一次构建尝试不同,比如正在重新构建中) |
envVars / network / volumeMounts / idlePolicy / timeout | 模板级别的默认配置,创建沙箱时相应字段留空会自动套用(timeout 除外,见「注册模板」一节说明) |
startupCommand | 模板级别的默认启动命令 |
sharedStorageCount | 配置了多少个默认存储挂载 |
设置了 pageSize 时,响应头会带上:
响应头 | 说明 |
X-Total-Count | 分页前的总匹配数量 |
状态码 | 说明 |
200 | 查询成功 |
401 | 未认证 |
500 | 服务端错误 |
[POST] /v3/templates
注册一个新模板(或复用已有别名)。这一步只是登记元数据、生成 templateID/buildID,还不会真正开始构建,需要接着调用第 14 节「触发构建」。如果 alias 已存在且属于你,会复用同一个 templateID,只产生一个新的 buildID(相当于给已有模板发起一次新的构建)。
字段 | 类型 | 必填 | 说明 |
alias | string | 是 | 模板别名,作为可读标识使用 |
cpuCount | integer | 否 | CPU 核数,最小 1 |
memoryMB | integer | 否 | 内存大小(MiB),最小 128 |
name / description | string | 否 | 展示名称/描述 |
imageId | string | 否 | 基于已有镜像构建(需要先通过镜像管理接口创建/查看可用镜像),与不传时走 Dockerfile 构建二选一 |
tags | array<string> | 否 | 自定义标签 |
startupCommand | string | 否 | 模板默认启动命令 |
envVars | object | 否 | 模板默认环境变量 |
network | object | 否 | 模板默认网络策略,结构同「创建沙箱」接口的 network 字段 |
volumeMounts | array | 否 | 模板默认存储挂载,结构同「创建沙箱」接口的 volumeMounts 字段 |
idlePolicy | object | 否 | 模板默认空闲处理策略,结构同「创建沙箱」接口的 idlePolicy 字段 |
timeout | integer | 否 | 模板默认存活时长(秒)。传了会校验是否超过账号套餐允许的最长时长,超过返回 400 |
projectId / streamLakeProjectId | string | 否 | 同「创建沙箱」接口的同名字段 |
nodeName | string | 否 | 指定在某个具体节点上构建 |
curl -X POST "https://{{DOMAIN}}/v3/templates" \
-H "X-API-Key: {{API_KEY}}" -H "Content-Type: application/json" \
-d '{"alias": "my-python-tpl", "cpuCount": 2, "memoryMB": 1024}'
{
"templateID": "5cdyt5whuantlxrnf2cf",
"buildID": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"public": false,
"aliases": ["my-python-tpl"],
"envVars": {},
"volumeMounts": [],
"network": null
}
字段 | 说明 |
templateID | 模板 ID,后续构建/查询/删除都用这个 |
buildID | 本次构建的 ID,触发构建时需要 |
public | 是否为公开模板 |
aliases | 别名列表 |
envVars / volumeMounts / network | 回显本次请求提交的配置 |
状态码 | 说明 |
202 | 注册成功,等待触发构建 |
400 | 请求参数不合法(包括别名冲突、超出存活时长限制等) |
401 | 未认证 |
500 | 服务端错误 |
[GET] /templates/{templateID}/files/{hash}
如果构建步骤中需要用到本地文件(比如 Dockerfile 风格构建里的 COPY 指令),需要先把打包好的 tar 文件上传。这一步是幂等的:先查询内容的哈希是否已经在缓存中,命中则跳过上传。
参数 | 说明 |
templateID | 模板 ID |
hash | 待上传文件内容的 SHA-256 哈希 |
# 1. 计算 tar 包内容的 hash
HASH=$(sha256sum build-context.tar | awk '{print $1}')
# 2. 查询是否已在缓存中
curl "https://{{DOMAIN}}/templates/{templateID}/files/${HASH}" \
-H "X-API-Key: {{API_KEY}}"
{
"present": false,
"url": "https://storage.example.com/upload-signed-url..."
}
字段 | 说明 |
present | 该内容哈希是否已经在缓存中;true 时可以跳过后续上传步骤 |
url | 上传地址(预签名 URL);仅 present=false 时需要用到 |
present=false 时,用这个 url 直接 PUT 文件内容完成上传:
curl -X PUT "$UPLOAD_URL" --data-binary @build-context.tar
状态码 | 说明 |
201 | 查询成功 |
400 | 请求参数不合法 |
401 | 未认证 |
404 | 模板不存在 |
500 | 服务端错误 |
[POST] /v2/templates/{templateID}/builds/{buildID}
正式开始构建。templateID/buildID 来自第 12 节「注册模板」的响应。
参数 | 说明 |
templateID | 模板 ID |
buildID | 构建 ID |
字段 | 类型 | 必填 | 说明 |
fromImage | string | 与 fromTemplate 二选一 | 作为构建基础的镜像标识,必须是你能访问到的镜像(平台预置的系统镜像,或你自己账号下的自建镜像) |
fromTemplate | string | 与 fromImage 二选一 | 基于某个已有模板继续构建(链式构建) |
fromImageRegistry | object | 否 | 如果需要从私有镜像仓库拉取而不是走平台镜像目录,在这里提供仓库认证信息;设置后 fromImage 会被当作该仓库里的镜像地址 |
steps | array | 否,默认 [] | 构建步骤列表,见下方说明 |
startCmd | string | 否 | 构建完成后,沙箱启动时执行的命令 |
readyCmd | string | 否 | 就绪检查命令:startCmd 启动服务后,这个命令返回成功(退出码 0)才认为沙箱真正就绪,常用于轮询等待端口起来 |
force | boolean | 否,默认 false | 是否忽略缓存强制重新构建全部步骤 |
capabilities | object | 否 | 可选的高级特性开关,不需要时不传即可 |
steps 数组,每项对象:
字段 | 类型 | 必填 | 说明 |
type | string | 是 | 步骤类型,支持 RUN / ENV / ARG / USER / WORKDIR / COPY / ADD |
args | array<string> | 否 | 参数,随步骤类型不同含义不同(见下表) |
filesHash | string | COPY/ADD 时必填 | 第 13 节上传的构建上下文哈希 |
force | boolean | 否 | 是否忽略缓存重新执行这一步 |
各步骤类型的 args 含义:
类型 | args 含义 |
RUN | 单个 shell 命令字符串,如 ["pip install numpy pandas"] |
ENV / ARG | 一个或多个 KEY=VALUE 形式的字符串 |
USER | 单个用户名 |
WORKDIR | 单个路径 |
COPY / ADD | [源路径, 目标路径],源路径相对于上传的构建上下文 |
curl -X POST "https://{{DOMAIN}}/v2/templates/{templateID}/builds/{buildID}" \
-H "X-API-Key: {{API_KEY}}" -H "Content-Type: application/json" \
-d '{
"fromImage": "registry.example.com/base-images/python:3.11",
"steps": [{"type": "RUN", "args": ["pip install numpy pandas"]}],
"startCmd": "",
"readyCmd": ""
}'
成功无响应体,仅返回状态码。
状态码 | 说明 |
202 | 构建请求已接受,开始异步执行 |
401 | 未认证 |
500 | 服务端错误 |
[GET] /templates/{templateID}/builds/{buildID}/status
轮询这个接口直到状态变为 ready 或 error,用于确认构建何时完成。
参数 | 类型 | 说明 |
logsOffset | integer,默认 0 | 返回构建日志的起始偏移,用于增量获取日志 |
level | string | 只返回该级别及以上的日志:debug / info / warn / error |
curl "https://{{DOMAIN}}/templates/{templateID}/builds/{buildID}/status" \
-H "X-API-Key: {{API_KEY}}"
{
"templateID": "5cdyt5whuantlxrnf2cf",
"buildID": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"status": "building",
"logs": ["Step 1/3: RUN pip install numpy pandas"],
"logEntries": [
{"timestamp": "2026-09-14T10:00:01Z", "message": "Step 1/3: RUN pip install numpy pandas", "level": "info"}
]
}
字段 | 说明 |
status | 构建状态:waiting(已注册,尚未开始)/ building(构建中)/ ready(构建成功,可以使用)/ error(构建失败) |
reason | 仅 status=error 时有值,包含 message(失败原因)和可选的 step(失败的步骤)、logEntries(相关日志) |
logs | 纯文本形式的构建日志(从 logsOffset 开始) |
logEntries | 结构化的构建日志,每条包含 timestamp/message/level/step |
状态码 | 说明 |
200 | 查询成功 |
401 | 未认证 |
500 | 服务端错误 |
[GET] /templates/{templateID}/builds/{buildID}
一次性返回某次构建的完整日志,不用于轮询进行中的构建(轮询请用第 15 节)。
curl "https://{{DOMAIN}}/templates/{templateID}/builds/{buildID}" \
-H "X-API-Key: {{API_KEY}}"
{
"buildID": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"status": "ready",
"createdAt": "2026-09-14T10:00:00Z",
"updatedAt": "2026-09-14T10:02:30Z",
"finishedAt": "2026-09-14T10:02:30Z",
"cpuCount": 2,
"memoryMB": 1024,
"diskSizeMB": 1024,
"envdVersion": "0.2.0",
"logs": ["..."],
"logEntries": [{"timestamp": "2026-09-14T10:00:01Z", "message": "...", "level": "info"}]
}
字段 | 说明 |
buildID | 构建 ID |
status | 同第 15 节 |
createdAt / updatedAt / finishedAt | 创建/最近更新/完成时间 |
cpuCount / memoryMB / diskSizeMB | 该次构建产物的规格 |
envdVersion | 内置的沙箱内部服务版本 |
logs / logEntries | 完整构建日志(纯文本/结构化两种形式) |
状态码 | 说明 |
200 | 查询成功 |
401 | 未认证 |
404 | 构建记录不存在 |
500 | 服务端错误 |
[GET] /templates/{templateID}
参数 | 说明 |
nextToken | 分页游标,从上一页响应的 X-Next-Token 响应头获取 |
limit | 每页返回的构建记录数量 |
curl "https://{{DOMAIN}}/templates/{templateID}" \
-H "X-API-Key: {{API_KEY}}"
{
"templateID": "5cdyt5whuantlxrnf2cf",
"public": false,
"name": "my-python-tpl",
"sandboxCategory": "custom",
"type": "custom",
"aliases": ["my-python-tpl"],
"createdAt": "2026-09-14T10:00:00Z",
"updatedAt": "2026-09-14T10:02:30Z",
"lastSpawnedAt": null,
"spawnCount": 0,
"sharedStorageCount": 0,
"builds": [
{
"buildID": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"status": "ready",
"createdAt": "2026-09-14T10:00:00Z",
"updatedAt": "2026-09-14T10:02:30Z",
"finishedAt": "2026-09-14T10:02:30Z",
"cpuCount": 2,
"memoryMB": 1024
}
]
}
模板本身的字段同第 11 节「列表模板」响应,额外增加:
字段 | 说明 |
builds | 该模板的构建历史列表,每项结构同第 16 节「查询构建详情」(不含完整日志,仅摘要字段) |
状态码 | 说明 |
200 | 查询成功 |
401 | 未认证 |
500 | 服务端错误 |
[DELETE]/templates/{templateID}
curl -X DELETE "https://{{DOMAIN}}/templates/{templateID}" \
-H "X-API-Key: {{API_KEY}}"
成功无响应体。
状态码 | 说明 |
204 | 删除成功 |
401 | 未认证 |
409 | 有其它模板通过链式构建(fromTemplate)依赖这个模板,需要先处理下游模板再删除 |
500 | 服务端错误 |
以下示例完全使用开源 e2b SDK(pip install e2b,2.10+ 版本写法),覆盖创建 / 查详情 / 查状态 / 连接执行命令(含常用的 exec / 文件读写)/ 暂停 / 恢复(推荐用 connect)/ 设置超时 / 续期保活 / 创建快照 / 销毁沙箱,以及模板管理(注册 / 构建 / 查询 / 删除)全部操作。
e2b 官方 SDK 目前未覆盖的几个接口(查询就绪状态、续期保活、创建快照、模板管理)通过 httpx 直接调用上面介绍的 RESTful 接口,复用同一套 domain / api_key。
"""
KwaiSandbox 完整接入示例(完全使用开源 e2b SDK,pip install e2b,2.10+ 版本
写法;不依赖 kwaisandbox 包)。覆盖 create/查详情/查状态/连接执行命令
(含最常用的 exec/文件读写)/pause/connect(推荐恢复)/set_timeout/
keepAlive/snapshot/kill 全部操作,以及模板管理(注册/构建/查询/删除)。
e2b 官方 SDK 目前没有覆盖的几个接口(查 envd 就绪状态 /status、续期
keepAlive /refreshes、创建快照 /snapshots、模板管理 /templates*)是这个
平台在 e2b 协议基础上加的扩展,SDK 没有对应方法,直接用 httpx 发原始
请求,复用同一套 domain/api_key,不是另起一个 client。
"""
import hashlib
import os
import time
import httpx
from e2b import Sandbox
from e2b.connection_config import ConnectionConfig
from e2b.exceptions import SandboxException
DOMAIN = "{{DOMAIN}}"
SANDBOX_DOMAIN = "{{SANDBOX_DOMAIN}}"
API_KEY = "{{API_KEY}}"
os.environ["E2B_DOMAIN"] = DOMAIN
os.environ["E2B_API_URL"] = f"https://{DOMAIN}"
# envd(执行命令/文件系统,第 4/6 步要用)走独立的 proxy 域名,不能用上面
# 那个 control-plane 域名——本平台两者证书/AccessProxy 网关注册都是分开的,
# 混用会导致 envd 的 Connect RPC 请求被打到 API 服务,报
# "no matching operation was found"(API 的 OpenAPI 校验中间件在拒绝一个
# 不在 spec/openapi.yml 里的路径)。同时 e2b 默认的
# `{port}-{sandboxID}.{domain}` 泛子域名寻址也会被 AccessProxy 拒绝,所以
# 不能用默认 domain 拼子域名,只能整个换成这个独立注册的 proxy 域名。
os.environ["E2B_SANDBOX_URL"] = f"https://{SANDBOX_DOMAIN}"
os.environ["E2B_API_KEY"] = API_KEY
# 平台扩展接口(e2b SDK 未覆盖)统一走这个 httpx client,复用同一个 domain/api_key
api = httpx.Client(base_url=f"https://{DOMAIN}", headers={"X-API-Key": API_KEY}, timeout=30)
# 1. 创建沙箱
sandbox = Sandbox.create(template="{{TEMPLATE_ID}}", timeout=300)
print("created:", sandbox.sandbox_id)
# 2. 查询沙箱详情
info = sandbox.get_info()
print("state:", info.state)
# 3. 查询沙箱状态(envd 就绪轮询;GET /sandboxes/{id}/status 是平台扩展
# 接口,e2b SDK 没有对应方法,直接调 REST)
while api.get(f"/sandboxes/{sandbox.sandbox_id}/status").json()["state"] != "ready":
time.sleep(2)
# 4. 连接沙箱后执行命令(最常用):exec + 文件读写
# 刚 create() 出来的这个 sandbox 实例已经自动带好了 envd 需要的
# E2b-Sandbox-Id/E2b-Sandbox-Port 请求头,可以直接用;如果是拿一个
# 已有 sandboxID 重新 Sandbox.connect() 连接(比如换了一个进程/脚本),
# 这两个 header 不会自动带上,要手动传,见下面"6. 恢复沙箱"的写法。
res = sandbox.commands.run("echo $HOME && whoami")
print("stdout:", res.stdout, "exit_code:", res.exit_code)
# 传环境变量 / 指定用户
res = sandbox.commands.run("env | grep MY_VAR", envs={"MY_VAR": "hello"}, user="user")
# 后台执行,拿 handle 自己控制生命周期
handle = sandbox.commands.run("sleep 30", background=True)
# ...干别的事...
handle.kill()
# 写文件 / 读文件 / 列目录
sandbox.files.write("/tmp/hello.txt", "hello from e2b sdk\n")
content = sandbox.files.read("/tmp/hello.txt")
print("file content:", content)
for entry in sandbox.files.list("/tmp"):
print(entry.name, entry.type)
# 5. 暂停沙箱(beta 接口,2.10 版本 SDK 里 pause 挂在 beta_pause 下)
sandbox.beta_pause()
# 6. 恢复沙箱(推荐:connect)。重新连接一个不是当前进程刚建出来的已有
# 沙箱时,要手动补上 E2b-Sandbox-Id/E2b-Sandbox-Port 两个 header,
# envd 的请求才能被正确路由到这台沙箱,见本文档第二章"连接沙箱执行
# 命令"一节。connect() 没有内置 409 退避重试(快照可能还在异步上传),
# 需要自己包一层重试。
for _ in range(10):
try:
sandbox = Sandbox.connect(
sandbox.sandbox_id,
timeout=300,
headers={
"E2b-Sandbox-Id": sandbox.sandbox_id,
"E2b-Sandbox-Port": str(ConnectionConfig.envd_port),
},
)
break
except SandboxException as e:
if "in progress" not in str(e):
raise
time.sleep(3)
# 备选:resume(已弃用,仅在需要钉节点恢复时用,e2b SDK 未覆盖,直接
# 调 REST:POST /sandboxes/{sandboxID}/resume -d '{"timeout":300,"nodeName":"node-7"}')
# 7. 设置超时(覆盖式,从当前时刻起重新计时)
sandbox.set_timeout(7200)
# 8. 续期保活(平台扩展接口,e2b SDK 未覆盖)
api.post(f"/sandboxes/{sandbox.sandbox_id}/refreshes", json={"duration": 300})
# 9. 创建快照(平台扩展接口,e2b SDK 未覆盖)
snap = api.post(f"/sandboxes/{sandbox.sandbox_id}/snapshots").json()
print("snapshot:", snap["snapshotId"])
# 10. 销毁沙箱
sandbox.kill()
# ---- 模板管理(e2b SDK 未覆盖以下几步,统一用 httpx 直连 REST) ----
# 11. 列表模板(可选)
templates = api.get("/templates", params={"keyword": "my-python-tpl"}).json()
print("total:", len(templates))
# 12. 注册模板(v3,走 alias 复用:alias 已存在时复用同一个 templateID,只产生新 buildID)
resp = api.post("/v3/templates", json={"alias": "my-python-tpl", "cpuCount": 2, "memoryMB": 1024}).json()
new_template_id, build_id = resp["templateID"], resp["buildID"]
print("registered:", new_template_id, build_id)
# 13. 上传 build context:先查该内容 hash 是否已在缓存里,命中则跳过上传
with open("build-context.tar", "rb") as f:
build_context = f.read()
file_hash = hashlib.sha256(build_context).hexdigest()
upload = api.get(f"/templates/{new_template_id}/files/{file_hash}").json()
# 防御性检查:这个查询本身也可能失败(比如后端 500,响应体是
# {"code":500,"message":"..."},没有 present 字段)——不能假设"没有
# present 就是已经存在,可以跳过上传",那样会把一次真正的查询失败悄悄
# 当成"缓存命中"放过,直接进入第 14 步触发构建,构建大概率会因为 layer
# 文件缺失而失败,且报错和这里完全对不上,排查成本更高。这里选择直接把
# 原始响应暴露出来,方便照着 upload 里的 code/message 去查后端,而不是
# 吞掉继续跑。
if "present" not in upload:
raise RuntimeError(f"failed to check build context upload cache: {upload}")
if not upload["present"]:
httpx.put(upload["url"], content=build_context)
# 14. 触发真正构建
api.post(
f"/v2/templates/{new_template_id}/builds/{build_id}",
json={
"fromImage": "registry.example.com/base-images/python:3.11",
"steps": [{"type": "RUN", "args": ["pip install numpy pandas"]}],
},
)
# 15. 轮询构建状态,直到 ready(building/waiting/ready/error 四种取值)
while True:
status = api.get(f"/templates/{new_template_id}/builds/{build_id}/status").json()
if status["status"] == "ready":
break
if status["status"] == "error":
raise RuntimeError("build failed")
time.sleep(3)
print("build ready")
# 16. build 详情(legacy,与 status 分开:一次性返回完整日志,不用于轮询)
build_detail = api.get(f"/templates/{new_template_id}/builds/{build_id}").json()
# 17. 模板详情(含构建历史)
detail = api.get(f"/templates/{new_template_id}").json()
print("builds:", len(detail.get("builds", [])))
# 18. 用新模板创建沙箱,验证构建产物可用
new_sandbox = Sandbox.create(template=new_template_id)
print("created:", new_sandbox.sandbox_id)
new_sandbox.kill()
# 19. 删除模板(清理;若有其它模板的 fromTemplate 还指向它会 409,需先处理下游模板)
api.delete(f"/templates/{new_template_id}")