Sandboxes

A fresh machine for every task.

Full Linux microVMs with their own kernel. Under a second to create cold, under 200ms to claim from a warm pool, and nothing shared with anyone else. Paused costs nothing.

npm i @sandal/sdk
agent-run.tsCreate → exec → fork → pause
import { Sandal } from "@sandal/sdk";
const sandal = new Sandal({ apiKey: process.env.SANDAL_API_KEY });
// 1, create. Warm claim, under 200ms.
const sb = await sandal.sandboxes.create({
template: "node-24",
resources: { vcpus: 4, memoryMb: 8192 },
ttl: "10m",
});
// 2, write files, run commands.
await sb.fs.write("/app/seed.ts", src);
const run = await sb.exec("pnpm test");
// 3, expose a port on signed HTTPS.
const url = await sb.expose(3000);
// 4, fork the finished disk, twice.
const [a, b] = await Promise.all([sb.fork(), sb.fork()]);
// 5, pause. Billing stops on this line.
await sb.pause();
<1scold create
<200mswarm claim
$0while paused
1kernel per sandbox
EU + USregions
Resend
bolt.new
Novu
CodeCrafters
Rork
Green-Got
Shockoe
Lightsprint
Copyfy
HireVoice
Standout
August
Two shapes

One meter, two ways to run.

A container and a full VM are not the same workload, so Sandal ships both on the same microVM substrate and the same per-second meter. Pick concurrency or pick a whole machine, the SDK is identical.

Short-lived · high concurrency

Containers →

Lean microVMs that start and exit in seconds. Fan out hundreds at once for agent tool-calls, code-interpreter turns, and CI shards, then tear them straight down. Own kernel, default-deny egress, billed by the second.

0.5 → 16 vCPU · Linux · from $0.0045 / container-hr
Full machine · warm all day

Virtual Machines →

Full microVMs with their own kernel that stay warm through a working day and pause to zero between bursts. Resume in under 200ms with your REPL variables and open files intact. Linux and Windows; macOS ships as a fixed Apple-silicon instance.

2 → 64 vCPU · Linux & Windows & macOS · from $0.036 / VM-hr
Both shapes bill by the second and cost nothing while paused. See the full rate card →
Inside the box

Everything inside the box.

Six primitives cover the whole surface: run things, move files, expose ports, branch state, keep data, and control what the guest can reach.

Commands & PTY

Run it buffered, streamed, or interactive.

exec runs a command to completion and hands back stdout, stderr and an exit code. Use the PTY when the process checks isatty(): REPLs, top, anything that reacts to a resize.

const r = await sb.exec("pnpm test"); // r.exitCode === 0
for await (const c of sb.exec.stream("pnpm build")) {
process.stdout.write(c.data);
}
const term = await sb.pty.create({ cols: 120, rows: 32 });
term.write("python3\n");
Filesystem

Talk to the disk, not to a shell.

Read, write, list and remove paths directly against the sandbox filesystem. Large payloads stream instead of buffering, and a watch subscription tells you when a build output changes.

await sb.fs.write("/app/x.ts", src);
const conf = await sb.fs.read("/app/config.json");
const entries = await sb.fs.list("/app");
sb.fs.watch("/app/dist", (ev) => console.log(ev.path));
Preview URLs

Any port, one signed HTTPS host.

Expose a listening port and get a public URL that routes to that sandbox and that port only. The URL dies with the sandbox, and is idempotent: calling expose twice on one port returns the same host.

sb.exec.stream("pnpm dev"); // serves :3000
const url = await sb.expose(3000);
// https://3000-sb_8f2c1a.sandal.dev
const api = await sb.expose(8080); // separate host
Snapshots & forks

Branch a machine like you branch a repo.

Every sandbox is snapshotted incrementally about once a minute, so only dirty blocks get written. A fork starts from the parent’s exact disk state with a new ID, new tokens and its own TTL clock.

await parent.exec("pnpm install"); // pay setup once
const child = await parent.fork();
const other = await parent.fork();
// both have node_modules; they diverge from here
Persistent volumes

Keep the cache, throw away the machine.

