REST API 调用流程

从报价到创建、执行、审计和销毁;默认使用生产 Gateway,每一步都能用 curl 或 API Explorer 验证。

Sandbox 是有生命周期的计费资源,不是普通的一次性 API。一个完整调用必须包含 资源选择、状态等待、业务执行、审计和清理。仅收到创建或销毁操作的 HTTP 200/202, 不代表实例已经完成状态转换。

环境与鉴权

环境Base URL用途
生产(默认)https://sandbox.xapi.to正式工作负载、文档 Explorer 与生成代码默认目标
测试https://sandbox.test.xapi.to集成测试、发布前验收;需要显式切换

所有需要账户身份的接口接受:

XAPI-Key: sk-xapi-...

测试 Gateway 仍会连接真实供应商、创建真实资源并从测试账户结算。不要把 API Key 写进 URL、源码、远端命令或截图;每次创建后都必须销毁实例。

完整生命周期

1

查看规格与环境

调用 GET /v1/offeringsGET /v1/environments,确认资源、能力、暂停支持和价格。 这两个接口不会创建实例。

2

按需求报价

调用 POST /v1/quotes,声明 exec / files / ports、CPU、内存、GPU、区域和 maxEstimatedHourlyUsd。保存返回的 quoteIdexpiresAt

3

创建实例

调用 POST /v1/sandboxes,把 quoteId 放入 selection。为每个业务任务生成稳定的 idempotencyKey;如果创建响应丢失,重试必须复用同一个 key。

4

等待 RUNNING

创建接口返回 202 Accepted 后,轮询 GET /v1/sandboxes/{id}。 只有 observedState=RUNNING 才能执行命令或访问文件。

5

执行工作负载

根据 offering 能力调用 commands、files、ports 或 extensions。Shell 成功同时要求 HTTP 成功且响应中的 exitCode=0

6

检查审计

运行中可先读取审计定位问题,但此时 usage segment 和 billing period 通常仍开放, 不能作为最终结算证据。

7

销毁并确认终态

finally 或 shell trap 中调用 POST /v1/sandboxes/{id}/terminate,随后继续轮询, 直到 observedState=TERMINATED 或 provider-terminal FAILED。进入终态后重新读取 operationseventsusageSegmentsbillingPeriods,核对 TERMINATE 操作、 终态事件、已关闭用量段、已结算账期和服务端 totalCost;最后用 sandbox-history?state=ACTIVE 确认本次实例没有残留。

在文档中试调

打开侧边栏的 全部 REST API 与测试。17 个接口按四类集中在同一页,组件内可以填写 参数、发送 Test 请求并查看完整响应。需要语言代码或完整 Schema 时再进入逐接口 API Reference

1

确认 Production 环境并鉴权

页面默认选择 Production;在鉴权框输入生产账户的 XAPI-Key。需要验收测试时再显式 切换到 Test 并改用测试账户 Key。不要把 Key 放进 URL 或请求体。

2

报价并保存 quoteId

调用获取 Sandbox 报价。它不会创建资源,但会验证鉴权、预算和规格选择。

3

创建并保存 instance id

quoteId 放入 selection.quoteId,使用稳定且唯一的 idempotencyKey 创建实例。

4

等待 RUNNING 后执行

使用查询实例详情轮询;只有 observedState=RUNNING 后才调用命令、文件或端口接口。

5

终止、等待和审计

无论中间是否成功,都调用终止实例,确认终态后查询审计与历史。

直接测试报价接口

下面是内嵌的真实请求构建器。它只生成报价,不创建实例:

Explorer 会根据 OpenAPI 示例自动生成 curl、TypeScript、Python 等调用代码。页面不会 把你的 API Key写入仓库;但共享屏幕或录屏时仍应清空鉴权框。

一键测试脚本

下面的脚本默认连接生产 Gateway,验证 quote → create → wait → exec → audit → terminate。 需要测试环境时,显式把 base_url 改为 https://sandbox.test.xapi.to 并使用测试 Key。 它要求本机安装 curljq,并通过 trap 尽力清理实例。

