Glac API
本文是 Glac API 的参考:每个调用接受什么、返回什么。CLI、SDK 和 MCP 服务器调用的都是这个 API。下载这份描述文件 (OpenAPI 3.1)。
调用和文档一样按产品编排:方块(调用都在 /v1/apps 下:API 里的应用就是方块,此外还有应用的版本和实例)、沙箱、云盘(以及云盘底层的存储卷),最后是平台本身的调用:项目、密钥、机密、账户和账单、事件。
地址。所有路径都以 https://api.glac.ai 为前缀。
一个 API 统管多台机器。沙箱及其中的一切都运行在机器上(调用里称为节点)。涉及沙箱、命令、文件、端口链接、快照或存储卷的调用,API 会转发给持有该对象的机器;创建这类对象的调用,则转发给 API 选定的机器;列表调用会询问每台机器,再把结果汇总。机器返回什么,API 就原样返回什么:这些调用下列出的正是机器的响应。项目的机密由 API 为所有机器统一保管,响应格式与机器的相同。API 还会给这些调用加上几种自己的响应,每个调用都一样:见转发给机器的调用。其余调用(应用、项目、密钥、支出上限、事件、账户和账单、控制台)由 API 自己处理。
身份验证
除控制台的注册和登录 (/v1/auth/…) 外,每个调用都要在 Authorization: Bearer <credential> 中提供凭据。
- 项目的密钥 (
ak_…)。在控制台的 API 密钥页面创建,权限范围和有效期由你选择;也可以用POST /v1/keys创建。密钥只在创建时显示一次;DELETE /v1/keys/{id}会立即撤销它。没有密钥返回 401missing_api_key,密钥错误返回invalid_api_key,密钥过期返回expired_api_key。 - 单个沙箱的令牌 (
st_…),由POST /v1/sandboxes/{id}/tokens签发:有效期短,带有url、exec、files、tty中的部分权限范围。在GET请求中也可以改用查询参数sandbox_token传递,供浏览器使用,因为浏览器无法为EventSource或 WebSocket 设置请求头。用令牌发起的调用,API 会转发给令牌所属沙箱的机器,由该机器校验令牌:用令牌访问其他任何沙箱都返回 404,调用需要项目密钥的接口则返回 403needs_api_key。 - 控制台会话。控制台自己的调用(
/v1/me…、/v1/accounts/{id}…)使用POST /v1/auth/sign-in设置的 Cookie。带上这个 Cookie,再用X-Account和X-Project指定账户和项目,控制台就能像持有该项目全部权限范围的密钥一样调用 API 的其余部分,但不超出用户在该账户中的角色所允许的范围。
权限范围。密钥可以拥有七种权限范围中的若干种:read、write、exec、files、tty、url 和 admin(各自开放哪些操作)。创建时不指定 scopes 的密钥拥有除 admin 以外的全部权限范围。每个调用的说明都以所需的权限范围结尾(“所需权限范围:read。”),缺少该权限范围的密钥会收到 403 key_scope,错误信息会指明缺少哪一个。
项目
一个账户有一个或多个项目,每个密钥属于其中一个项目。用密钥创建的一切都归该项目所有,调用也只能看到该项目的沙箱、快照、存储卷、机密、应用等:其他项目的资源对它来说并不存在 (404)。在一个项目内,同一类资源的名称不能重复。
id 的前缀表明它指的是什么:sbx-、exe-(命令)、snap-、vol-、sshkey-。凡是路径、字段或 sandbox_id 筛选条件接受沙箱、快照、存储卷或 SSH 密钥的 id 的地方,也都接受它的名称(端口链接的主机名中除外)。
幂等键
任何 POST 都可以带上 Idempotency-Key(1 到 255 个字符)。用相同的键和相同的请求体再次发起同一调用,会得到与第一次相同的响应,并带有 Idempotent-Replayed: true,操作不会重复执行。同一个键配上不同的请求体返回 409 idempotency_key_reused;第一次调用仍在执行时,第二次调用最多等待 60 秒,之后返回 409 idempotency_in_progress。响应保留 24 小时;409 和 5xx 响应以及事件流不保留,调用会重新执行。命令的键(…/run、…/exec)随命令保存,沙箱保留该命令多久,键就保留多久。带着原来的键再次发送的创建请求,会发往第一次去往的机器,并得到与第一次相同的规格,无论账户届时是什么套餐。
分页
列表调用接受 limit(1 到 1000;默认 100)和 cursor,返回 {"data": […], "next_cursor": "…"},最新的在前(目录条目和机密按名称排序)。最后一页的 next_cursor 为 null;否则把它作为 cursor 传入,即可获取下一页。游标指向的是位置而不是页:期间有资源创建或删除,它依然有效。
错误
所有错误的格式都相同:
{"error": {"type": "not_found", "code": "sandbox_not_found", "message": "no sandbox sbx-…",
"param": null, "retryable": false, "request_id": "req-…", "docs_url": null}}
type 决定状态码:invalid_request 400、unauthenticated 401、payment_required 402、forbidden 403、not_found 404、conflict 409、too_large 413、too_many_requests 429、internal 500、bad_gateway 502、unavailable 503、timeout 504。code 指明具体是哪种失败:每个调用都按状态码列出了它的错误码。message 用文字向人说明错误;param 指出出错的字段或查询参数;retryable 表示再次发起同一调用是否可能成功,能确定何时可以重试时,Retry-After 响应头会给出时间。调用不接受的字段或查询参数会被拒绝(400 unknown_field、unknown_parameter),错误信息会列出它接受哪些。request_id 是该调用的 id,也会出现在响应的 X-Request-Id 头中:你也可以自己发送 X-Request-Id(1 到 64 个字母、数字、.、_ 或 -),它会被采用。500 只说明出了故障:请提供它的 request_id。错误带有 docs_url 时,它指向错误页面上该错误码的说明,那里写明了遇到这些拒绝该怎么办:套餐上限、项目的云盘和存储卷数量、账户被冻结、用户被锁定。
事件流和终端
命令的输出可以作为事件流 (text/event-stream) 读取:带 stream: true(或 Accept: text/event-stream)调用 POST /v1/run 和 POST /v1/sandboxes/{id}/run,或者调用 GET /v1/sandboxes/{id}/exec/{exec_id}/output。每个事件有一行 event:;输出事件还有一行 id:(事件最后一个字节之后的位置,标准输出和标准错误合并计数);最后是一行 JSON 格式的 data:。没有任何动静时,每 15 秒发送一条注释 : waiting。读取方落后太多、所需的输出沙箱已不再保留时,会收到 gap。连接断开后要接着读,带上 Last-Event-ID(或 from)请求 …/exec/{exec_id}/output。
事件如下:
start:由事件流启动命令时的第一个事件。数据:{id, "object": "exec", sandbox_id, command}。stdout:一段标准输出。id是它最后一个字节之后的位置。数据:{text}或{base64}。stderr:一段标准错误。id是它最后一个字节之后的位置。数据:{text}或{base64}。gap:输出已不再保留:读取方请求的,或即将收到的那部分。事件流从available_from继续。数据:{from, available_from}。exit:最后一个事件:命令已结束。id是输出最后一个字节之后的位置。数据:{id, "object": "exec", sandbox_id, command, tty, status, …}(与GET /v1/sandboxes/{id}/exec/{exec_id}的响应相同)。end:命令仍在运行时,以follow=false请求的事件流的最后一个事件。id是已读到的位置。数据:{"status": "running", offset}。error:最后一个事件:命令丢失了(沙箱从磁盘重启、被恢复或被删除),或者沙箱连同进程迁到了另一个节点,命令在那里继续运行:此时为sandbox_moved(可重试),带有to、epoch和offset(它前一个事件的 id);通过控制平面再次请求同一事件流,并把Last-Event-ID(或from)设为offset,就会在新节点上接着读,不漏也不重。数据就是其他所有错误使用的错误对象。数据:{error, …},见错误。
终端 (GET /v1/sandboxes/{id}/exec/{exec_id}/tty) 是一个 WebSocket。二进制消息是原始字节:一个方向是键入的内容,另一个方向是终端显示的内容。文本消息是 JSON:
{"type": "eof"}:客户端发往服务端的文本消息:客户端不再发送数据。客户机半关闭它与对端的连接;对端之后发来的数据仍会传回。{"type": "listening", path}:服务端发往客户端的第一条消息:套接字在沙箱中的位置。{"type": "reset"}:服务端发往客户端:到该套接字的所有连接都已断开(沙箱进入了休眠);套接字本身仍在。{"type": "resize", cols, rows}:客户端发往服务端的文本消息:终端现在是这个尺寸。超出范围的尺寸会被直接丢弃,不作响应。{"type": "signal", …}:客户端发往服务端的文本消息:向命令的进程组发送一个信号。{"type": "exit", status, exit_code, signal}:服务端发往客户端的文本消息:命令已结束。随后是一个关闭帧 (1000)。{"type": "error", error, …}:服务端发往客户端的文本消息:命令丢失了(沙箱被重启、恢复或删除),或者沙箱连同进程迁到了另一个节点,命令在那里继续运行(sandbox_moved,可重试,带有to、epoch和offset:通过控制平面重新连接,并把from设为offset),或者客户端的某次按键或某条消息被拒绝(比如spend_limit_reached)。error就是其他所有错误使用的错误对象。随后是一个关闭帧:sandbox_moved之后为 1012,原因为sandbox_moved;其他情况为 1000。沙箱迁移期间键入的按键会等到迁移结束:迁移失败时,按键照常输入。{"type": "gap", from, available_from}:服务端发往客户端的文本消息:客户端落后的程度超出了沙箱为命令保留的输出(最后 16 MiB),终端接下来显示的内容从available_from开始,而不是从原来的位置接着显示。它在后续内容之前发送,与事件流的gap事件一样。终端显示的字节不带位置信息:如果客户端自己计数,以便连接断开后用from重新连接(例如 SSH 网关,它的会话在节点守护进程重启后依然存在),就应从available_from继续计数。
转发给机器的调用
这些调用下列出的,都是持有该对象的机器的响应。下面这些响应由 API 自己给出,要么在询问任何机器之前,要么取代机器的响应:
- 400
invalid_json:POST /v1/sandboxes、/v1/run和/v1/volumes的请求体不是 JSON 对象(没有请求体视为{})。在询问任何机器之前就被拒绝,无论有没有可用的机器。 - 400
unknown_field:创建时给出了project_address:沙箱在项目网络中的地址只能由 API 分配。沙箱的drives中某个挂载项带有它不支持的字段。创建或修改不是浏览器会话的沙箱时给出了proxy:只有浏览器会话才能设置代理(POST /v1/browsers、PATCH /v1/browsers/{id})。 - 400
unknown_region:创建时给出的region不是现有的区域之一。 - 400
invalid_field:从备份 (from_backup) 创建存储卷时还给出了size_bytes、shared或from_backup_index(这样的存储卷沿用备份来源存储卷的大小和类型),或者给出的region与备份的不同。沙箱的drives:不是列表、path不是绝对路径、mode不是read_write(以只读方式挂载云盘快照以后才会支持)。 - 400
missing_field:创建沙箱时,drives中的某个云盘缺少drive或path(param为drives[i].drive、drives[i].path)。 - 400
reserved_metadata:创建沙箱(POST /v1/sandboxes、/v1/run)或修改沙箱 (PATCH /v1/sandboxes/{id}) 时,metadata中有键product(param为metadata.product):沙箱作为什么产品出售(比如浏览器会话)由 API 自己记录,不能由元数据指定。 - 400
unknown_parameter:列表调用带了该列表不接受的查询参数,而此时没有机器响应(没有机器,或者都连不上),由 API 根据上次看到的情况返回。 - 402
account_in_debt:账户为超出套餐的存储欠费。所有调用都会被拒绝,而且在检查密钥的权限范围之前就拒绝,以下除外:读取账户、账单、用量和备份,付款,从备份创建存储卷,以及(除非 API 另有配置)列出、读取和删除账户存储的沙箱、快照和存储卷。错误信息会说明欠款金额和付款方式。 - 403
key_scope:密钥没有调用所需的权限范围;错误信息会指明是哪一个。 - 403
account_held:账户因付款问题被冻结:除GET /v1/account外,用它的密钥和它的沙箱令牌发起的所有调用都会被拒绝。错误信息会提示你联系支持团队。 - 403
spend_limit_reached:项目或沙箱已达到支出上限:不能再创建沙箱、分叉、一次性运行、存储卷(从备份创建的除外)或快照,被上限冻结的沙箱也不会被唤醒。 - 403
balance_used_up:账户套餐包含的 CPU 时间或内存已用完,账户也没有余额支付更多用量:同样的那些调用会被拒绝。 - 403
plan_size_exceeded:超出了账户套餐允许的沙箱规格:可能是请求的limits(param为limits.vcpus、limits.memory_bytes,两者都超出时为limits),也可能是创建、分叉或回滚所依据的快照或模板的规格。不指定规格的沙箱,创建时不会超过套餐允许的最大规格。 - 403
plan_cap_reached:超出了账户同时可拥有的数量(param指明是哪一项;GET /v1/account的caps中列有各项上限):运行中的沙箱数、它们的 vCPU 或内存(创建、一次性运行、分叉或唤醒沙箱的调用),休眠中的沙箱数,或最近一分钟、一小时内创建的沙箱数(这两项为retryable,并带有Retry-After)。休眠和删除从不会被拒绝。 - 403
volume_limit_reached:POST /v1/volumes时,存储卷的个数、单个大小或总大小超出了套餐允许项目保留的上限(具体数字见GET /v1/billing)。 - 403
drive_limit_reached:针对带有云盘的沙箱快照:从快照创建的沙箱(POST /v1/sandboxes和POST /v1/run,带上snapshot)、带云盘的沙箱分叉出的每个副本,以及回滚到快照的沙箱,都会为每个云盘得到一个新云盘;如果这会让项目超出套餐允许保留的云盘上限(个数、单个大小、总大小),就会在询问任何机器之前被拒绝,与通过POST /v1/drives/{id}/fork分叉云盘时一样。创建和回滚时param为snapshot;错误信息会说明套餐的上限、项目现有的量,以及这次操作之后的量。 - 403
secret_limit_reached:PUT /v1/secrets/{name}时:机密个数超出套餐允许项目保留的上限,值超出允许的大小(param为value),或者会让某个应用的实例获得的项目机密超过单个沙箱允许的数量。 - 403
proxy_not_on_plan:在免费版上修改浏览器会话 (PATCH /v1/sandboxes/{id}) 并为它设置proxy,而免费版不提供代理。 - 403
account_share_reached:账户新建的东西(沙箱、一次性运行、分叉出的副本、快照、存储卷或云盘)会让它在所在机器的数据盘上超出自己的份额:一个账户最多只能占用一台机器数据盘的一部分(默认四分之一,除非机器另有设置)。API 会把新建的东西放到账户还有空间的机器上,所有机器都没有空间时才返回这个错误。 - 403
storage_quota_reached:免费版账户保存的数据已达到免费版允许的上限(15 GB 存储卷数据:存储卷中的数据,加上快照和休眠沙箱保存的数据):新建存储卷或快照会被拒绝(param为storage),直到保存的数据减少,或者升级到付费版。 - 404
backup_not_found:从项目中不存在的备份创建存储卷(param为from_backup)。 - 404
drive_not_found:创建沙箱时,drives指定了项目中不存在的云盘(param为drives[i].drive)。 - 404
snapshot_not_found:创建沙箱时,drives挂载了项目中不存在的云盘快照(param为drives[i].snapshot)。 - 409
not_on_one_machine:沙箱与它要挂载的存储卷,或者与创建、回滚所依据的快照所挂载的共享存储卷,不在同一台机器上。还没有被任何沙箱用过的存储卷则会迁到沙箱所在的机器。对于沙箱的drives:它挂载的云盘和以只读方式挂载的云盘快照来自多个区域,或者来自与创建时指定的不同的区域(param为region);快照在创建时其云盘所在的区域读取,无论该云盘之后是否已被删除。 - 409
snapshot_not_replicated:要在创建快照的机器以外的机器上(原机器不在线,或存储卷在别处)从快照创建沙箱或回滚到快照,而快照还没有复制到对象存储:它的last_replication_status说明复制进展。param为snapshot。 - 409
not_loadable:调用所到的机器无法按快照发布时的样子用它创建虚拟机(Firecracker 构建不同、设备设置不同、规格更大、磁盘大小不同);错误信息会说明有哪些不同。 - 409
backup_not_ready、backup_unusable:从仍在创建中或已失败的备份创建存储卷。 - 409
too_many_drives、key_drive、duplicate_volume、drive_attached:创建带drives的沙箱时:云盘和存储卷合计超过 8 个 (too_many_drives);挂载了键控实例的云盘 (key_drive);同一个云盘指定了两次 (duplicate_volume);云盘已被另一个沙箱持有,它会留在原处 (drive_attached)。 - 409
not_copied、store_unfenced:创建带drives的沙箱时:云盘不在沙箱能创建的那台机器上,而它所在的机器不向对象存储复制任何数据 (not_copied),或者它复制到的对象存储不经手任何迁移 (store_unfenced)。 - 409
managed_by_database:创建沙箱(POST /v1/sandboxes、/v1/run)或修改其挂载(PATCH /v1/sandboxes/{id},带上volumes)时,以读写方式挂载了数据库的云盘(param指明是哪一个);删除、修改或恢复作为数据库实例的沙箱;对作为数据库云盘的存储卷做任何修改。构成数据库的资源只能通过/v1/databases修改。 - 409
managed_by_installation:与上一条相同,只是针对构成托管应用安装的资源:创建沙箱或修改其挂载时,以读写方式挂载了某个安装的云盘;删除、修改或恢复作为安装中应用实例的沙箱;对作为安装云盘的存储卷做任何修改。构成安装的资源只能通过/v1/installations修改。 - 409
checkpoints_unavailable:沙箱开启了checkpoint(沙箱自己的设置,或键控实例从其应用继承的设置),但没有机器支持检查点,或者创建它所依据的资源所在的机器不支持;以及在不支持检查点的机器上用PATCH开启检查点。param为checkpoint。如果支持检查点的机器都不在线或已满,创建会改为返回 503no_capacity,可重试 (retryable)。 - 429
quota_used_up:免费版账户已用完今天(按 UTC 计)的 CPU 时间或内存配额,而调用还要用到它:创建、一次性运行、分叉或会唤醒沙箱的调用都会被拒绝,直到 UTC 时间 00:00,无论账户里有多少余额(Retry-After给出距那时的秒数)。 - 502
fetch_failed:在创建快照的机器以外的机器上,从快照创建沙箱或回滚到快照:该机器无法从对象存储取回快照。错误信息中附有它的原话。 - 503
node_unavailable:持有该对象的机器连不上,或者还没有项目最新的机密:不会丢失任何东西,机器恢复后再次发起同一调用即可 (retryable)。 - 503
no_capacity:没有机器有空间容纳新的沙箱或存储卷(从备份创建存储卷时:备份所在区域没有能创建存储卷的机器)。 - 503
moving:沙箱正在迁往另一台机器:迁移结束之前,对它的所有调用都返回此错误(retryable,Retry-After: 1)。 - 503
upgrading:该沙箱是一个键控实例,它的机器正在把它切换到应用的另一个版本 (POST /v1/apps/{app}/instances/{key}/upgrade):在新版本就绪或实例恢复原样之前,对它的所有调用都返回此错误(retryable,Retry-After: 1)。 - 503
creation_cancelled、creation_cleanup_failed:机器创建了对象,但 API 无法记录:调用创建的东西已被删除 (creation_cancelled),或者没能全部删除(creation_cleanup_failed:错误信息会列出剩下的部分,删除之前它们会一直计费)。 - 503
creation_replay_incomplete、creation_state_unavailable、creation_replay_record_failed:带着原来的Idempotency-Key再次发送的创建请求,机器会再次给出第一次的响应:但响应中提到的东西已不全在了(creation_replay_incomplete;换一个键会重新创建),无法询问机器(creation_state_unavailable,retryable),或者东西都在但 API 无法记录 (creation_replay_record_failed)。 - 503
copy_behind、drive_not_brought、not_served_here、unavailable:沙箱drives中的每个云盘都要先迁到创建沙箱的机器上:对象存储中的云盘还不是最新状态 (copy_behind);云盘原来的机器没有释放它,或者新机器无法接手 (drive_not_brought);该区域能接收它的机器都不保存云盘 (not_served_here);另一个调用正在迁移它 (unavailable)。除not_served_here外都可重试 (retryable)。 - 503
standby:这个 API 副本不是当前提供服务的那个:响应会指明正在服务的副本 (active)。可重试 (retryable),带有Retry-After。
所在机器连不上的沙箱、快照或存储卷,仍然可以列出和读取,内容是 API 上次看到的样子,status 为 unavailable(对沙箱而言,awake_because 和 sleep_tier 为 null)。创建调用接受 region(na、eu、ap),由 API 自己读取,不会转发;沙箱和存储卷的 region 表示它们所在的区域。
Version: 0.1.0
Servers
- https://api.glac.ai