A volume is named storage that outlives any single sandbox, mounted at a path you choose. Point your package store or build cache at one and a cold sandbox stops paying the cold-install tax.

const vol = await sandal.volumes.create({
name: "pnpm-store", sizeGb: 20,
});
const sb = await sandal.sandboxes.create({
template: "node-24",
volumes: [{ id: vol.id, mountPath: "/root/.pnpm-store" }],
});
Network policy

Default deny, and secrets stay outside.

A fresh sandbox reaches nothing. You declare the hosts it may reach, and credentials for those hosts are injected at the edge proxy, so the token is never a file the guest can read.

const sb = await sandal.sandboxes.create({
template: "node-24",
network: {
egress: ["registry.npmjs.org", "api.github.com"],
inject: [{ host: "api.github.com",
header: "Authorization", secret: "gh_pat" }],
},
});
The loop

Create → Run → Return → Destroy

One call to get a machine, one to do the work, one to take the result out, and one to give it back. Nothing survives a delete, including the key.

agent-run.ts
import { Sandal } from "@sandal/sdk";
const sandal = new Sandal({ apiKey: process.env.SANDAL_API_KEY });
// 1, create. Warm claim, under 200ms.
const sb = await sandal.sandboxes.create({
template: "node-24",
resources: { vcpus: 4, memoryMb: 8192 },
ttl: "10m",
});
// 2, write files, run commands.
await sb.fs.write("/app/seed.ts", src);
const run = await sb.exec("pnpm test");
// 3, expose a port on signed HTTPS.
const url = await sb.expose(3000);
// 4, fork the finished disk, twice.
const [a, b] = await Promise.all([sb.fork(), sb.fork()]);
// 5, pause. Billing stops on this line.
await sb.pause();
  1. 01

    Ask for a machine.

    One call boots a microVM from a template, or claims a pre-booted one from the warm pool in under 200ms.

  2. 02

    Do the work.

    Exec commands, attach a PTY, write files, install packages, start a server on any port you like.

  3. 03

    Take the result out.

    Read stdout and exit codes, stream logs, pull artifacts off the filesystem, or hand back a preview URL.

  4. 04

    Give it back.

    Pause for $0, fork it for the next branch of work, or delete it. Nothing survives a delete, including the key.

Lifecycle

Six states, and what each one costs.

A sandbox moves through one state machine for its whole life. Here is the table, including the billing column most vendors leave out.

State
Billed?
creatingMicroVM is booting from a template.
No
runningKernel is live, CPU and RAM metered.
Active CPU + RAM
pausedMemory snapshotted to disk, CPU released.
No
stoppedProcess exited, disk retained, needs a fresh boot.
No
archivedEncrypted snapshot in cold storage, compute reclaimed.
Storage only
deletedGone. The encryption key is destroyed with it.
,

creating → running ⇋ paused → archived → deleted, with stopped as a side exit from running. Full lifecycle reference →

Pause ≠ stop

Pause keeps the memory.

pause() freezes exact in-memory state and releases the CPU, so resume() returns sub-second with your REPL variables and open file descriptors intact.

Stop

Stop tears the VM down.

stop() ends the process and keeps the disk; a later start() reboots from the last snapshot. Neither state is billed.

Archive

Cold storage, storage-only billing.

An archived sandbox is an encrypted snapshot with its compute reclaimed. Deleting it destroys the per-sandbox key, there is no wipe step to trust.

Templates & images

Build the stack once. Fork it after that.

A template is the base filesystem a sandbox boots from. Start on a maintained one, or push your own and let the block cache do the rest, a custom template still boots in under a second.

Custom templates

Cached per layer

node-24, python-3.12 and ubuntu-24.04 ship with a working toolchain. Custom templates build from a Dockerfile-like spec and are cached at the block level, so a rebuild re-snapshots only the layers that changed. Default disk is 5 GB, up to 100 GB; resize CPU and RAM on a running sandbox with sb.resize().

Sandalfile
FROM node-24
RUN apt-get install -y postgresql-client
COPY package.json pnpm-lock.yaml /app/
WORKDIR /app
RUN pnpm install --frozen-lockfile
# cached per layer; only changed layers re-snapshot

Build once, fork many

