SandboxesSDK Reference

Sandbox API and SDK reference

Lizard SDK is the client for Sandboxes on Lizard: @lizard-build/sdk on npm for TypeScript and JavaScript, and lizard-sdk on PyPI for Python, imported as lizard. Both call the same HTTP API. Find each sandbox call below under its TypeScript and Python names, with examples in both languages. For a guided first run, use the quickstart; for what Sandboxes are, read the overview.

Install and authenticate

npm install @lizard-build/sdk
pip install lizard-sdk
export LIZARD_API_KEY="<your-api-key>"

Create an API key in Settings → API keys or with lizard keys create <name>. The SDK reads LIZARD_API_KEY; pass apiKey (Python: api_key) to use another key. Each sandbox needs a project: pass projectId (Python: project_id) or resolve an ID, slug, or name with project.

Create options

TypeScriptPython
projectId / projectproject_id / project
timeoutMstimeout_ms
regionregion
volumeName / volumeIdvolume_name / volume_id
sizesize
snapshotIdsnapshot_id
envsenvs
metadatametadata
lizardTokenlizard_token
apiKeyapi_key
import { Sandbox } from '@lizard-build/sdk';
 
const sandbox = await Sandbox.create('base', {
  projectId: 'proj_123',
  size: 'small',
  timeoutMs: 10 * 60 * 1000,
  envs: { MODE: 'test' },
  metadata: { task: 'report-42' },
});
from lizard import Sandbox
 
sandbox = Sandbox.create(
    "base",
    project_id="proj_123",
    size="small",
    timeout_ms=10 * 60 * 1000,
    envs={"MODE": "test"},
    metadata={"task": "report-42"},
)
  • The first argument is a template from the catalog in runtime and templates. Without one, the SDK boots base.
  • size is 'mini', 'small', 'medium', or 'large': 1 vCPU / 2 GB, 2 vCPU / 4 GB, 4 vCPU / 8 GB (the default), or 8 vCPU / 16 GB; see sizes and prices. 'mini' starts only from the base, claude, codex and interpreter templates, without a volume. During the trial, 'small' is the default, 1 Small and 2 Mini run at a time, and only US regions work; 'medium', 'large', a second Small, a third Mini or another region throws AuthenticationError with HTTP 403. size does not apply to a sandbox restored from a snapshot; it runs at its source’s size.
  • timeoutMs defaults to 300000, five minutes. 0 disables expiration. During the trial, timeoutMs is at most 3600000 (1 hour), 0 gets 1 hour, and a longer value throws AuthenticationError with HTTP 403 and code set to TRIAL_SANDBOX_LIFETIME. See resources and timeouts.
  • Select a region explicitly when needed; a catalog entry does not guarantee free capacity. Leave region unset when you attach a volume: the sandbox runs where the volume is.
  • metadata is your own labels; getInfo() returns it, list() does not.
  • lizardToken takes one of your liz_ API keys and signs in lizard inside the sandbox. Your key only authorizes the request: the sandbox gets a new key of its own, limited to the sandbox’s project and deleted with the sandbox, so lizard there can act only on that project. The base template has no lizard.

Methods

TypeScriptPythonWhat it does
Sandbox.create(template, options)Sandbox.create(template, ...)Create in a project from a catalog template.
Sandbox.connect(sandboxId)Sandbox.connect(sandbox_id)Attach to an existing sandbox; no automatic resume.
Sandbox.list({ projectId })Sandbox.list(project_id=...)List accessible sandboxes; inspect their status.
sandbox.getInfo()sandbox.get_info()Fetch status, runtime, and metadata.
sandbox.process.exec(cmd, opts)sandbox.process.exec_(cmd, ...)Run a shell command or an argv array; return stdout, stderr, and the exit code.
sandbox.process.list()sandbox.process.list()List processes started with exec.
sandbox.process.kill(pid, signal)sandbox.process.kill(pid, signal)Signal a process and its children; SIGTERM by default.
sandbox.fs.write(path, data)sandbox.fs.write(path, data)Write text or bytes; create parent directories.
sandbox.fs.read(path)sandbox.fs.read(path)Read a UTF-8 text file.
sandbox.fs.readBytes(path)sandbox.fs.read_bytes(path)Read bytes from a file.
sandbox.fs.list(path)sandbox.fs.list(path)List directory entries.
sandbox.fs.stat(path)sandbox.fs.stat(path)Return type, size, mode, owner, and modification time.
sandbox.fs.makeDir(path)sandbox.fs.make_dir(path)Create a directory and missing parents.
sandbox.fs.move(from, to)sandbox.fs.move(src, dst)Move a file or directory.
sandbox.fs.remove(path)sandbox.fs.remove(path)Remove a file or directory.
sandbox.exposePort(port)sandbox.expose_port(port)Publish a port; return url, hostname, and the access token.
sandbox.getHost(port)sandbox.get_host(port)Publish a port and return its hostname; requests need sandbox.accessToken.
sandbox.unexpose(port)sandbox.unexpose(port)Remove a published port.
sandbox.setTimeout(ms)sandbox.set_timeout(ms)Set the remaining lifetime, at least 1000 ms.
sandbox.pause() / sandbox.resume()sandbox.pause() / sandbox.resume()Queue a pause or resume and return at once.
sandbox.pause({ fast: true })sandbox.pause(fast=True)Pause keeping files only, in about a second at any memory size; processes end and the resume boots the sandbox fresh.
sandbox.waitForStatus(status)sandbox.wait_for_status(status)Wait for paused or running; throw if the pause failed.
sandbox.fork({ count })sandbox.fork(count=...)Copy a running sandbox 1 to 100 times, memory and processes included; each copy counts toward your running-sandbox limit.
sandbox.snapshot(name)sandbox.snapshot(name)Save memory, processes and every file as a snapshot. It restores only in this sandbox’s project and region.
Sandbox.waitForSnapshot(snapshotId)Sandbox.wait_for_snapshot(snapshot_id)Wait until a snapshot is ready.
Sandbox.restore(snapshotId, { projectId, volumeId, timeoutMs })Sandbox.restore(snapshot_id, project_id=..., volume_id=..., timeout_ms=...)Start a new sandbox from a snapshot, in the snapshot’s project and region. Any other projectId throws AuthenticationError with HTTP 403 and code set to SNAPSHOT_SCOPE.
Sandbox.snapshots(projectId)Sandbox.snapshots(project_id)List the snapshots taken in a project.
Sandbox.mySnapshots()Sandbox.my_snapshots()List every snapshot you took, in all your workspaces.
Sandbox.deleteSnapshot(snapshotId)Sandbox.delete_snapshot(snapshot_id)Delete a snapshot; sandboxes restored from it keep running.
sandbox.kill()sandbox.kill()Stop the sandbox and detach its volume.

