实例
一个实例,就是一份租下的报价在跑一个模板。这些端点能做实例页面能做的一切。
部署实例
curl -s -X POST "$SUPERHEAT_API/v1/instances" \
-H "Authorization: Bearer $SUPERHEAT_KEY" \
-H "Content-Type: application/json" \
-d '{
"offer_id": "5d2f9c31-8b64-4c0e-9a77-2e0f1b6a4c11",
"template_id": "b1f4a6d2-90c7-5e33-8a2b-1d7c4e0f9a56",
"disk_gb": 60,
"label": "sft-run-14",
"ssh_key_id": "7a3c1e08-42bd-4f19-9d63-5b8e0c2a1f40",
"env_overrides": [
{"key": "HF_TOKEN", "value": "hf_...", "secret": true}
]
}'
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
offer_id | uuid | 是 | 必须仍然是 available,否则调用返回 409 OFFER_UNAVAILABLE |
template_id | uuid | 是 | 系统模板、公开模板,或你的组织拥有的模板 |
disk_gb | 整数 | 是 | 介于 10 和 20000 之间,且不大于主机的 machine.disk_gb |
label | 字符串,最多 64 个字符 | 否 | 显示在控制台列表里。默认为 null。 |
ssh_key_id | uuid | 否 | 用已登录会话或 personal API 密钥时:你自己的一把 SSH 密钥。用 team API 密钥时:当前任何一位组织成员的密钥 —— 绝不能是已离开成员的,包括已离开的密钥创建者本人。不传的话,实例上没有授权密钥,你就 SSH 不进去。 |
env_overrides | {key, value, secret} 数组 | 否 | 按 key 匹配,叠加到模板的 env 之上。值为 null 的条目会被丢弃。最多 100 条;key 最长 256 个字符,值最长 4,096 个字符。 |
环境变量覆盖项会叠加在模板的 env 之上,但平台自己的覆盖层压过这两者。你无法遮蔽 JUPYTER_TOKEN、GPU_COUNT 或实例访问令牌(SUPERHEAT_ACCESS_TOKEN,以及任何携带它的模板变量)。
响应是 201 加上实例对象。此时其中的 ssh_host 和 ssh_port 还是 null,等工作负载起来之后才会填上。计费从工作负载真正进入 running 时开始,而不是这次调用的那一刻。
部署一个实例时,以及之后每次启动一个实例时,余额都必须覆盖组织全部消耗一小时的费用:新实例的 GPU 和磁盘,加上每个运行中、启动中或创建中实例的 GPU 和磁盘,以及每个已停止实例的磁盘。但什么都不会被预留或冻结:实例运行期间 GPU 时间按秒计费,停止期间磁盘按 GB 每小时计费。余额为零时,运行中的实例会被自动停止;在零或以下持续 24 小时之后,一旦余额到了 −$5 或更低,已停止和出错的实例会被销毁。见计费如何运作。
部署被拒绝时
被拒绝的部署会返回下列代码之一。响应体在 code 和 message 之外,还会带上这里列出的额外字段。
| 状态码 | 代码 | 何时出现 |
|---|---|---|
| 402 | INSUFFICIENT_BALANCE | 加上这个实例之后,余额不足以覆盖组织一小时的消耗。带有 balance_micro 和 required_micro |
| 402 | PAYMENTS_FROZEN | 该组织还有一笔未解决的付款争议 |
| 409 | OFFER_UNAVAILABLE | 报价不存在、已下架,或已经没有空闲单元——包括在你的部署预留期间被另一个部署拿走的单元 |
| 409 | MACHINE_IN_MAINTENANCE | 这台机器很快就要按计划下线,来不及在上面启动。带有 next_maintenance |
| 409 | MACHINE_DISK_EXHAUSTED | 这台机器的磁盘已经许诺给其他租户了。带有 available_disk_gb 和 requested_disk_gb |
| 409 | INSTANCE_LIMIT_REACHED | 该组织已经有 25 个未销毁的实例。已停止和处于 error 的实例也算在内。带有 limit |
| 409 | OFFER_NOT_PINNABLE | 这台机器没有在控制平面注册,所以部署无法固定到它上面 |
| 422 | DISK_TOO_LARGE | disk_gb 超过了这台机器的磁盘总量。带有 max_disk_gb |
| 422 | OFFER_NOT_VM_CAPABLE | 把 vm 模板投放到了无法运行虚拟机的机器上 |
| 422 | TEMPLATE_COMING_SOON | 模板的镜像还没有发布 |
| 422 | TEMPLATE_CUDA_TOO_NEW | 模板最高只支持到它的 cuda_max,而这台机器报告的 CUDA 版本更新 |
| 422 | TEMPLATE_ARCH_UNSUPPORTED | 模板需要 x86_64 以外的硬件。所有 Superheat 主机都是 x86_64 |
| 422 | TEMPLATE_REQUIRES_OWNER | 模板从私有镜像仓库拉取,只有拥有它的组织才能部署 |
| 404 | TEMPLATE_NOT_FOUND | 模板不存在、对你的组织不可见,或已被停用 |
| 404 | SSH_KEY_NOT_FOUND | ssh_key_id 不是这个凭据可以附加的密钥(见上面的 ssh_key_id),或者不存在 |
这张表里的每个代码都是在保留任何东西之前返回的:被拒绝的部署不会留下实例,也不产生费用。未注册机器上的报价不会出现在 GET /v1/offers 里,所以通常只有拿着早先保存的报价 id 时才会遇到 OFFER_NOT_PINNABLE。
每个代码该怎么处理,见错误代码。
实例字段
{
"id": "e08b5f27-1c4a-4d90-b3e6-72a9d1f45c83",
"label": "sft-run-14",
"status": "creating",
"disk_gb": 60,
"price_per_hour_micro": 2290000,
"storage_price_per_gb_hour_micro": 250,
"ssh_host": null,
"ssh_port": null,
"ssh_user": "root",
"jupyter_url": null,
"open_url": null,
"access_token": null,
"error_code": null,
"error_message": null,
"allowed_actions": ["destroy"],
"gpus_released": false,
"gpu_release": null,
"gpu_release_requested_at": null,
"stop_releases_gpus": false,
"hold_price_per_hour_micro": null,
"stop_hold_price_per_hour_micro": null,
"suspended_hold_price_per_hour_micro": null,
"created_at": "2026-07-24T11:12:03.771Z",
"started_at": null,
"status_changed_at": "2026-07-24T11:12:03.771Z",
"destroyed_at": null,
"spend_micro": 0,
"deployed_by": "you@example.com",
"deployed_via_api_key": null,
"offer": { "id": "5d2f9c31-8b64-4c0e-9a77-2e0f1b6a4c11" },
"template": { "id": "b1f4a6d2-90c7-5e33-8a2b-1d7c4e0f9a56" }
}
offer 和 template 就是报价和模板里记录的对象;这里只做了缩略展示。内嵌的模板里 readme 和 onstart 为 null——需要时请用 GET /v1/templates/{id} 获取。
| 字段 | 类型 | 含义 |
|---|---|---|
status | 字符串 | creating、starting、running、stopping、stopped、destroying、destroyed、error 之一。见实例状态。 |
disk_gb | 整数 | 你申请的磁盘,停止期间按 GB 每小时计费 |
price_per_hour_micro | 整数 | 租用时从报价快照下来的费率,对应整个切片 |
storage_price_per_gb_hour_micro | 整数 | 租用时快照下来的磁盘费率 |
ssh_host, ssh_port, ssh_user | 字符串或 null、整数或 null、字符串 | 连接目标。ssh_host 是这台 GPU 机器的公网 IP 地址。端口从主机发布的范围里分配,绝不会是 22。工作负载起来之前,主机和端口都是 null。 |
jupyter_url | 字符串或 null | JupyterLab 的入口地址,等 jupyter 模板上报之后才有。以 ?token= 带着访问令牌 |
open_url | 字符串或 null | 模板打开按钮指向的地址,上报之后才有。打开按钮的端口由访问代理守护时带有 ?token= |
access_token | 字符串或 null | 实例的访问令牌:JupyterLab 令牌、模板应用端口所要求的 bearer 密钥,以及模板预先创建的任何账号的密码。模板不使用令牌时为 null,实例销毁后也为 null。见端口与打开按钮 |
error_code、error_message | 字符串或 null | 实例为什么处于 error——例如 provision_failed 或 workload_completed——或者在 running 或 stopped 的实例上,说明为什么一次停止或启动没有生效(stop_timed_out、start_timed_out、gpus_unavailable)。其他情况下为 null。控制台的实例页面会用你的控制台语言解释这个代码;它认识的每个代码都列在错误码里 |
allowed_actions | 数组 | 当前状态下 API 接受 start、stop、destroy 中的哪几个 |
gpus_released | 布尔值 | 已停止实例的 GPU 被释放回机器后为 true——余额到了零,或者一笔支付争议冻结了组织的付款。它的容器和磁盘还在,启动必须取回同样的 GPU:只要另一位租户正在使用其中一块,就会返回 409 CAPACITY_UNAVAILABLE。参见停止与销毁 |
gpu_release | 字符串或 null | 释放进行到了哪一步:pending(已决定,机器尚未确认)、released,或者 unsupported(机器无法释放 GPU,或者实例运行的是 vm 模板,所以它们继续保留,实例只收取存储费,和你自己停止之后一样)。没有决定释放时为 null |
gpu_release_requested_at | 时间戳或 null | 决定释放的时间 |
stop_releases_gpus | 布尔值 | 当一笔支付争议冻结了组织的付款时为 true:此时请求的停止会在机器支持的情况下释放 GPU,而不是继续保留它们 |
hold_price_per_hour_micro、stop_hold_price_per_hour_micro、suspended_hold_price_per_hour_micro | 整数或 null | 为已停止实例的 GPU 保留费预留的字段。目前不收取这种费用——已停止的实例只收取存储费——所以这三个字段都是 null |
created_at, started_at, status_changed_at, destroyed_at | 时间戳 | started_at 在工作负载第一次运行时被写入 |
spend_micro | 整数 | 这个实例迄今已结算的费用。它随每一次结算变动,并且和账本记录在同一个事务里写入,所以它始终等于这个实例在交易列表上所有扣费之和 |
deployed_by | 字符串 | 部署它的团队成员的邮箱。用 team API 密钥部署的实例,这里显示的是密钥本身:它的名称和前缀,例如 CI deploys (shk_1a2b3c4d…) |
deployed_via_api_key | 对象或 null | 部署这个实例所用的 API 密钥 —— id、name、kind(team 或 personal)、prefix 和 revoked —— 控制台部署的实例,以及在开始记录这一项之前部署的实例,为 null |
列出实例
curl -s "$SUPERHEAT_API/v1/instances" \
-H "Authorization: Bearer $SUPERHEAT_KEY"
返回当前组织的 {"items": [...]},最新的在前。除非你传 include_destroyed=true,否则已销毁的实例不在其中。
获取单个实例
curl -s "$SUPERHEAT_API/v1/instances/e08b5f27-1c4a-4d90-b3e6-72a9d1f45c83" \
-H "Authorization: Bearer $SUPERHEAT_KEY"
未知的 id,以及属于别的组织的实例,都返回 404 INSTANCE_NOT_FOUND。
停止、启动和销毁
| 请求 | 效果 | 允许的起始状态 |
|---|---|---|
POST /v1/instances/{id}/stop | 关掉容器,保留磁盘,并占住 GPU 槽位——或者在一笔支付争议冻结了组织的付款期间,在机器支持的情况下释放 GPU(stop_releases_gpus)。状态先变为 stopping,再变为 stopped。 | running |
POST /v1/instances/{id}/start | 在同一块磁盘和同样的 GPU 上把已停止的实例拉回来;gpus_released 的实例必须重新取回这些 GPU。状态先变为 starting,再变为 running。 | stopped |
DELETE /v1/instances/{id} | 拆掉实例并释放切片。状态先变为 destroying,再变为 destroyed。对已经处于 destroying 的实例,它会重新发送销毁请求并返回 200。 | 除 destroyed 外的所有状态 |
curl -s -X POST "$SUPERHEAT_API/v1/instances/$INSTANCE_ID/stop" \
-H "Authorization: Bearer $SUPERHEAT_KEY"
这三个都会返回处于新的过渡状态的完整实例对象——状态转换是异步完成的,所以要一直轮询到状态稳定下来。
在不允许的状态下调用其中任何一个,都会返回 409 INVALID_STATE,消息里会点明当前状态,例如 Cannot start an instance while it is running。实例上的 allowed_actions 列出了此刻合法的操作。启动时会重新检查余额,余额必须覆盖这个实例运行时组织一小时的消耗,否则返回 402 INSUFFICIENT_BALANCE。付款争议未解决期间,启动会返回 402 PAYMENTS_FROZEN。
主机接不了启动时,启动也会失败。以下每种情况下,实例都会被放回 stopped,磁盘还在——名额也在,除非它的 GPU 已被释放:
| 状态码 | 代码 | 何时出现 |
|---|---|---|
| 409 | CAPACITY_UNAVAILABLE | 实例的 GPU 已被释放,而另一位租户现在正在使用其中一块。稍后再试,或者先把数据拷出来再销毁它 |
| 409 | HOST_UNREACHABLE | 实例所在的机器没有连接。稍后再试 |
| 409 | START_REFUSED | 主机拒绝了恢复,或者已经不再持有工作负载——消息会说明是哪一种。第二种情况下请销毁实例并重新部署 |
| 503 | START_UNAVAILABLE | 恢复现在无法完成。稍等片刻再试 |
停止和销毁会先被记录下来,然后才发给主机,平台会一直重新发送,直到主机完成为止。主机在 30 分钟后仍未确认的停止,会把实例放回 running,按 GPU 计费,并继续重试停止;error_code 为 stop_timed_out。
同一次停止、启动或销毁的每一次重发都带着同一个身份标识,而你发起的每个新请求都会得到一个新的。所以在你已经重新启动实例之后才重发的那次停止,会被识别为过时的,不会再把它停一次;平台在启动已经生效之后才重试的那次启动,也不会再被当作拒绝来回复。你自己的调用不需要做任何额外的事:如果想让拆除再发一次,就再发一次 DELETE,它会被当作一个新请求。
DELETE 是终局的:容器、磁盘以及上面的一切都没了,报价重新回到市场上。想让数据等着你,就改用停止。见停止与销毁的区别。
日志
curl -s "$SUPERHEAT_API/v1/instances/$INSTANCE_ID/logs?tail=50" \
-H "Authorization: Bearer $SUPERHEAT_KEY"
{
"lines": [
"[2026-07-24 11:12:07] superheat-agent: pulling image vastai/pytorch",
"[2026-07-24 11:12:11] superheat-agent: image ready, creating container",
"[2026-07-24 11:12:13] nvidia-smi: detected 1 GPU(s), driver 570.86",
"[2026-07-24 11:12:14] sshd: listening on 0.0.0.0:41207",
"[2026-07-24 11:12:15] superheat-agent: instance is ready"
]
}
| 参数 | 取值范围 | 默认值 |
|---|---|---|
tail | 1 到 1000 | 200 |
超出该范围的值会被以 422 拒绝。在工作负载第一次跑起来之前 lines 都是空的,所以 creating 状态的实例什么都不返回。没有流式端点;请轮询这一个——控制台每三秒刷新一次。见日志。
从部署到连接,端到端
这是从一个空 shell 到一个 SSH 会话的完整路径。它假定你有 jq、已经在控制台里添加了 SSH 密钥,并且余额里有钱。
1. 设好你的凭据。SSH 密钥属于你的账户而不是组织,所以列出它们需要会话令牌;其余一切都靠 API 密钥跑。
export SUPERHEAT_API="https://<your-superheat-api-host>"
export SUPERHEAT_KEY="shk_your_key_here"
AUTH=(-H "Authorization: Bearer $SUPERHEAT_KEY")
2. 找出 SSH 密钥的 id,用已登录浏览器里的会话令牌:
curl -s "$SUPERHEAT_API/v1/ssh-keys" \
-H "Authorization: Bearer $SESSION_TOKEN" \
| jq -r '.items[] | "\(.id) \(.name) \(.fingerprint)"'
7a3c1e08-42bd-4f19-9d63-5b8e0c2a1f40 laptop SHA256:Yx1r0oW2fS7Tq8kJ3mN4pB6vC9dE0gH2iK5lM8nP1qR
SSH_KEY_ID="7a3c1e08-42bd-4f19-9d63-5b8e0c2a1f40"
3. 挑最便宜的单卡 H100 切片:
OFFER_ID=$(curl -s "$SUPERHEAT_API/v1/offers?gpu_model=H100%20SXM&num_gpus=1&sort=price_asc" \
"${AUTH[@]}" | jq -r '.items[0].id')
4. 挑一个 SSH 模板:
TEMPLATE_ID=$(curl -s "$SUPERHEAT_API/v1/templates?tab=recommended&mode=ssh" \
"${AUTH[@]}" | jq -r '.items[0].id')
5. 部署:
INSTANCE_ID=$(curl -s -X POST "$SUPERHEAT_API/v1/instances" \
"${AUTH[@]}" -H "Content-Type: application/json" \
-d "{\"offer_id\":\"$OFFER_ID\",\"template_id\":\"$TEMPLATE_ID\",\"disk_gb\":60,\"label\":\"api-demo\",\"ssh_key_id\":\"$SSH_KEY_ID\"}" \
| jq -r '.id')
6. 等它变成 running:
while :; do
STATUS=$(curl -s "$SUPERHEAT_API/v1/instances/$INSTANCE_ID" "${AUTH[@]}" | jq -r '.status')
echo "$STATUS"
case "$STATUS" in
running) break ;;
error|destroyed) exit 1 ;;
esac
sleep 3
done
7. 直接从实例本身拼出连接命令,而不是自己去还原它——端口是按实例分配的:
curl -s "$SUPERHEAT_API/v1/instances/$INSTANCE_ID" "${AUTH[@]}" \
| jq -r '"ssh -p \(.ssh_port) \(.ssh_user)@\(.ssh_host)"'
ssh -p 41207 root@203.0.113.14
8. 连上去,看看 GPU:
ssh -p 41207 root@203.0.113.14 nvidia-smi
9. 中途歇一会儿就停止,彻底干完就销毁:
curl -s -X POST "$SUPERHEAT_API/v1/instances/$INSTANCE_ID/stop" "${AUTH[@]}" | jq -r '.status'
curl -s -X DELETE "$SUPERHEAT_API/v1/instances/$INSTANCE_ID" "${AUTH[@]}" | jq -r '.status'