#!/usr/bin/env bash
set -euo pipefail
base_url="https://sandbox.xapi.to"
read -rsp "XAPI production key: " XAPI_KEY
printf '\n'
instance_id=""
cleanup() {
if [[ -n "$instance_id" ]]; then
curl -fsS -X POST "$base_url/v1/sandboxes/$instance_id/terminate" \
-H "XAPI-Key: $XAPI_KEY" \
-H "Content-Type: application/json" \
-d "{\"idempotencyKey\":\"docs-terminate-$instance_id\"}" >/dev/null || true
fi
}
trap cleanup EXIT INT TERM
quote_json="$(curl -fsS -X POST "$base_url/v1/quotes" \
-H "XAPI-Key: $XAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"requirements": {"capabilities": ["exec", "files"]},
"maxEstimatedHourlyUsd": "0.20"
}')"
quote_id="$(jq -er '.quoteId' <<<"$quote_json")"
create_key="docs-create-$(date +%s)-$RANDOM"
create_json="$(curl -fsS -X POST "$base_url/v1/sandboxes" \
-H "XAPI-Key: $XAPI_KEY" \
-H "Content-Type: application/json" \
-d "{
\"selection\": {\"quoteId\": \"$quote_id\"},
\"metadata\": {\"client\": \"docs-curl\", \"purpose\": \"sandbox-api-test\"},
\"idempotencyKey\": \"$create_key\",
\"policy\": {\"resumeOnAccess\": false}
}")"
instance_id="$(jq -er '.id' <<<"$create_json")"
printf 'instance=%s\n' "$instance_id"
for _ in $(seq 1 90); do
detail="$(curl -fsS "$base_url/v1/sandboxes/$instance_id" \
-H "XAPI-Key: $XAPI_KEY")"
state="$(jq -r '.observedState' <<<"$detail")"
[[ "$state" == "RUNNING" ]] && break
[[ "$state" == "FAILED" || "$state" == "TERMINATED" ]] && {
jq . <<<"$detail"; exit 1;
}
sleep 2
done
[[ "${state:-}" == "RUNNING" ]] || { echo "RUNNING timeout" >&2; exit 1; }
exec_json="$(curl -fsS -X POST "$base_url/v1/sandboxes/$instance_id/commands" \
-H "XAPI-Key: $XAPI_KEY" \
-H "Content-Type: application/json" \
-d '{"command":"python3 -c \"print(6 * 7)\"","timeoutSeconds":60}')"
jq -e '.exitCode == 0 and (.stdout | contains("42"))' <<<"$exec_json" >/dev/null
cleanup
trap - EXIT INT TERM
for _ in $(seq 1 90); do
final_json="$(curl -fsS "$base_url/v1/sandboxes/$instance_id" \
-H "XAPI-Key: $XAPI_KEY")"
final_state="$(jq -r '.observedState' <<<"$final_json")"
[[ "$final_state" == "TERMINATED" || "$final_state" == "FAILED" ]] && break
sleep 2
done
jq '{id,observedState,totalCost,auditCounts}' <<<"$final_json"
[[ "$final_state" == "TERMINATED" || "$final_state" == "FAILED" ]]
# 终态之后再读取审计;此前的用量段/账期仍可能是开放状态,不能作为结算证据。
for _ in $(seq 1 30); do
operations="$(curl -fsS "$base_url/v1/sandboxes/$instance_id/audit?kind=operations&page=1&pageSize=100" \
-H "XAPI-Key: $XAPI_KEY")"
events="$(curl -fsS "$base_url/v1/sandboxes/$instance_id/audit?kind=events&page=1&pageSize=100" \
-H "XAPI-Key: $XAPI_KEY")"
usage="$(curl -fsS "$base_url/v1/sandboxes/$instance_id/audit?kind=usageSegments&page=1&pageSize=100" \
-H "XAPI-Key: $XAPI_KEY")"
billing="$(curl -fsS "$base_url/v1/sandboxes/$instance_id/audit?kind=billingPeriods&page=1&pageSize=100" \
-H "XAPI-Key: $XAPI_KEY")"
if jq -e 'any(.items[]; .type == "TERMINATE" and .status == "SUCCEEDED")' <<<"$operations" >/dev/null \
&& jq -e 'any(.items[]; .currentState == "TERMINATED" or .currentState == "FAILED")' <<<"$events" >/dev/null \
&& jq -e '(.total > 0) and all(.items[]; .endsAt != null and .status != "OPEN")' <<<"$usage" >/dev/null \
&& jq -e '(.total > 0) and all(.items[]; .endedAt != null and .status != "RESERVED")' <<<"$billing" >/dev/null; then
break
fi
sleep 2
done
jq -e 'any(.items[]; .type == "TERMINATE" and .status == "SUCCEEDED")' <<<"$operations" >/dev/null
jq -e 'any(.items[]; .currentState == "TERMINATED" or .currentState == "FAILED")' <<<"$events" >/dev/null
jq -e '(.total > 0) and all(.items[]; .endsAt != null and .status != "OPEN")' <<<"$usage" >/dev/null
jq -e '(.total > 0) and all(.items[]; .endedAt != null and .status != "RESERVED")' <<<"$billing" >/dev/null
active_json="$(curl -fsS "$base_url/v1/sandbox-history?state=ACTIVE&page=1&pageSize=100" \
-H "XAPI-Key: $XAPI_KEY")"
jq -e --arg id "$instance_id" 'all(.items[]; .id != $id)' <<<"$active_json" >/dev/null
printf 'audit and cleanup verified for %s; totalCost=%s\n' \
"$instance_id" "$(jq -r '.totalCost' <<<"$final_json")"

推荐:CLI 安全封装

如果只是验证 Sandbox 是否能执行命令,优先使用 CLI。它已封装报价、等待和 finally 清理:

XAPI_SANDBOX_HOST=sandbox.xapi.to \
npx xapi-to sandbox run \
--capabilities exec,files \
--max-hourly-usd 0.20 \
--command 'python3 -c "print(6 * 7)"' \
--format pretty

成功条件不只是 stdout 包含 42,还应确认 cleanup.statefinalStatetotalCost 以及 Console 中对应的操作和账期记录。

常见错误

状态含义处理
400参数错误或没有兼容规格重新查看 offerings,减少能力或调整预算
401API Key 缺失或无效检查鉴权框/请求头,不要把 key 放进 URL
402余额或密钥限额不足在测试账户充值或调整密钥额度
409当前状态不允许操作查询实例状态,等待正在执行的状态变更结束
429创建频率过高不要循环创建;复用相同 idempotencyKey 并退避

需要 OpenAI Agents SDK + DeepSeek 的组合流程,请继续阅读 AI + Sandbox Agent