Hanzo

Sandboxes

Package sandbox is the ONE compute primitive: a sandbox is a gVisor pod that runs somebody else's code, and every lifetime is the same object.

Package sandbox is the ONE compute primitive: a sandbox is a gVisor pod that runs somebody else's code, and every lifetime is the same object.

Base URLhttps://api.hanzo.ai
Operations15
AuthAuthorization: Bearer $HANZO_API_KEY

sandboxes

POST /v1/sandboxes/{id}/exec

Run a command in a sandbox

Runs a command inside the sandbox and returns its exit code, stdout and stderr. A non-zero exit is a SUCCESSFUL call carrying a failed program — the HTTP status stays 200, because "the tests failed" and "the sandbox is broken" are different facts.

NOTHING RUNS IN cloud. The command is streamed to the Kubernetes exec subresource of the sandbox's pod, which runs under the gVisor runtime class. The sandbox is addressed by pod NAME through the apiserver, never by address.

ParameterInTypeRequiredDescription
idpathstringyes

GET /v1/sandboxes/{id}/fs

Read a file, or list a directory

Reads one file from the sandbox's project directory as text, or lists the entries when the path names a directory. Paths resolve under the project root and a path that climbs out is refused rather than rewritten.

ParameterInTypeRequiredDescription
idpathstringyes

POST /v1/sandboxes/{id}/fs

Write a file

Writes the request body to one file in the sandbox's project directory, creating parent directories. Same confinement as the read above.

ParameterInTypeRequiredDescription
idpathstringyes

POST /v1/sandboxes/{id}/terminal/ticket

Open a terminal

Mints a SINGLE-USE ticket for one interactive terminal in this sandbox and returns {ticket, expiresIn, url}, where url is the terminal PAGE with the ticket already on it.

It exists because a browser carries no Authorization header into a WebSocket or an iframe, so a terminal cannot be authenticated the way every other route here is. The ticket is a credential MINTED for that one terminal: bound to this org and this sandbox, valid for thirty seconds, and gone the first time it is presented. A long-lived bearer in a query string would instead be written into every access log on the path.

Mint one per terminal, and mint a fresh one to reconnect.

ParameterInTypeRequiredDescription
idpathstringyes

GET /v1/sandboxes/{id}/terminal/ws

The terminal, as a socket

Upgrades to a WebSocket carrying a login shell on a pseudo-terminal inside the sandbox — for a host that brings its own emulator. Requires ticket; a missing, expired or already-spent one answers 401 without upgrading.

THE WIRE. A text frame is stdin, unless it is the one control object {"resize":{"cols":N,"rows":M}}; a binary frame is always stdin. Output comes back as BINARY frames, because a pty emits arbitrary bytes cut at arbitrary offsets and a text frame carrying half a rune is one the browser closes the connection over.

arg names a SESSION: the shell runs under tmux new -A -s <arg>, which attaches to that session if it exists and creates it if it does not — so one sandbox holds as many terminals as a caller has names for. It is 1-64 characters of letters, digits, - or _ and may not begin with -; anything else is 400. Without arg the shell is unnamed and unmultiplexed.

The shell is zsh -l, falling back to bash -l and then to sh -l, and to the plain shell again when the image has no tmux. Every step is a preference and none is a requirement: whatever else the image carries — the hanzo CLI included — is a command to type, never a condition for getting a prompt.

ParameterInTypeRequiredDescription
idpathstringyes

GET /v1/sandboxes/{id}/terminal

The terminal, as a page

A complete, self-contained terminal — xterm inline, no other origin — that opens its own socket and runs a shell in this sandbox. Embed it in an iframe and there is nothing else to build.

ticket is the credential from the POST above and arg names the session (see the socket below); both are simply carried through to the socket. The page is NOT gated — it is inert markup and does not redeem the ticket, because a ticket is spent once and a page that spent it would hold a credential that no longer opens anything.

When the terminal is up it posts {source:"hanzo-term", ready:true} to its parent frame, so a host can tell a live terminal from a page that failed into something else. frame-ancestors admits our own brands' hosts and nothing further.

ParameterInTypeRequiredDescription
idpathstringyes

GET /v1/sandboxes/{id}

One sandbox

Returns one of the caller org's sandboxes. An id belonging to another org answers 404 and not 403 — a 403 would confirm the id exists, and whether a given sandbox exists is itself a cross-tenant fact.

ParameterInTypeRequiredDescription
idpathstringyes

DELETE /v1/sandboxes/{id}

End a sandbox

Stops the sandbox's pod and drops the lease. The VOLUME survives by default, so a dev or desktop sandbox can be leased again over the same project and find its checkout where it left it.

purge=1 deletes the volume too. It is opt-in because it is the one part of this that cannot be undone.

ParameterInTypeRequiredDescription
idpathstringyes

POST /v1/sandboxes/end

End a sandbox and release it

Request bodyapplication/json (required)

FieldTypeRequiredDescription
idstring
purgeboolean

POST /v1/sandboxes/lease

Lease a sandbox — a real computer — or resume one you hold

Request bodyapplication/json (required)

FieldTypeRequiredDescription
classstring
idstring
projectstring
runtimestring
ttlSecinteger

POST /v1/sandboxes/read

Read a file from a sandbox you hold

Request bodyapplication/json (required)

FieldTypeRequiredDescription
idstring
pathstring

POST /v1/sandboxes/stop

Stop what a sandbox is running, and keep the sandbox

Request bodyapplication/json (required)

FieldTypeRequiredDescription
idstring

POST /v1/sandboxes/write

Write a file into a sandbox you hold

Request bodyapplication/json (required)

FieldTypeRequiredDescription
datastring
idstring
pathstring

GET /v1/sandboxes

The sandboxes this org holds

Lists the caller org's sandboxes, newest first. project and status narrow it, and both are read from the QUERY STRING.

It answers from the org's own store rather than from the cluster, so a sandbox whose pod has since died still appears, carrying the status it was last known to have. That is deliberate: a lease you are being charged for should not vanish from the list because the thing behind it fell over.

POST /v1/sandboxes

Lease a sandbox

Creates a sandbox and returns it. class is one of exec, dev or desktop; dev and desktop are attached to a project, which is required for them and names the volume the work persists on. ttlSec bounds the lease, and image overrides the class default.

This is the ONLY path that creates cluster objects. The isolation boundary is the pod's runtime class, one field, so what a sandbox is confined by is a deployment decision rather than anything this operation negotiates.


All Hanzo APIs · Interactive reference

How is this guide?

On this page