Run commands

process.exec (Python: exec_) runs a command and waits for it to finish. Pass onStdout or onStderr (Python: on_stdout, on_stderr) to receive output while the command runs, one line per call without the trailing newline; the call still returns the full result at the end. Pass an argv array instead of a string to skip the shell, which is the safe way to pass untrusted text as an argument.

const result = await sandbox.process.exec('npm test', {
  workdir: '/app',
  envs: { NODE_ENV: 'test' },
  timeoutMs: 120_000,
  onStdout: (line) => console.log(line),
});
if (result.exitCode !== 0) throw new Error(result.stderr);
 
await sandbox.process.exec(['grep', '-rn', '--', userQuery, '/workspace']);
result = sandbox.process.exec_(
    "npm test",
    workdir="/app",
    envs={"NODE_ENV": "test"},
    timeout_ms=120_000,
    on_stdout=print,
)
if result.exit_code != 0:
    raise RuntimeError(result.stderr)
 
sandbox.process.exec_(["grep", "-rn", "--", user_query, "/workspace"])

Command results contain stdout, stderr, and the exit code. A nonzero exit code is a result you check; request failures throw. timeoutMs (Python: timeout_ms) sets how long a command may run, from 1 to 600 seconds. Without it, the server stops the command after 150 seconds, and the TypeScript SDK stops waiting after 60 seconds and throws a DOMException named TimeoutError. Pass timeoutMs for any command that may run longer than a minute. process.exec accepts envs, workdir, and user; commands run as root unless you pass user. Create-time envs reach every command.

Files

Use fs.write for text or bytes and fs.read for text. For binary files, write bytes and read them back with fs.readBytes (Python: read_bytes). Use absolute paths. A missing path throws NotFoundError.

await sandbox.fs.write('/app/data.csv', 'x,y\n1,2\n');
const text = await sandbox.fs.read('/app/data.csv');
const entries = await sandbox.fs.list('/app');
const png = await sandbox.fs.readBytes('/app/chart.png');
sandbox.fs.write("/app/data.csv", "x,y\n1,2\n")
text = sandbox.fs.read("/app/data.csv")
entries = sandbox.fs.list("/app")
png = sandbox.fs.read_bytes("/app/chart.png")

A pause or a snapshot keeps every file on the disk, wherever you wrote it. After a stop (kill or expiration), files outside a Persistent Volume at /workspace are gone, unless you took a snapshot first.

Ports

Start a server on 0.0.0.0 inside the sandbox, then publish its port. Every request needs the access token, as the X-Lizard-Access-Token header or once as ?lizard_token= in a browser. See preview URLs.

const { url, hostname, accessToken } = await sandbox.exposePort(3000);
console.log(url); // open in a browser; it carries the token
await fetch(`https://${hostname}/api`, { headers: { 'X-Lizard-Access-Token': accessToken } });
await sandbox.unexpose(3000);
import httpx
 
exposed = sandbox.expose_port(3000)
print(exposed.url)  # open in a browser; it carries the token
httpx.get(f"https://{exposed.hostname}/api", headers={"X-Lizard-Access-Token": exposed.access_token})
sandbox.unexpose(3000)

Pause, snapshot, and fork

These calls keep memory, running processes and files. The quickstart explains what each one keeps, who can restore a snapshot, how restores are billed, and what fails.

