Glac API
This is the reference of the Glac API: every call, what it takes and what it answers. The CLI, the SDKs and the MCP server call it. Download this description (OpenAPI 3.1).
The calls are arranged by product, as the docs are: Cube (its calls are /v1/apps: an app, which is what a Cube is, its versions and its instances), Sandbox, Drive (with the volumes underneath Drives), and then the platform's own calls: the project, keys, secrets, the account and its bill, events.
Address. Every path is under https://api.glac.ai.
One API in front of many machines. Sandboxes, and what is in them, live on machines (nodes, in the calls' own words). A call about a sandbox, a command, a file, a port's link, a snapshot or a volume is passed by the API to the machine that holds the thing, and a call that makes one to a machine the API picks; a list asks every machine and puts their answers together. What the machine answers comes back unchanged: that is what each of these calls lists. A project's secrets are kept by the API for all its machines, and answered in the same words. The API adds a few answers of its own to these calls, the same for each: Calls passed to a machine. The other calls (apps, projects, keys, spend limits, events, the account and its bill, the console) are the API's own.
Authentication
Every call but the console's sign-up and sign-in (/v1/auth/…) takes a credential, in Authorization: Bearer <credential>.
- A project's key (
ak_…). Make one in the console, on its API keys page, with the scopes and the expiry you choose, or withPOST /v1/keys. The key is shown once, when it is made;DELETE /v1/keys/{id}revokes it at once. No key is 401missing_api_key, a wrong oneinvalid_api_key, one past its expiryexpired_api_key. - A token for one sandbox (
st_…), fromPOST /v1/sandboxes/{id}/tokens: short-lived, with some of the scopesurl,exec,filesandtty. On aGETit may be the query parametersandbox_tokeninstead, for a browser, which cannot set headers on anEventSourceor a WebSocket. The API passes a call made with a token to the machine of its sandbox, which checks the token: to a token every other sandbox is 404, and a call that takes a project's key is 403needs_api_key. - The console's session. The console's own calls (
/v1/me…,/v1/accounts/{id}…) take the cookie thatPOST /v1/auth/sign-insets. With it, andX-AccountandX-Projectnaming the account and the project, the console calls the rest of the API as a key of that project with every scope, within what the user's role in the account allows.
Scopes. A key allows some of seven scopes: read, write, exec, files, tty, url and admin (what each one opens). A key made without scopes has all of them but admin. Each call ends its description with the scope it takes ("Takes read."), and a key without that scope is 403 key_scope, whose message names it.
Projects
An account has one or more projects, and a key belongs to one of them. Everything a call makes with it is that project's, and a call sees only that project's sandboxes, snapshots, volumes, secrets, apps and the rest: another project's are not there (404). Names are unique within a project, for each kind of thing.
Ids begin with what they name: sbx-, exe- (a command), snap-, vol-, sshkey-. Wherever a path, a field or the sandbox_id filter takes the id of a sandbox, a snapshot, a volume or an SSH key, it takes its name as well (but in the host name of a link to a port).
Idempotency keys
Any POST may carry Idempotency-Key (1 to 255 characters). The same call again, with the same key and the same body, is answered as the first one was, with Idempotent-Replayed: true, and nothing is done twice. The same key with another body is 409 idempotency_key_reused; while the first call still runs, a second one waits up to 60 seconds, then is 409 idempotency_in_progress. An answer is kept for 24 hours; answers of 409 and of 5xx, and event streams, are not kept, and the call runs again. A command's key (…/run, …/exec) is kept with the command, for as long as the sandbox has it. A create sent again with its key goes to the machine the first one went to, and is given the size the first one was given, whatever the account's plan is by then.
Pagination
A list takes limit (1 to 1000; 100 when not given) and cursor, and answers {"data": […], "next_cursor": "…"}, newest first (the entries of a directory, and secrets, by name). next_cursor is null on the last page; give it as cursor for the next one. A cursor names a position, not a page: it stays good while things are made and deleted.
Errors
Every error has one shape:
{"error": {"type": "not_found", "code": "sandbox_not_found", "message": "no sandbox sbx-…",
"param": null, "retryable": false, "request_id": "req-…", "docs_url": null}}
type sets the status: 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 says which failure it is: each call lists its codes under each of its statuses. message says it in words for people; param names the field or the query parameter at fault; retryable says whether the same call again may succeed, and a Retry-After header says when, where that is known. A field or a query parameter the call does not take is refused (400 unknown_field, unknown_parameter), and the message lists what it takes. request_id is the call's id, in the answer's X-Request-Id header too: send an X-Request-Id of your own (1 to 64 letters, digits, ., _ and -) and it is used. A 500 says only that something failed: quote its request_id. docs_url, where an error has one, leads to its code on the errors page, which says what to do about the refusals that send you there: a plan's cap, a project's Drives and volumes, an account on hold, a locked user.
Event streams and terminals
A command's output can be read as a stream of events (text/event-stream): from POST /v1/run and POST /v1/sandboxes/{id}/run with stream: true (or Accept: text/event-stream), and from GET /v1/sandboxes/{id}/exec/{exec_id}/output. Each event has an event: line, for output an id: line (the position after the event's last byte, stdout and stderr counted together) and one data: line of JSON. A comment, : waiting, comes every 15 seconds while nothing happens. A reader that falls behind what the sandbox still keeps is told so with gap. To read on after a lost connection, ask …/exec/{exec_id}/output with Last-Event-ID (or from).
The events:
start: First event of a stream that started the command. Its data:{id, "object": "exec", sandbox_id, command}.stdout: A piece of standard output.idis the position after its last byte. Its data:{text}or{base64}.stderr: A piece of standard error.idis the position after its last byte. Its data:{text}or{base64}.gap: Output is no longer kept: what the reader asked for, or what it was about to get. The stream goes on fromavailable_from. Its data:{from, available_from}.exit: Last event: the command has ended.idis the position after the last byte of its output. Its data:{id, "object": "exec", sandbox_id, command, tty, status, …}(asGET /v1/sandboxes/{id}/exec/{exec_id}answers it).end: Last event of a stream asked for withfollow=falsewhile the command is still running.idis the position reached. Its data:{"status": "running", offset}.error: Last event: the command was lost (the sandbox was restarted from its disk, restored or deleted), or the sandbox was moved to another node with its processes, where the command goes on:sandbox_moved(retryable), withto,epochandoffset, the id of the event before it; the same stream asked for again through the control plane withLast-Event-ID(orfrom) set tooffsetgoes on there, missing nothing and repeating nothing. Its data is the error object of every other error. Its data:{error, …}, the error.
A terminal (GET /v1/sandboxes/{id}/exec/{exec_id}/tty) is a WebSocket. Binary messages are bytes: what is typed one way, what the terminal shows the other. Text messages are JSON:
{"type": "eof"}: Client to server, as a text message: the client sends no more. The guest half-closes its connection to the peer; what the peer still sends comes back.{"type": "listening", path}: Server to client, the first message: where the socket is in the sandbox.{"type": "reset"}: Server to client: every connection to the socket is gone (the sandbox went to sleep); the socket stays.{"type": "resize", cols, rows}: Client to server, as a text message: the terminal has this size now. Out-of-range sizes are dropped without an answer.{"type": "signal", …}: Client to server, as a text message: send the command's process group a signal.{"type": "exit", status, exit_code, signal}: Server to client, as a text message: the command has ended. A close frame (1000) follows.{"type": "error", error, …}: Server to client, as a text message: the command was lost (the sandbox was restarted, restored or deleted), or the sandbox was moved to another node with its processes, where the command goes on (sandbox_moved, retryable, withto,epochandoffset: join again through the control plane withfromset tooffset), or a key or a message of the client's was refused (spend_limit_reached, say).erroris the error object of every other error. A close frame follows: 1012 with the reasonsandbox_movedaftersandbox_moved, 1000 otherwise. A key typed while the sandbox is being moved waits for the move's end: it goes in where the move failed.{"type": "gap", from, available_from}: Server to client, as a text message: the client fell further behind than the sandbox keeps of a command's output (the last 16 MiB), and what the terminal shows next begins atavailable_from, not where it stood. Sent before what follows it, as a stream'sgapevent is. The bytes a terminal shows carry no position: a client that counts them, to join again withfromafter its connection was lost (the SSH gateway, whose sessions outlive a restart of the node's daemon), goes on counting fromavailable_from.
Calls passed to a machine
What the machine that holds a thing answers is what each of these calls lists. The API answers some of them itself, before any machine is asked or in place of the machine's answer:
- 400
invalid_json: OfPOST /v1/sandboxes,/v1/runand/v1/volumes: the body is not a JSON object (no body is{}). Refused before any machine is asked, whether or not one is there. - 400
unknown_field: A create that givesproject_address: the address of a sandbox on its project's network is the API's to give. Of a sandbox'sdrives, a field a mount does not have. A create or a change of a sandbox that is no Browser session that gives it aproxy: a proxy is a session's (POST /v1/browsers,PATCH /v1/browsers/{id}). - 400
unknown_region: A create whoseregionis not one of the regions. - 400
invalid_field: A volume made from a backup (from_backup) that also givessize_bytes,sharedorfrom_backup_index(it has the size and the kind of the volume the backup was taken of), or aregionother than the backup's. Of a sandbox'sdrives: no list, apaththat is not absolute, amodeother thanread_write(the read-only mount of a Drive's snapshot comes later). - 400
missing_field: A sandbox's create whosedrivesnames a Drive without itsdriveor itspath(paramdrives[i].drive,drives[i].path). - 400
reserved_metadata: A sandbox's create (POST /v1/sandboxes,/v1/run) or change (PATCH /v1/sandboxes/{id}) whosemetadatanames the keyproduct(parammetadata.product): what a sandbox is sold as (a Browser session) is the API's own record, and no metadata says it. - 400
unknown_parameter: A list that no machine answers (none is there, or none can be reached), which the API answers from what it last saw, given a query parameter the list does not take. - 402
account_in_debt: The account owes for what it stores past its plan. Every call is refused, before the key's scope is looked at, but reading the account, its bill, its usage and its backups, paying, making a volume from a backup, and (unless the API is run otherwise) listing, reading and deleting the sandboxes, snapshots and volumes it stores. The message says how much is owed and how to pay. - 403
key_scope: The key does not allow the scope the call takes; the message names it. - 403
account_held: The account is held for its payments: every call of its keys and of its sandboxes' tokens butGET /v1/account. The message says to write to support. - 403
spend_limit_reached: The project, or the sandbox, is at its spend limit: no sandbox, fork, one-shot run, volume (but one made from a backup) or snapshot is made, and a sandbox the limit froze is not woken. - 403
balance_used_up: The CPU time or the memory the account's plan includes is used up, and the account has no balance to pay for more: the same calls are refused. - 403
plan_size_exceeded: Larger than the account's plan lets a sandbox be: thelimitsasked for (paramlimits.vcpus,limits.memory_bytes, orlimitsfor both), or the shape of the snapshot or template it is made from, forked from or rolled back to. A sandbox that names no size is made as large as the plan allows at most. - 403
plan_cap_reached: More than the account may have at once (paramnames which;GET /v1/accounthas them undercaps): sandboxes awake, their vCPUs or their memory (of a create, a one-shot run, a fork, or a call that wakes a sandbox), sandboxes asleep, or sandboxes made in the last minute or hour (these areretryable, withRetry-After). A sleep and a delete are never refused. - 403
volume_limit_reached: OfPOST /v1/volumes: one volume more, a larger one, or more in all than the plan lets a project keep (GET /v1/billinghas the numbers). - 403
drive_limit_reached: Of a sandbox's snapshot that carries Drives: a sandbox made from it (POST /v1/sandboxesandPOST /v1/runwithsnapshot), each copy of a fork of a sandbox that has Drives, and a sandbox taken back to it get a new Drive of each, and are refused before any machine is asked where those would take the project past the Drives its plan lets it keep (how many, how large each, how large together), as a fork throughPOST /v1/drives/{id}/forkis.paramissnapshotof a create and a roll-back; the message says the plan's numbers, what the project has and what this would make. - 403
secret_limit_reached: OfPUT /v1/secrets/{name}: a secret more than the plan lets a project keep, a value larger than one may be (paramvalue), or one that would give an app's instances more of the project's secrets than one sandbox may be given. - 403
proxy_not_on_plan: A change of a Browser session (PATCH /v1/sandboxes/{id}) that gives it aproxyon the free plan, which has none. - 403
account_share_reached: Something new of the account (a sandbox, a one-shot run, a fork's copies, a snapshot, a volume or Drive) would take it past its share of the data drive of the machine it would be on: one account holds at most a share of a machine's drive (a quarter unless the machine is set otherwise). The API puts what is new on a machine where the account has the room, and passes this on when none has. - 403
storage_quota_reached: Of an account on Free that keeps as much as Free lets it keep at once (15 GB of volume data: what its volumes hold, its snapshots and sleeping sandboxes keep): a new volume or snapshot is refused (paramstorage) until it keeps less, or is on Paid. - 404
backup_not_found: A volume made from a backup that the project does not have (paramfrom_backup). - 404
drive_not_found: A sandbox's create whosedrivesnames a Drive the project does not have (paramdrives[i].drive). - 404
snapshot_not_found: A sandbox's create whosedrivesmounts a snapshot of a Drive the project does not have (paramdrives[i].snapshot). - 409
not_on_one_machine: A sandbox and a volume it is to have, or a shared volume that the snapshot it is made from or rolled back to mounts, are on different machines. A volume no sandbox has had yet moves to the sandbox's machine instead. Of a sandbox'sdrives: the Drives it mounts, and the snapshots of Drives it mounts read-only, are of more than one region, or of another region than the create names (paramregion); a snapshot is read in the region its Drive was in when it was taken, whether or not the Drive was deleted since. - 409
snapshot_not_replicated: A sandbox is to be made from, or rolled back to, a snapshot on another machine than the one that took it (that one is away, or the volumes are elsewhere), and the snapshot is not in the object store yet: itslast_replication_statussays how its copy stands.paramissnapshot. - 409
not_loadable: The machine the call goes to cannot make machines from the snapshot as it was published (another build of Firecracker, other settings of the devices, a larger shape, another size of disk); the message says what differs. - 409
backup_not_ready,backup_unusable: A volume made from a backup that is still being made, or that failed. - 409
too_many_drives,key_drive,duplicate_volume,drive_attached: Of a sandbox's create withdrives: more than 8 Drives and volumes together (too_many_drives); a keyed instance's Drive (key_drive); one Drive named twice (duplicate_volume); a Drive another sandbox has, which stays where it is (drive_attached). - 409
not_copied,store_unfenced: Of a sandbox's create withdrives: a Drive on another machine than the one the sandbox can be made on, where that machine copies nothing to an object store (not_copied), or copies to one that no move goes through (store_unfenced). - 409
managed_by_database: A sandbox's create (POST /v1/sandboxes,/v1/run), or a change of its mounts (PATCH /v1/sandboxes/{id}withvolumes), that mounts a database's Drive read-write (paramnames it); a delete, a change or a restore of a sandbox that is a database's instance; any change of a volume that is a database's Drive. What a database is made of is changed through/v1/databasesalone. - 409
managed_by_installation: The same of what an installation of a Managed Application is made of: a sandbox's create, or a change of its mounts, that mounts an installation's Drive read-write; a delete, a change or a restore of a sandbox that is an instance of an installation's app; any change of a volume that is an installation's Drive. What an installation is made of is changed through/v1/installationsalone. - 409
checkpoints_unavailable: A sandbox withcheckpointon (its own setting, or a keyed instance's from its app) where no machine takes checkpoints, or where what it is made from is on a machine that does not; and aPATCHthat turns them on, on a machine that does not.paramischeckpoint. While every machine that takes them is away or full, the create is 503no_capacityinstead,retryable. - 429
quota_used_up: Of an account on Free that has used up today's quota (UTC) of the CPU time or the memory the call would use: a create, a one-shot run, a fork, or a call that would wake a sandbox is refused until 00:00 UTC, whatever the account holds (Retry-Aftersays the seconds until then). - 502
fetch_failed: A sandbox made from, or rolled back to, a snapshot on another machine than the one that took it: that machine could not bring the snapshot from the object store. The message has its words. - 503
node_unavailable: The machine that holds the thing cannot be reached, or does not have the project's latest secrets yet: nothing is lost, and the same call will do once it is back (retryable). - 503
no_capacity: No machine has room for a new sandbox or volume (of a volume made from a backup: no machine of the backup's region makes them). - 503
moving: The sandbox is moving to another machine: every call for it until the move is over (retryable,Retry-After: 1). - 503
upgrading: The sandbox is a keyed instance its machine is moving over to another version of its app (POST /v1/apps/{app}/instances/{key}/upgrade): every call for it until the new version is up, or the instance is put back (retryable,Retry-After: 1). - 503
creation_cancelled,creation_cleanup_failed: The machine made the thing and the API could not record it: what the call made was deleted again (creation_cancelled), or not all of it could be (creation_cleanup_failed: the message lists what is left, which is billed until it is deleted). - 503
creation_replay_incomplete,creation_state_unavailable,creation_replay_record_failed: A create sent again with itsIdempotency-Key, whose first answer the machine gives again: what that answer named is no longer all there (creation_replay_incomplete; another key makes it anew), the machine could not be asked (creation_state_unavailable,retryable), or it is all there and the API could not record it (creation_replay_record_failed). - 503
copy_behind,drive_not_brought,not_served_here,unavailable: Of a sandbox'sdrives, each brought first to the machine the sandbox is made on: the object store does not hold the Drive as it is yet (copy_behind); its machine did not let it go, or the other could not take it (drive_not_brought); no machine of the region that could take it keeps Drives (not_served_here); another call is bringing it (unavailable). Each isretryablebutnot_served_here. - 503
standby: This copy of the API is not the one that serves: the answer names the one that does (active).retryable, withRetry-After.
A sandbox, snapshot or volume whose machine cannot be reached is still listed and read, as the API last saw it, with status unavailable (of a sandbox, awake_because and sleep_tier are null). The creates take region (na, eu, ap), which the API reads itself and does not pass on; a sandbox's and a volume's region says where it is.
Version: 0.1.0
Servers
- https://api.glac.ai