A graph op, not a disk copy

A dependency install you run 500 times a day is 500 billable installs. Run it once in a parent, fork the result, and you pay for the install once plus a near-constant cost per fork.

shard-tests.ts
// Do the expensive part a single time.
const base = await sandal.sandboxes.create({ template: "web-ci" });
await base.exec("pnpm install --frozen-lockfile");
await base.exec("pnpm build");
// Then branch it.
const shards = await Promise.all(
Array.from({ length: 8 }, () => base.fork())
);
await Promise.all(
shards.map((s, i) => s.exec(`pnpm test --shard=${i + 1}/8`))
);
SDKs

Same three lines, three languages.

Create a sandbox, run a command, read the result. TypeScript and Python wrap the same REST API you can hit with curl.

TypeScript

npm i @sandal/sdk

Typed end to end. Every mutation is idempotent, every long call streams, and the same client drives containers, VMs, runners and devices.

create-and-exec.ts
import { Sandal } from "@sandal/sdk";
const sandal = new Sandal({ apiKey: process.env.SANDAL_API_KEY });
const sb = await sandal.sandboxes.create({
template: "node-24",
resources: { vcpus: 2, memoryMb: 4096 },
region: "eu",
});
const result = await sb.exec("pnpm test");
console.log(result.stdout, result.exitCode);
await sb.pause();

Python

pip install sandal

The same surface with snake_case names, sync by default and async under sandal.aio. The REST API underneath takes an Idempotency-Key on every mutation.

create_and_exec.py
import os
from sandal import Sandal
sandal = Sandal(api_key=os.environ["SANDAL_API_KEY"])
sb = sandal.sandboxes.create(
template="node-24",
resources={"vcpus": 2, "memory_mb": 4096},
region="eu",
)
result = sb.exec("pnpm test")
print(result.stdout, result.exit_code)
sb.pause()

Prefer raw HTTP? POST to api.sandal.dev/v1/sandboxes with a bearer key and an Idempotency-Key header. API reference →

Where they show up

Three places a fresh machine pays for itself.

Agent loops

A machine per turn. The model proposes a command, the sandbox runs it, the output goes back into context. Pause between turns and the idle time is free.

Preview environments

One per pull request. Fork a warm parent that already has dependencies installed, check out the branch, expose the dev server. The URL dies with the sandbox.

Code interpreter

Untrusted input, contained. Arbitrary generated code runs against its own kernel with default-deny egress. The worst case is a wasted sandbox, not a shared host.

Pricing

Price your sandboxes.

Billed per second while the machine runs, and $0 while paused. Pick a container fleet or a VM fleet, set the size and the hours, and see it against what the same work costs on the field.

OS
CPU
Memory
Concurrent machines50
Hours each240 hr each

Compare Price

Sandal4 vCPU / 8 GB · $0.036/hr$432
Modal$0.0236/vCPU + $0.008/GB4.4×$1,900
Daytona$0.0504/vCPU + $0.0162/GB9.2×$3,974
E2B$0.0504/vCPU + $0.0162/GB9.2×$3,974
Vercel Sandbox$0.128/vCPU + $0.0212/GB19×$8,179

Pricing breakdown

ItemQuantityCost
VM time12,000 VM hrs$432
Paused timeUnmetered$0
Estimated total for 12,000 VM hrs$432
You save 77% a month against
$1,468

Billed by the second at $0.036/hr for 4 vCPU / 8 GB. Others at their published unit rates for the same shape, wall-time assumed; a paused Sandal machine is not billed.

An agent that pauses between turns pays only for the turns, a 4 vCPU sandbox active 8 hours and paused the other 16 costs about $0.29 for the day.

Under the floor

Built to disappear.

Infrastructure an agent has to wait for is infrastructure the agent will work around. The target is a machine that arrives faster than the model can decide what to do with it.

01 / Own kernel, own metal

Firecracker on bare metal.

Sandboxes run as Firecracker microVMs on machines we rent whole from Hetzner, OVH and Leaseweb. Larger shapes move to Cloud Hypervisor. Each guest gets its own kernel, so there is no shared kernel to escape into.

02 / Warm pools

