Sandboxes
What a sandbox is, how it behaves, and which endpoints you use to manage and invoke it.
A sandbox is an isolated runtime environment. Create one from a template, invoke it over HTTP or WebSocket, then delete it when you are done.
Why use sandboxes?
Sandboxes exist for security. They let agents execute arbitrary code, access files, and use the network without compromising your credentials, local files, or host system. This isolation is essential when agents run autonomously.
Isolation and security
Every sandbox runs in its own Firecracker microVM:
- Firecracker is launched with the Jailer for additional process and filesystem isolation.
- Sandboxes run on the Linux 6.1 LTS kernel.
- Each sandbox gets dedicated network boundaries with egress policy enforcement. Control outbound access with
network_policy.
See Firewall for egress configuration details.
Lifecycle
A sandbox can have several different states. Each state reflects the current status of your sandbox.
starting: The sandbox is booting and preparing its runtime environment before it can accept requests.running: The sandbox is active and ready to handle HTTP, WebSocket, filesystem, process, and other tool requests.paused: The sandbox's memory and disk state have been saved, and compute is no longer running. It resumes automatically when it receives a request.unpausing: The sandbox is being restored from its paused state so it can return torunning.stopping: The sandbox is shutting down while preserving disk changes, but not in-memory execution state.stopped: The sandbox is fully off, but its disk changes are preserved. Callstartto boot it again.deleting: The sandbox is being cleaned up and permanently removed.deleted: The sandbox has been permanently removed and is no longer available.
Create a sandbox
Set auto_pause to true if you want Leap0 to pause the sandbox when its timeout is reached.
from leap0 import Leap0Client, NetworkPolicyMode
client = Leap0Client()
sandbox = client.sandboxes.create(
vcpu=2,
memory=2048,
timeout=30,
auto_pause=True,
env_vars={
"NODE_ENV": "production",
},
network_policy={
"mode": NetworkPolicyMode.CUSTOM,
"allow_cidrs": ["1.2.3.4/32"],
},
)
print(sandbox.id, sandbox.state)import { Leap0Client, NetworkPolicyMode } from "leap0";
const client = new Leap0Client();
const sandbox = await client.sandboxes.create({
vcpu: 2,
memory: 2048,
timeout: 30,
autoPause: true,
envVars: {
NODE_ENV: "production",
},
networkPolicy: {
mode: NetworkPolicyMode.CUSTOM,
allowCidrs: ["1.2.3.4/32"],
},
});
console.log(sandbox.id, sandbox.state);
await client.close();Get a sandbox
Returns the sandbox status, resource configuration, and current state.
from leap0 import Leap0Client
client = Leap0Client()
sandbox = client.sandboxes.create()
print(sandbox.id, sandbox.state, sandbox.vcpu, sandbox.memory)import { Leap0Client } from "leap0";
const client = new Leap0Client();
const sandbox = await client.sandboxes.get("<sandbox_id>");
console.log(sandbox.id, sandbox.state, sandbox.vcpu, sandbox.memory);
await client.close();Invoke a sandbox
Send any HTTP or WebSocket request directly to the sandbox host. Whatever your app serves inside the sandbox is reachable.
By default, traffic is routed to the port defined by the template image's PORT environment variable.
If your app serves on a different port, append -<port> to the sandbox subdomain.
Port routing
Only one port can be reached per request. To target a specific port, append -<port> to the sandbox subdomain (e.g. sbx-abc123-3000). If no port suffix is set, traffic routes to the port from the template's PORT environment variable.
Examples:
- Default port from image metadata:
https://sbx-abc123.sandbox.leap0.dev/health - Explicit port routing:
https://sbx-abc123-3000.sandbox.leap0.dev/health
HTTP
import os
import httpx
from leap0 import Leap0Client
API_KEY = os.environ["LEAP0_API_KEY"]
client = Leap0Client()
sandbox = client.sandboxes.create()
response = httpx.get(
sandbox.invoke_url("/<your-path>"),
headers={"authorization": API_KEY},
)
response.raise_for_status()
print(response.text)import { Leap0Client } from "leap0";
import WebSocket from "ws";
const apiKey = process.env.LEAP0_API_KEY ?? "<your-api-key>";
const client = new Leap0Client();
const sandbox = await client.sandboxes.get("<sandbox_id>");
const res = await fetch(sandbox.invokeUrl("/<your-path>"), {
headers: { authorization: apiKey },
});
console.log(await res.text());
await client.close();Server-Sent Events
import os
import httpx
from leap0 import Leap0Client
API_KEY = os.environ["LEAP0_API_KEY"]
client = Leap0Client()
sandbox = client.sandboxes.create()
with httpx.stream(
"GET",
sandbox.invoke_url("/<your-path>"),
headers={
"authorization": API_KEY,
"Accept": "text/event-stream",
},
) as response:
response.raise_for_status()
for line in response.iter_lines():
if line:
print(line)import { Leap0Client } from "leap0";
const apiKey = process.env.LEAP0_API_KEY ?? "<your-api-key>";
const client = new Leap0Client();
const sandbox = await client.sandboxes.get("<sandbox_id>");
// EventSource does not support custom headers.
// Use fetch + ReadableStream to send an Authorization header.
const res = await fetch(sandbox.invokeUrl("/<your-path>"), {
headers: {
authorization: apiKey,
Accept: "text/event-stream",
},
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buf = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
const lines = buf.split("\n");
buf = lines.pop();
for (const line of lines) {
if (line.startsWith("data: ")) {
console.log(line.slice(6));
}
}
}
await client.close();WebSocket
import asyncio
import os
import websockets
from leap0 import Leap0Client
API_KEY = os.environ["LEAP0_API_KEY"]
client = Leap0Client()
sandbox = client.sandboxes.create()
async def connect() -> None:
async with websockets.connect(
sandbox.websocket_url("/<your-path>"),
additional_headers={"authorization": API_KEY},
) as ws:
await ws.send("hello")
response = await ws.recv()
print(response)
asyncio.run(connect())import { Leap0Client } from "leap0";
const apiKey = process.env.LEAP0_API_KEY ?? "<your-api-key>";
const client = new Leap0Client();
const sandbox = await client.sandboxes.get("<sandbox_id>");
const ws = new WebSocket(sandbox.websocketUrl("/<your-path>"), {
headers: { authorization: apiKey },
});
await new Promise((resolve, reject) => {
ws.on("open", () => ws.send("hello"));
ws.on("message", (data) => {
console.log(data.toString());
ws.close();
});
ws.on("close", resolve);
ws.on("error", reject);
});
await client.close();Pause a sandbox
Pause a running sandbox. The VM state, memory, and disk are captured and uploaded, the sandbox is marked as paused, and the VM is terminated.
The sandbox can later be resumed automatically when it receives a request.
from leap0 import Leap0Client
client = Leap0Client()
sandbox = client.sandboxes.create()
sandbox.pause()
print(sandbox.state)import { Leap0Client } from "leap0";
const client = new Leap0Client();
const sandbox = await client.sandboxes.get("<sandbox_id>");
await sandbox.pause();
console.log(sandbox.state);
await client.close();Stop a sandbox
Stop a running sandbox when you want to shut compute down but keep filesystem changes for later. Unlike pause, this does not preserve in-memory execution state.
curl -X POST \
-H "Authorization: Bearer <api-key>" \
"https://api.leap0.dev/v1/sandbox/<sandbox_id>/stop"The sandbox moves through stopping and then stopped.
Start a stopped sandbox
Start a sandbox that is currently stopped. Leap0 boots a fresh VM from the original template and reapplies the persisted writable disk state.
curl -X POST \
-H "Authorization: Bearer <api-key>" \
"https://api.leap0.dev/v1/sandbox/<sandbox_id>/start"The response now includes the full updated sandbox metadata, so SDKs do not need to issue a follow-up get after start.
The sandbox returns to running when the request completes.
start is for stopped sandboxes only. You do not need to call it for paused sandboxes.
Delete a sandbox
Running, paused, and stopped sandboxes can all be deleted.
from leap0 import Leap0Client
client = Leap0Client()
sandbox = client.sandboxes.create()
sandbox.delete()import { Leap0Client } from "leap0";
const client = new Leap0Client();
await client.sandboxes.delete("<sandbox_id>");
await client.close();Was this page helpful?