await sandbox.pause();
await sandbox.waitForStatus('paused');
await sandbox.resume();
await sandbox.waitForStatus('running');
 
const saved = await sandbox.snapshot('ready-env');
const copy = await Sandbox.restore(saved.id);
 
for (const f of await sandbox.fork({ count: 2 })) {
  if (f.sandbox) await f.sandbox.process.exec('echo hi');
  else console.warn('fork failed:', f.error);
}
sandbox.pause()
sandbox.wait_for_status("paused")
sandbox.resume()
sandbox.wait_for_status("running")
 
saved = sandbox.snapshot("ready-env")
copy = Sandbox.restore(saved["id"])
 
for f in sandbox.fork(count=2):
    if f.sandbox:
        f.sandbox.process.exec_("echo hi")
    else:
        print("fork failed:", f.error)

fork returns one entry per copy: the new sandbox, or an error such as the account’s sandbox limit. Each copy bills like its source.

Run code with state

CodeSandbox boots the interpreter template and runs code with runCode (Python: run_code). In that template, Python runs in a persistent Jupyter kernel per context, so variables carry over between calls and results can include charts as image/png. JavaScript and bash run as a fresh process on each call. See code interpreter.

Volumes

Attach a Persistent Volume by name to keep /workspace between sandboxes:

import { Sandbox, Volume } from '@lizard-build/sdk';
 
await Volume.getOrCreate('proj_123', 'agent-workdir', { sizeGb: 10 });
const sandbox = await Sandbox.create('base', { projectId: 'proj_123', volumeName: 'agent-workdir' });
from lizard import Sandbox, Volume
 
Volume.get_or_create("proj_123", "agent-workdir", size_gb=10)
sandbox = Sandbox.create("base", project_id="proj_123", volume_name="agent-workdir")

Unsupported APIs

File watching is not available yet. sandbox.fork() returns 409 when a volume is attached. Sandbox.connect does not resume a paused or stopped sandbox; call resume() on a paused one. SSH protocol access is not available; lizard sandbox ssh opens an interactive shell with your CLI credentials.

Errors

API failures throw (Python: raise) these errors. Each carries the HTTP status and, when the API sends one, a code.

ErrorMeaning
PaymentRequiredErrorHTTP 402: the account must start Pro or pay an invoice before it can create anything. url is the page to open.
AuthenticationErrorHTTP 401 or 403: invalid key or no access. During the trial, a second Small or a third Mini running at once returns 403 with code set to TRIAL_SIZE_RUNNING, on create, resume or fork alike; a Medium sandbox returns TRIAL_SANDBOX_SIZE, a Large one SANDBOX_SIZE_NOT_ALLOWED, and a region outside the US TRIAL_SANDBOX_REGION. A lifetime over 1 hour, on create, fork or setTimeout, returns TRIAL_SANDBOX_LIFETIME.
NotFoundErrorHTTP 404: the sandbox, file, or snapshot does not exist or has expired.
ConflictErrorHTTP 409: conflicting state, such as forking a sandbox with a volume.
LimitExceededErrorHTTP 429: too many vCPU of sandboxes at once or too many starts. code says which limit, such as SANDBOX_LIMIT_REACHED or SANDBOX_RATE_LIMITED. For SANDBOX_LIMIT_REACHED, limit and running are in vCPU, unit says so (vcpu), and needed is the vCPU the refused sandbox needs. retryAfterS (Python: retry_after_s) says when to try again.
TimeoutErrorHTTP 408 or 504. The TypeScript SDK’s own 60-second wait for process.exec throws a DOMException named TimeoutError instead.
LizardErrorOther API failures, including unsupported operations.

The numbers behind HTTP 429 are in account limits.

HTTP API

The SDK calls Lizard’s REST API at https://lizard.build. Send your API key as Authorization: Bearer liz_... or in the X-API-Key header.

CallRequest
CreatePOST /api/sandboxes with template, projectId, size, timeoutMs, envs, metadata, region, volumeName; returns 201 with sandboxId
Get statusGET /api/sandboxes/{id}
ListGET /api/sandboxes, or GET /api/projects/{projectId}/sandboxes for one project
Run a commandPOST /api/sandboxes/{id}/exec with cmd, envs, workdir, user, timeoutMs; returns stdout, stderr, exitCode
Change lifetimePOST /api/sandboxes/{id}/timeout with timeoutMs
Pause or resumePOST /api/sandboxes/{id}/pause, POST /api/sandboxes/{id}/resume
Publish a portPOST /api/sandboxes/{id}/expose/{port}; DELETE on the same path removes it
KillDELETE /api/sandboxes/{id}
Check limitsGET /api/sandboxes/limits; returns tier, unit, limits, running and runningVcpu
curl -X POST https://lizard.build/api/sandboxes \
  -H "Authorization: Bearer $LIZARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"template": "base", "projectId": "proj_123", "timeoutMs": 300000}'

The raw API creates a sandbox with no expiration unless you pass timeoutMs.

Storage and billing

For billing, egress and files that survive a stop, see storage and billing. Sizes and hourly prices are in limits.

Updated