当某个操作被拒绝时,原因会以一个简短的机器可读代码返回。控制台把其中大部分渲染成你所用控制台语言的 toast 提示;它还不认识的代码则显示 API 自己的英文消息。API 以 JSON 返回。
{
"detail": {
"code": "OFFER_UNAVAILABLE",
"message": "This offer is no longer available"
}
}
少数代码会在 code 和 message 之外附带额外字段——DISK_TOO_LARGE 会返回 max_disk_gb,MACHINE_DISK_EXHAUSTED 会返回 available_disk_gb,客户端据此可以直接把滑块收到上限以内并重试,不必再发一次请求。
这两个值不能互换,夹错了会无限循环。max_disk_gb 是机器的总磁盘容量;available_disk_gb 是扣掉同机其他租户以及 50 GB 主机预留之后剩下的。所以一台机器可以通过 DISK_TOO_LARGE 而仍然返回 MACHINE_DISK_EXHAUSTED——即使是一台空机器,因为那份预留永远不出售。拿到 available_disk_gb 时请按它来夹。
身份验证与组织范围
| 状态码 | 代码 | 成因 | 处理方式 |
|---|
| 401 | TOKEN_INVALID | 没有 Authorization 头、令牌格式错误、API 密钥未知或已吊销(成员被移出组织时,他创建的个人密钥随之吊销),或者把 API 密钥发到了账户级接口 | 发送有效的 bearer 令牌。/v1/me 和 /v1/ssh-keys 这类账户级接口需要已登录的会话,而不是 shk_ 密钥 |
| 401 | TOKEN_EXPIRED | 会话令牌已过期 | 重新登录。无人值守的脚本请改用 API 密钥 |
| 403 | NOT_A_MEMBER | X-Org-Id 指向了一个你不属于的组织 | 去掉该请求头以使用你的个人组织,或者请管理员发送邀请 |
| 403 | ORG_MISMATCH | X-Org-Id 与该 API 密钥所属的组织不一致 | 移除该请求头,或改用属于该组织的密钥 |
| 403 | ADMIN_REQUIRED | 以成员身份或使用 API 密钥执行了仅管理员可做的操作——充值订单、发票信息、邀请、成员管理、团队 API 密钥 | 请管理员操作,或在已登录的管理员会话中重新执行 |
| 400 | INVALID_ORG_ID | X-Org-Id 不是合法的 id | 从 GET /v1/orgs 中复制 id |
| 403 | MFA_REQUIRED | 你的账户已启用双重认证,而这个会话令牌还没有完成第二步验证 | 重新登录并输入身份验证器应用里的验证码。控制台会自己把你带到那一步。API 密钥不受影响 |
| 503 | AUTH_UNAVAILABLE | API 暂时无法核验你的登录——签名密钥或双重认证状态读取不到 | 稍后重试。你的会话仍然有效 |
每个请求都有的限制
| 状态码 | 代码 | 成因 | 处理方式 |
|---|
| 429 | RATE_LIMITED | 同一个调用方的请求太多——按登录用户、API 密钥计,匿名请求按 IP 地址计。每个 /v1 请求都计入每分钟 600 次的额度;POST、PUT、PATCH 和 DELETE 还另计入每分钟 60 次。某个 IP 地址的请求每分钟因未通过认证被拒超过 60 次时,来自它、还没成功认证过的凭据也会收到这个错误 | 等 Retry-After 响应头里的秒数(响应体里也有 retry_after_s),再重试。/v1/health 和支付通知永远不受限制 |
| 413 | PAYLOAD_TOO_LARGE | 请求体超过了端点接受的大小:大多数端点 1 MiB,/v1/templates 下为 4 MiB。响应中包含 limit_bytes | 发小一点的请求体。模板的说明和启动脚本各有自己的上限,远在这个范围之内 |
| 状态码 | 代码 | 成因 | 处理方式 |
|---|
| 402 | INSUFFICIENT_BALANCE | 部署或启动实例时,你的余额不足以覆盖组织在这个实例运行后一小时的费用 —— 这个实例的 GPU 加磁盘,加上运行中、启动中或正在创建的实例的 GPU 加磁盘,以及已停止实例的磁盘。余额大于零是不够的。响应中包含 balance_micro 和 required_micro | 充值余额。只有管理员才能充值 |
| 409 | OFFER_UNAVAILABLE | 从你打开报价到你点击部署之间,该形态的最后一份空闲已被拿走——或者就在你的部署预留的那一刻被另一次部署拿走 | 返回市场另选一份报价。卡片上的数字只是一个快照,真正决定的是部署本身 |
| 404 | OFFER_NOT_FOUND | 传给 GET /v1/offers/{id} 的 id 不存在,或该报价已从目录中撤下。针对未知报价的部署返回的则是 OFFER_UNAVAILABLE | 重新列出报价,使用当前有效的 id |
| 422 | DISK_TOO_LARGE | 请求的 disk_gb 超过了该机器的总容量。响应中包含 max_disk_gb | 请求不超过 max_disk_gb 的容量,或改选磁盘更大的机器 |
| 409 | MACHINE_IN_MAINTENANCE | 这台机器已被安排下线,时间太近,无法在上面启动实例。响应中包含 next_maintenance | 改选别的机器,或等这段维护窗口过去再来。更远的窗口不会阻止部署——它会显示在报价上 |
| 409 | MACHINE_DISK_EXHAUSTED | 该机器的磁盘已经许诺给其他租户了。一台机器同时承载多位租户,磁盘是按剩余量而不是总量出售的——所以这个错误可能紧跟在一个通过了 DISK_TOO_LARGE 的 disk_gb 之后。响应中包含 available_disk_gb 和 requested_disk_gb | 按不超过 available_disk_gb 重试,或改选别的机器。available_disk_gb 可能为 0 |
| 422 | OFFER_NOT_VM_CAPABLE | 一个 vm 启动模式的模板被投放到了无法运行虚拟机的机器上 | 把该模板部署到支持虚拟机的报价上,或改用其他启动模式的模板 |
| 404 | TEMPLATE_NOT_FOUND | 模板 id 不存在,或该模板已被删除 | 使用模板库中的模板,或使用别人给你的 hash_id |
| 404 | SSH_KEY_NOT_FOUND | 该 ssh_key_id 不是这个凭据可以附加的密钥:不是你自己的,或者——用 team API 密钥时——不是现任成员的 | 用会话令牌调 GET /v1/ssh-keys 列出并使用你自己账户下的 id。密钥属于用户,不属于组织 |
| 409 | INSTANCE_LIMIT_REACHED | 组织里尚未销毁的实例已有 25 个——stopped、error 和正在销毁的也算在内。响应中包含 limit | 销毁不再需要的实例,包括处于 error 的。控制台提示为「你已达到此组织的实例数量上限。请先销毁一个。」 |
| 422 | TEMPLATE_COMING_SOON | 模板被标记为即将推出:镜像还没有发布,比如 Superheat CN 上来自 NVIDIA NGC(nvcr.io)的模板 | 换一个模板。控制台里这种模板无法选择,所以通常只有 API 调用会遇到 |
| 422 | TEMPLATE_CUDA_TOO_NEW | 模板固定了较旧的 CUDA 栈(cuda_max),而这台机器的 CUDA 版本更高 | 选一台 CUDA 版本不超过模板上限的机器,或换一个模板 |
| 422 | TEMPLATE_ARCH_UNSUPPORTED | 模板声明了 amd64/x86_64 以外的架构;Superheat 的机器都是 x86_64 | 改用 x86_64 镜像的模板 |
| 422 | TEMPLATE_REQUIRES_OWNER | 模板属于别的组织,并且从带凭据的私有镜像仓库拉取,只有拥有它的组织能部署。控制台提示为「此模板从私有镜像仓库拉取,只能由拥有它的组织部署。」 | 把它复制到你的组织并填入你自己的仓库凭据(前提是你有权拉取那个镜像),或请拥有者部署 |
| 409 | OFFER_NOT_PINNABLE | 报价所在的机器没有在机群控制平面登记,部署无法固定到它上面。市场列表不会显示这种报价,一般只有拿着旧的 offer_id 才会遇到。控制台提示为「此机器目前无法部署。请换一台。」 | 重新列出报价另选一份 |
本节的每个代码都在保留任何东西之前返回,所以被拒绝的部署什么都不会创建,也不产生任何费用。
实例操作
| 状态码 | 代码 | 成因 | 处理方式 |
|---|
| 404 | INSTANCE_NOT_FOUND | 该实例 id 在本次请求所限定的组织中不存在 | 检查 id,并确认你处在正确的组织中 |
| 409 | INVALID_STATE | 该操作在实例当前状态下不合法——例如停止一个正在停止的实例,或启动一个正在运行的实例。销毁在 destroyed 以外的任何状态下都允许 | 等实例进入 running 或 stopped 后再重试。实例的 allowed_actions 列出当前合法的操作。参见 实例状态 |
| 409 | HOST_UNREACHABLE | 你启动了一个已停止的实例,而它所在的机器当前未连接。实例仍然是 stopped | 稍后再启动 |
| 409 | START_REFUSED | 主机拒绝恢复这个实例,或者已不再持有它的工作负载——消息会说明是哪一种。实例仍然是 stopped | 如果主机已不再持有工作负载,销毁实例并重新部署;否则稍后再试 |
| 503 | START_UNAVAILABLE | 暂时无法完成恢复。实例仍然是 stopped;如果恢复其实已经落地,它会自己进入 running | 稍后重试 |
| 409 | CAPACITY_UNAVAILABLE | 你启动的实例在余额用完时 GPU 被释放了(被自动停止的,或者你之前自己停止的),而它在那台机器上原来用的 GPU 现在正被别人使用,或者已停止服务。实例仍然是 stopped,磁盘完好。控制台提示为「此实例使用的 GPU 目前正被他人使用,无法启动。实例仍处于已停止状态,磁盘完好——请稍后再试。」 | 稍后再启动:启动成功之前拿不到磁盘上的数据。一直不成功的话,唯一的出路是销毁实例(磁盘会一起删除)后重新部署。参见 GPU 被释放之后 |
实例为何失败
这些不是对某个请求的拒绝,而是实例落入 error,或者一次停止或启动没有生效、实例回到 running 或 stopped 时所带的 error_code。实例页面会用你的控制台语言给出解释 —— 出错的实例显示在出了什么问题下,运行中或已停止的实例显示在上一个操作没有完成下 —— 代码本身写在下面,方便你把它报给支持人员。在其他状态下,页面会隐藏这两个字段,因为它们描述的是更早的状态。通过 API,它们就是实例上的 error_code 和 error_message。
平台自己设置的代码。控制台的解释会取代 API 返回的英文 error_message:
error_code | 控制台显示的内容 |
|---|
provision_failed | 调度器没有接受这次部署。未产生任何费用——请销毁后重试。 |
provision_lost | 部署从未到达调度器。未产生任何费用——请销毁后重试。 |
provision_timed_out | 工作负载始终没有被放置到机器上。请销毁后重试。 |
dispatch_job_missing | 调度器中已不存在此实例的工作负载。请销毁后重新部署。 |
stop_timed_out | 机器没有确认停止。实例仍在运行并计费,系统正在重试停止。 |
start_timed_out | 机器没有确认恢复。实例仍处于已停止状态——请再次尝试启动。 |
workload_completed | 工作负载已自行退出,其 GPU 已释放——请销毁此实例。 |
machine_unreachable | 启动时此实例所在的机器未连接。实例仍处于已停止状态——请稍后重试。 |
start_refused | 机器拒绝恢复此实例。实例仍处于已停止状态。 |
workload_gone | 机器上已不存在此实例的工作负载。请销毁后重新部署。 |
dispatch_unavailable | 无法连接调度器来恢复此实例。实例仍处于已停止状态——请稍后重试。 |
gateway_unavailable | 无法连接机器来恢复此实例。实例仍处于已停止状态——请稍后重试。 |
gpus_unavailable | 此实例在其机器上使用的 GPU 目前正被他人使用。实例仍处于已停止状态,磁盘完好——请稍后再启动,或将其销毁。 |
workload_gone 有两种来源:启动时机器已不再持有这个工作负载,或者实例停止期间它的容器在机器上不见了。后一种情况下实例会转到 error,不再计费,只能销毁。
调度器针对机器上的工作负载记录的代码。控制台先显示解释,并在其下以详情显示主机自己的 error_message,它往往就是确切原因 —— 一个退出状态、一个缺失的镜像:
error_code | 控制台显示的内容 |
|---|
workload_failed | 容器在机器上运行失败。 |
workload_lost | 机器失去了对容器的追踪。 |
workload_exit_nonzero | 容器因错误退出。 |
workload_terminal | 工作负载在机器上已经结束。 |
start_timeout | 容器没有在规定时间内启动。很大的镜像下载时间可能超出允许范围。 |
deadline_exceeded | 工作负载达到了时间上限。 |
machine_lost | 机器已离线。 |
machine_gone | 机器已离开机群。 |
unknown_to_gateway | 机器上没有此工作负载的记录。 |
placement_error | 无法把工作负载放置到机器上。 |
drain | 机器因维护已停止服务。 |
manual_reschedule | 支持人员已把此工作负载迁移到另一台机器。 |
image_not_provisioning_capable | 此镜像无法以该模板的启动模式启动。 |
launch_mode_unsupported | 没有机器支持该模板的启动模式。 |
vm_not_supported | 这台机器无法运行虚拟机。 |
两张表里都没有的代码仍然会原样显示,并附上 API 发来的消息。接下来该怎么做,见故障排查。
浏览报价
| 状态码 | 代码 | 成因 | 处理方式 |
|---|
| 422 | INVALID_CURSOR | GET /v1/offers 的 cursor 不是 API 发出的,或者是为另一种 sort 发出的 | 用同一个 sort 原样传回上一页的 next_cursor,或者省略它从头开始。参见报价 |
| 状态码 | 代码 | 成因 | 处理方式 |
|---|
| 403 | TEMPLATE_IMMUTABLE | 你试图编辑或删除由 Superheat 精选维护的系统模板 | 先复制一份,再编辑你的副本 |
| 409 | TEMPLATE_NAME_TAKEN | 你所在组织中已有同名模板(不区分大小写) | 换一个名称 |
| 404 | TEMPLATE_NOT_FOUND | 该 id 或 hash_id 无法解析到你可见的模板 | 确认分享链接完整且是最新的——启动配置一变,hash_id 就会变 |
| 422 | INVALID_TAB | 模板列表接口的 tab 查询参数不属于模板库的任何一个标签页 | 使用受支持的 tab 取值 |
| 422 | INVALID_CURSOR | 模板列表接口的 cursor 不是 API 发出的 | 原样传回上一页的 next_cursor,或者不传,从头开始 |
| 409 | PUBLIC_TEMPLATE_LIMIT_REACHED | 你的组织已经有 50 个公开模板。响应中包含 limit | 先把其中一个设为私有或删掉,再发布新的 |
SSH 密钥
| 状态码 | 代码 | 成因 | 处理方式 |
|---|
| 422 | INVALID_SSH_KEY | 该值不是一行 OpenSSH 公钥、密钥内容不是有效的 base64,或者密钥类型不受支持 | 把 .pub 文件的全部内容粘贴成一行,以密钥类型开头。参见 受支持的密钥类型 |
| 409 | DUPLICATE_SSH_KEY | 你已经添加过指纹相同的密钥 | 直接用已有的那把密钥,或先删除旧条目 |
| 404 | SSH_KEY_NOT_FOUND | 该密钥 id 不是你的 | 密钥按用户隔离。别的成员的密钥你永远看不到 |
团队与邀请
| 状态码 | 代码 | 成因 | 处理方式 |
|---|
| 400 | PERSONAL_ORG | 你试图把别人邀请进个人组织 | 先创建一个真正的组织,再从那里发邀请 |
| 404 | INVITE_NOT_FOUND | 邀请令牌不正确、该邀请已被撤回,或者它发往的邮箱与你当前登录的邮箱不同 | 用受邀的邮箱登录,或请管理员重新发一个链接 |
| 410 | INVITE_REVOKED | 发出邀请的人已不再是这个组织的管理员,所以邀请已失效 | 请现任管理员重新发一个邀请 |
| 410 | INVITE_USED | 该邀请已被接受 | 请对方重新发一个邀请链接 |
| 410 | INVITE_EXPIRED | 该邀请已过期 | 请对方重新发一个邀请链接 |
| 404 | MEMBER_NOT_FOUND | 该用户不是本组织的成员 | 刷新团队页面——对方可能已被移除 |
| 400 | LAST_ADMIN | 移除或降级该成员会让组织没有任何管理员 | 先把另一名成员提升为管理员 |
| 状态码 | 代码 | 成因 | 处理方式 |
|---|
| 422 | INVALID_AMOUNT | amount_minor 既不是套餐金额,也不在 ¥20 到 ¥10,000 之间。响应中包含以分计的 min_minor 和 max_minor(默认 2000 和 1000000) | 选择 ¥50、¥100 或 ¥500 的套餐,或填写 min_minor 到 max_minor 之间的金额 |
| 422 | PROVIDER_NOT_ENABLED | 本站没有开通所请求的支付方式:充值开放之前的任何订单,或者域名启用 HTTPS 之前的 wechat | 从 GET /v1/billing/config 返回的 providers 里选一种。列表为空时,暂时无法在线充值 |
| 503 | PROVIDER_NOT_CONFIGURED | 该支付方式已开通,但服务端的凭据不完整 | 这是服务端的配置问题,不是你的请求有误。稍后重试;持续出现请写信到 finance@superheat.cloud |
| 429 | TOO_MANY_OPEN_ORDERS | 你已经持有五个未付款、未失效的二维码 | 付掉其中一个,或者等它们失效(15 分钟)。在同一个组织里用同样的方式和金额再次发起(开具发票的订单除外),只要原来的二维码还剩两分钟以上,就会拿回原来那个。这五个的上限按你本人计算,跨所有组织 |
| 422 | INVOICE_PROFILE_REQUIRED | 以 want_invoice: true 下单,但组织还没有保存发票信息 | 先用 PUT /v1/billing/invoice-profile 保存发票信息,或在控制台里打开开具发票并填好表单 |
| 404 | ORDER_NOT_FOUND | 该订单 id 在本次请求所限定的组织中不存在 | 检查 id,并确认你处在下单时的那个组织中 |
| 403 | ADMIN_REQUIRED | 由成员或使用 API 密钥创建了充值订单,或读写了发票信息 | 请组织管理员来充值。参见 充值余额 |
| 422 | INVALID_TIME_ZONE | GET /v1/billing/spend-daily 收到的 tz 不是 API 认识的 IANA 时区名 | 发送 Asia/Shanghai 这样的名称,或者不传 tz(按 UTC) |
API 密钥
| 状态码 | 代码 | 成因 | 处理方式 |
|---|
| 404 | API_KEY_NOT_FOUND | 该密钥 id 在本组织中不存在 | 重新列出你的密钥并使用当前有效的 id |
| 403 | ADMIN_REQUIRED | API 密钥试图创建或吊销另一个 API 密钥,或成员试图管理团队级密钥 | 在已登录的会话中创建和吊销密钥 |
校验失败的请求
如果请求的正文或查询字符串不符合 schema,它会在到达上述任何检查之前就被拒绝。这类请求返回 422,detail 下是一组字段错误,而不是一个 code:
{
"detail": [
{
"type": "less_than_equal",
"loc": ["body", "disk_gb"],
"msg": "Input should be less than or equal to 20000"
}
]
}
loc 条目指出了出问题的字段。值得记住的边界是:disk_gb 介于 10 和 20,000 之间,label 最长 64 个字符,日志接口上的 tail 介于 1 和 1,000 之间。模板和部署上:环境变量最多 100 条(模板上和 env_overrides 里各自计算),每个值最长 4,096 个字符;端口最多 32 个;约束选择器最多 32 个;cuda_min、cuda_max 和 arch 最长 32 个字符;模板列表和报价列表的 limit 介于 1 和 200 之间;报价列表的 q 最长 100 个字符。
相关内容