#SDKs
Clients for Sandbox API v1. They speak to any endpoint —
local, self-hosted or cloud — and learn what it offers from capabilities().
| SDK | Status | Tested |
|---|---|---|
python/ (sandboxapi) |
standard library only; unix socket, http(s), private CA | yes: make test-sdk runs it against a real sandboxd |
typescript/ (sandboxapi) |
fetch-based: Node 18+, Deno, Bun, browsers; http(s) endpoints |
not yet run: written to the same contract, no Node in the development environment that wrote it |
#API keys
Every request carries the API key as Authorization: Bearer <key>: pass it
as token= (Python) or { token } (TypeScript), or build the client from the
environment so the key stays out of your code:
export SANDBOX_ENDPOINT=https://gateway.example:7443
export SANDBOX_API_KEY=… # SANDBOX_TOKEN is read too
export SANDBOX_ORG=acme # optional; SANDBOX_CA_FILE too, in Python
from sandboxapi import Client
c = Client.from_env()
import { Client } from "sandboxapi";
const c = Client.fromEnv();
On a gateway a user can make personal keys for their scripts, each with some of their own scopes, bound to the organization the client acts in, and always expiring (an hour to 366 days). The secret is in the answer once and stored only as a hash; a personal key cannot make keys itself.
| Python | TypeScript | |
|---|---|---|
create_api_key(name, scopes, expires_in_secs=30*86400) |
createAPIKey(name, scopes, expiresInSecs?) |
make one; its secret is shown this once |
api_keys() |
apiKeys() |
your personal keys, without secrets |
revoke_api_key(id) |
revokeAPIKey(id) |
what it has open ends |
#Calls
Both cover sandboxes, processes (run, background, output streaming, stdin, signals), files, suspend/resume and snapshots. Attach and tunnels (connection upgrades) are in the Go client and the CLI only, for now.
A snapshot captures a running sandbox — whole, memory and processes too, on Firecracker; its files on macOS — and any number of new sandboxes start from it. A sandbox started from one takes the snapshot's image and size, and its own network policy:
snap = c.create_snapshot(sb["id"])
fork = c.create_sandbox(snapshot_id=snap["id"])
c.snapshots() # newest first
c.delete_snapshot(snap["id"]) # forks keep running
const snap = await c.createSnapshot(sb.id);
const fork = await c.createSandbox({ snapshot_id: snap.id });
await c.deleteSnapshot(snap.id);
Against a gateway in front of many sandboxd nodes, both also have its
user-facing calls (a plain sandboxd answers each with not_found):
| Python | TypeScript | |
|---|---|---|
whoami() |
whoami() |
the user, tenant, scopes and key id behind the API key |
ssh_info() |
sshInfo() |
the SSH server's host and port, and the host keys to pin |
add_ssh_key(key, sandbox="") |
addSSHKey(key, sandbox?) |
register an authorized_keys line; sandbox limits it to one |
ssh_keys() |
sshKeys() |
your registered keys |
remove_ssh_key(id) |
removeSSHKey(id) |
|
ssh_access(ref, ttl_secs=0) |
sshAccess(ref, ttlSecs?) |
a short-lived login; its user is the token, and the whole credential until expires_at |
And its jobs, agent runs and secrets — work the gateway runs after the request that started it has gone (docs/jobs.md):
| Python | TypeScript | |
|---|---|---|
create_job(spec) |
createJob(spec) |
start a job: command or agent + prompt/prompts, parallelism, completions, retries, timeout_secs, env, secrets, keep, notify, labels, … |
agent_run(agent, prompt, **opts) |
agentRun(agent, prompt, opts?) |
a job of one agent run |
jobs() |
jobs() |
your jobs, newest first, without their runs |
job(id) |
job(id) |
one job, with each run's state, exit code, attempts and sandbox |
cancel_job(id) |
cancelJob(id) |
runs not started never are; running ones' sandboxes are terminated |
job_output(id, run) |
jobOutput(id, run) |
what a run kept of its stdout and stderr |
job_file(id, run, path) |
jobFile(id, run, path) |
a file a run kept (keep.files) |
set_secret(name, value) |
setSecret(name, value) |
set one of the tenant's secrets; never returned |
secrets() |
secrets() |
the tenant's secrets by name |
delete_secret(name) |
deleteSecret(name) |
And organizations (docs/organizations.md). A client made with
org="acme" (Python) or { org: "acme" } (TypeScript), or derived with
with_org("acme") / withOrg("acme"), sends X-Sandbox-Org: acme on every
request, so everything it does acts in that organization; the gateway answers
not_found for one the key's user is not a member of.
| Python | TypeScript | |
|---|---|---|
orgs() |
orgs() |
the organizations the key may act in, its own tenant first (default when unnamed) |
create_org(name) |
createOrg(name) |
needs org:create; you become its owner |
org_members(org) |
orgMembers(org) |
any member may list them |
set_org_member(org, user, role="", tenant="") |
setOrgMember(org, user, {role?, tenant?}) |
owners add a member or change a role |
remove_org_member(org, user, tenant="") |
removeOrgMember(org, user, tenant?) |
owners; what the member had open there ends at once |
The gateway's admin calls (other users' API keys, nodes, cordon) are in the Go client only;
admin_orgs() / adminOrgs() lists every organization for an admin key.
Byte fields are bytes (bytes / Uint8Array); the wire format's base64 is
handled inside. A sandbox has no repository: put code in with the files calls,
or clone it inside the sandbox.