Sandboxes quickstart
Use Lizard SDK to create a sandbox, run Python, write a file and open a port, then stop the sandbox. Each step has TypeScript and Python code. You need a Lizard account on Pro or the trial, an API key and a project. To learn what Sandboxes are first, read the overview.
Install and authenticate
Install the Lizard SDK and create an API key in Settings → API keys, or with lizard keys create <name>. Store it in LIZARD_API_KEY. Every sandbox belongs to a project; the examples pass its ID as projectId (Python: project_id), and project also accepts a slug or name.
npm install @lizard-build/sdk
pip install lizard-sdk
export LIZARD_API_KEY="<your-api-key>"Run a command
Sandbox.create boots a sandbox from a template, here interpreter, which has Python and common data libraries. timeoutMs sets its lifetime; the SDK default is five minutes.
import { Sandbox } from '@lizard-build/sdk';
const sandbox = await Sandbox.create('interpreter', {
projectId: 'proj_123', timeoutMs: 300_000,
});
try {
const result = await sandbox.process.exec("python -c 'print(2 ** 10)'");
console.log(result.stdout, result.stderr, result.exitCode);
} finally {
await sandbox.kill();
}from lizard import Sandbox
sandbox = Sandbox.create("interpreter", project_id="proj_123", timeout_ms=300_000)
try:
result = sandbox.process.exec_("python -c 'print(2 ** 10)'")
print(result.stdout, result.stderr, result.exit_code)
finally:
sandbox.kill()A nonzero exit code is a result, not an error: check exitCode (Python: exit_code) yourself.
Files
Write text or bytes with fs.write, read text with fs.read, and read bytes with fs.readBytes (Python: read_bytes). Every file on the disk survives a pause and goes into a snapshot. A kill or expiration deletes the disk, so keep files you need later on a Persistent Volume at /workspace or in a snapshot.
await sandbox.fs.write("/tmp/task.py", "print(42)\n");
const result = await sandbox.process.exec("python /tmp/task.py");
console.log(result.stdout);sandbox.fs.write("/tmp/task.py", "print(42)\n")
result = sandbox.process.exec_("python /tmp/task.py")
print(result.stdout)Public ports
Start a server that listens on 0.0.0.0, then call exposePort(port) (Python: expose_port). It returns url, hostname, and accessToken. The port is private: open url in a browser (it carries ?lizard_token=), or send the token in the X-Lizard-Access-Token header. Requests without the token get 401. A stopped sandbox no longer serves the port. See preview URLs for a dev server an agent built.
await sandbox.process.exec(
"nohup python -m http.server 3000 --bind 0.0.0.0 --directory /tmp >/tmp/http.log 2>&1 </dev/null &",
);
const { url, hostname, accessToken } = await sandbox.exposePort(3000);
console.log(url); // open in a browser
const res = await fetch(`https://${hostname}/`, {
headers: { 'X-Lizard-Access-Token': accessToken },
});sandbox.process.exec_(
"nohup python -m http.server 3000 --bind 0.0.0.0 --directory /tmp >/tmp/http.log 2>&1 </dev/null &"
)
exposed = sandbox.expose_port(3000)
print(exposed.url) # open in a browserReconnect to a running sandbox
Keep the sandbox ID (sandbox.sandboxId, Python: sandbox.sandbox_id) and call Sandbox.connect(id) from another process or a later request to get a handle to the same sandbox. connect only checks that the sandbox exists: it does not resume a paused sandbox, and it throws NotFoundError once the sandbox has been killed or has expired.
const connected = await Sandbox.connect(sandbox.sandboxId);
console.log(await connected.getInfo());connected = Sandbox.connect(sandbox.sandbox_id)
print(connected.get_info())Pause, resume, and snapshots
pause() saves the sandbox’s memory, running processes and every file on its disk. A successful pause stops compute charges; Persistent Volumes storage continues to be billed. resume() restores the sandbox with the same ID and restarts compute billing. Both calls queue the work and return at once; wait for paused or running to confirm success.
await sandbox.pause();
await sandbox.waitForStatus('paused');
await sandbox.resume();
await sandbox.waitForStatus('running');- Pausing closes open network connections. Programs in the sandbox must reconnect after a resume.
- If the processes cannot be saved, the sandbox keeps running and
waitForStatusthrows with the reason. If a resume fails, the sandbox stays paused with its checkpoint; callresume()again. - The lifetime deadline stops while the sandbox is paused and continues after the resume. A paused sandbox does not count toward the running limit.
Fast pause. pause({ fast: true }) (Python: pause(fast=True), CLI: lizard sandbox pause <id> --fast) keeps only the files: the whole disk and /workspace. It takes about a second at any memory size, while a full pause takes longer the more memory is in use. Running processes end, and resume() boots the sandbox fresh with its files, like a restart; the resume response has memoryRestored: false. Use it when nothing that is running needs to survive. Billing and the lifetime deadline work as for any pause.
await sandbox.pause({ fast: true });
await sandbox.waitForStatus('paused');A snapshot saves a running sandbox’s memory, running processes and every file on its disk. It restores only in its own project and region. The user who took it and the members of that project can restore it. Sandbox.restore(id, { projectId }) (Python: project_id=) with any other project fails with HTTP 403 and code set to SNAPSHOT_SCOPE. Close clients and terminals and stop writes to /workspace before you capture.
If the sandbox has a Persistent Volume attached, the snapshot includes a copy of the volume’s files as they were at capture. Every restore gets that copy, not the live volume, and cannot attach another volume. Anyone who can restore the snapshot can read those files. A snapshot of a sandbox without a volume can mount one at restore with volumeId (Python: volume_id) from the same project and region.
const saved = await sandbox.snapshot('my-app');
await Sandbox.waitForSnapshot(saved.id);
const copy = await Sandbox.restore(saved.id);A snapshot is ready when the call returns and restores from disk, so poolSize does not apply. Each restore runs at the source’s size and bills at that size’s hourly price; size does not apply to a restore. In the CLI, use lizard sandbox pause <id>, lizard sandbox resume <id>, lizard sandbox snapshot <id> --name my-app, and lizard sandbox restore <snapshot-id>; see lizard sandbox for --volume.
Clean up and next steps
Call kill() when the task ends, as the examples do in finally. In Python, with Sandbox.create(...) as sandbox: kills the sandbox when the block exits. A sandbox you forget stops at its deadline, five minutes after creation unless you set another lifetime; see resources and timeouts.
- Every call, option and error: Sandbox API and SDK reference.
- Code from an LLM with state between calls: code interpreter.
- Files that outlive a sandbox: Persistent Volumes.
- A coding agent in a sandbox: Claude Code or OpenCode.
- The same steps from a terminal:
lizard sandbox. In the browser: the Sandboxes dashboard. - Sizes, prices and how many sandboxes can run at once: limits. Product overview and comparison: Sandboxes.
Updated