Pre-booted, per template.

Most creates never boot anything. Pools hold pre-booted microVMs, one cohort per template, refilled as they drain. Claiming one hands it a fresh identity, a fresh network fence and fresh credentials.

03 / Snapshots

Encrypted, incremental, lazy.

Disk state is captured as encrypted snapshots about once a minute. Object storage is the durability floor; local NVMe is a disposable cache. Restore hands back the tree immediately and hydrates on demand.

Isolation

Assume the guest is hostile root.

The workload is code nobody reviewed, running unattended, at machine speed. Nothing in the design depends on a guest behaving. Forks never inherit the parent’s live credentials, and deleting the per-sandbox key is the deletion, there is no wipe step to trust.

Kernel

A kernel per sandbox, not a namespace.

Your sandbox is a microVM, not a container sharing the host kernel with other tenants. A dedicated kernel removes the cross-tenant escape surface instead of patching it.

Network

Own netns, default-deny egress.

Each guest gets its own network namespace and TAP device. Outbound access starts at zero and only opens for hosts the workload declared. Management interfaces never listen where a guest can reach.

Keys

Per-sandbox keys, destroyed on release.

Every snapshot is encrypted with a key scoped to that one workload. When the sandbox is released the key is destroyed, so what remains is unrecoverable ciphertext.

Plans

One balance. Every machine you run.

Containers and VMs draw from the same credit pool as your runners, simulators and Xcode builds, billed by the second at one published rate card.

Starter

For a solo build or a first agent in production.

$20/ month

$20 of usage credits

≈ 550 VM-hours or 33,000 CI minutes

Get started
  • 20 concurrent machines
  • Committed in-plan rates on every product
  • Linux & Windows, sandboxes, VMs, runners
  • Per-second billing, paused is free
  • Full API, SDKs, CLI & MCP server
  • Unlimited seats
  • Community support
Most popular

Team

Apple silicon unlocked, and a balance that rolls over.

$100/ month

$100 of usage credits

≈ 2,700 VM-hours or 10,000 Mac build minutes

Get started
  • 100 concurrent machines
  • Everything in Starter, plus:
  • macOS, iOS/Android simulators & Xcode builds
  • Unused credits roll over one month
  • Usage exports & per-second line items
  • Email support

Business

Higher ceilings for a team leaning on the fleet daily.

$250/ month

$250 of usage credits

≈ 6,900 VM-hours or 25,000 Mac build minutes

Get started
  • 300 concurrent machines
  • Everything in Team, plus:
  • Raised concurrency & start rate
  • Priority support & shared Slack channel
  • Single sign-on (SAML / OIDC)
  • Volume credit rate

Enterprise

Dedicated capacity, your compliance surface, one invoice.

Custom

Committed volume rate

Committed volume rate, negotiated per account

Talk to sales
  • Reserved bare-metal capacity
  • BYOC or self-host the control plane
  • SSO & SCIM, audit logs, data residency
  • Annual invoicing, MSA, DPA
  • Support SLA & solutions engineering
  • Custom regions on request

Every plan spends its credit pool first, then usage keeps flowing and bills per second at your committed in-plan rate, no hard stop mid-run, no forced upgrade. Without a plan you pay the on-demand rate instead. Prices are illustrative pending final list pricing; the per-second billing model is final.

Rate card

Every machine, every size.

Linux

MachineIn-plan / container-hrOn-demand / container-hr
0.5 vCPU / 1 GB$0.0045$0.0067
1 vCPU / 2 GB$0.009$0.013
2 vCPU / 4 GB$0.018$0.027
4 vCPU / 8 GB$0.036$0.054
8 vCPU / 16 GB$0.072$0.108
16 vCPU / 32 GB$0.144$0.216

Priced from the Linux formula: $0.0065 / vCPU-hr + $0.00125 / GB-hr. Any vCPU:RAM combination in between is priced the same way. On-demand is 1.5× the in-plan rate. All usage bills by the second; paused machines are not billed.

Get started

Give your AI agents somewhere to run today.

Your agents get their own machines across Linux, macOS, Windows, iOS and Xcode in milliseconds, and you stop paying for idle.

npx sandal init