# HTTPS API reference One API for every Runtime Cloud product, at `https://api.withruntime.com`. The SDKs, the CLI and MCP are thin layers over it. This page is the contract; the machine-readable one is the OpenAPI document at `https://api.withruntime.com/v1/openapi.json`, public and generated from the same route definitions the server validates with. API version: **0.2.0**. ## Requests Send the key as a bearer token. Keys stay in server-side secret storage; never in a URL, a browser or a command line others can read. ```bash no-run curl https://api.withruntime.com/v1/sandboxes \ -H "Authorization: Bearer ${RUNTIME_API_KEY}" \ -H "Content-Type: application/json" \ -H "Prefer: wait=60" \ -d '{}' ``` In a shell, put a variable before a colon in braces: `"${ID}:exec"`. zsh, the macOS default, reads `$ID:e` as a modifier and sends `/xec`. Never use `curl -v` or `--trace` with the key: they print the `Authorization` header. ```bash no-run curl -sS "https://api.withruntime.com/v1/sandboxes/${ID}:exec" \ -H "Authorization: Bearer ${RUNTIME_API_KEY}" \ -H "Content-Type: application/json" \ -d '{"command": "python3 -c \"print(6 * 7)\""}' ``` - Resources live at `/v1//{id}`. A change that is not a plain update is `POST /v1//{id}:`: `:stop`, `:pause`, `:wake`, `:exec`. - Field names are camelCase. Times are ISO 8601 in UTC. Money is integer microdollars: 1,000,000 is one US dollar. - Unknown fields are refused, and every wrong field is named in one answer. - `Prefer: wait=N` (seconds, at most 120) on a change answers once the resource has settled rather than at once. `GET /v1/sandboxes/{id}?waitFor=running&timeoutSeconds=60` waits for a state without polling. ## Responses and errors Every response carries `x-request-id`. Every error has one shape: ```json { "error": { "code": "trial_busy", "status": 409, "message": "The trial runs 3 sandboxes at once; ... are running or paused.", "hint": "Stop one (runtime sandbox stop ), or pass funding: \"paid\" to use prepaid credit.", "details": { "sandboxIds": ["..."], "concurrent": 3 }, "requestId": "req_0mud1dtgjogtn2z8t6y" } } ``` Follow the `hint`. Quote the `requestId` when you report a problem. A 5xx never carries internal detail, only a fixed message and the request id. | Status | Codes you may see | Safe next step | | ------ | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | | 400 | `invalid_request` (with `details.issues`), `invalid_region`, `invalid_trial` | Fix the named fields | | 401 | `unauthorized` | Check the key | | 402 | `trial_exhausted`, `insufficient_funds`, `spending_limit_reached` | Add credit, pay with `funding: "paid"`, or read `GET /v1/limits` | | 403 | `forbidden` (a read-only key asking to change something is one), `permission_denied` | Do not work around a refusal | | 404 | `not_found`, `route_not_found`, `file_not_found` | Check the id or path | | 409 | `no_capacity`, `trial_busy`, `not_running`, `sandbox_paused`, `sandbox_stopped`, `sandbox_not_ready`, `is_a_directory` | Resolve the state, then retry | | 422 | `idempotency_key_reused` | Same key, same body; or a new key for new work | | 426 | `upgrade_required` | Move to this API version | | 429 | `rate_limited` | Wait `Retry-After`, then retry with the same key | | 503 | `busy`, `host_unavailable`, `api_unavailable`, `guest_busy` | Retry after `Retry-After` with the same key (the SDKs do) | | 503 | `unavailable`, `fork_unavailable`, `previews_unavailable` | Switched off here on purpose; retrying will not help | The server can refuse capacity; a request is not a guarantee of allocation. ## Retries and idempotency Send `Idempotency-Key: ` on a change. The server remembers the key for 24 hours: - The same key with the same body answers the first result again, marked `"replayed": true` and with the header `Idempotency-Replayed: true`. Nothing happens twice. - The same key with a different body is refused with 422 `idempotency_key_reused`. - A new key is new work. The SDKs send a key on every change and retry transport failures, 429, 502, 503 and 504 with it. Changing the body while reusing a key is not a retry. ## Limits Each key may make about 50 requests a second, in bursts of up to 200, with 128 being served at once; past that, a request waits its turn for up to five seconds before it is refused. An organization may have 192 requests in flight across all its keys, and eight terminals open. Over a limit you get 429 with `Retry-After`. These are protection, not quotas, and a refusal costs nothing. ## Sandboxes | Method and path | What it does | | ----------------------------------- | -------------------------------------------------------------------------------------- | | `POST /v1/sandboxes` | Create. Every field optional. | | `GET /v1/sandboxes` | List: `state`, `name`, `label=key:value` (repeat), `includeStopped`, `limit`, `cursor` | | `GET /v1/sandboxes/{id}` | Read; `waitFor` and `timeoutSeconds` wait for a state | | `POST /v1/sandboxes/{id}:stop` | Stop. Compute ends on confirmed shutdown. | | `POST /v1/sandboxes/{id}:pause` | Save memory and files; compute billing stops | | `POST /v1/sandboxes/{id}:wake` | Restore a paused sandbox; `timeoutSeconds` sets its next lease | | `POST /v1/sandboxes/{id}:extend` | `{"seconds": 600}` more before the lease ends | | `POST /v1/sandboxes/{id}:retention` | `{"days": 30}` to keep a paused sandbox, 1 to 365 | | `POST /v1/sandboxes/{id}:restart` | Start a stopped persistent sandbox again from its disk | The create body: | Field | Default | Notes | | ------------------ | ------------------------------- | ------------------------------------------------------------ | | `name`, `labels` | none | Your own handle and up to 32 `key: value` tags | | `funding` | trial while it lasts, then paid | `"trial"` never falls back to paid credit | | `region` | the default region | Use a region listed for your account | | `vcpu` | 2 | Sizes are subject to the deployed host's measured capacity | | `memoryMiB` | 4096 | | | `diskMiB` | 4096 | | | `cpu` | `"shared"` | `"reserved"` guarantees every vCPU | | `cpuFloorMillis` | 50 | Guaranteed CPU while shared, in thousandths of a vCPU | | `timeoutSeconds` | 1800 | How long it may run before its lease ends, at most 3600 | | `onLeaseEnd` | `"pause"` | Or `"stop"` | | `pausable` | true | | | `network` | the public web on ports 80, 443 | Same shape as `PUT /v1/sandboxes/{id}/network` | | `maxCostMicros` | none | Refuse the create if its first lease would cost more | | `image`, `volumes` | none | A ready image id and up to four `{volumeId, path, mode}` | | `snapshot` | none | A ready snapshot id: start as a copy of it. Not with `image` | Sizes are subject to the deployed host's measured capacity. Sizes are limits you ask for; the server checks their combinations, account limits and available host capacity. ## Commands and processes | Method and path | What it does | | ------------------------------------------------------ | ------------------------------------------------------------------------------ | | `POST /v1/sandboxes/{id}:exec` | Run and answer `{exitCode, stdout, stderr, timedOut}` | | `POST /v1/sandboxes/{id}/processes` | Start in the background; add `"pty": {"cols", "rows"}` for a terminal | | `GET /v1/sandboxes/{id}/processes` | Running and recent processes | | `GET /v1/sandboxes/{id}/processes/{processId}` | One process | | `GET /v1/sandboxes/{id}/processes/{processId}/output` | Output from `cursor`; `waitMs` long-polls; `follow=true` streams | | `POST /v1/sandboxes/{id}/processes/{processId}:write` | `{"data"}` or `{"base64"}`, with `offset` and `eof`; offsets make retries safe | | `POST /v1/sandboxes/{id}/processes/{processId}:signal` | `{"signal": "SIGTERM"}` | | `POST /v1/sandboxes/{id}/processes/{processId}:resize` | `{"cols", "rows"}` for a pty | The exec body is `command` (run under `bash -c`) or `argv` (no shell), and optionally `cwd`, `env`, `stdin`, `timeoutMs` (default 60,000; at most 24 hours) and `stream`. With `"stream": true` the answer is NDJSON, one event a line: `start`, `stdout`, `stderr`, `exit`, and `continue` with a cursor when a stream passes the server's time limit, from which `…/output?follow=true&cursor=` resumes. A timeout is a result with `timedOut: true`, not an error. `env` values are never echoed and are stored only as hashes. ## Terminals `GET /v1/sandboxes/{id}/terminal` with `Upgrade: websocket` and the bearer header opens a terminal: `cols`, `rows`, `command` (default `bash -l`), `cwd`, or `processId` to attach to a process started with a pty. Binary frames are terminal bytes both ways. Text frames are JSON: you send `{"type":"resize","cols":120,"rows":40}`; the server sends `{"type":"ready","processId":...}` and `{"type":"exit","exitCode":...}`. ## Files | Method and path | What it does | | --------------------------------------------------- | --------------------------------------------------------- | | `GET /v1/sandboxes/{id}/files/content?path=` | The file's raw bytes | | `PUT /v1/sandboxes/{id}/files/content?path=` | Replace the file with the raw request body; makes parents | | `GET /v1/sandboxes/{id}/files/list?path=` | Entries; `depth`, `glob`, `hidden`, `limit` | | `GET /v1/sandboxes/{id}/files/stat?path=` | Type, size, mode and times, or `{"exists": false}` | | `POST /v1/sandboxes/{id}/files:mkdir` | `{"path", "parents"}` | | `POST /v1/sandboxes/{id}/files:remove` | `{"path", "recursive"}` | | `POST /v1/sandboxes/{id}/files:rename` | `{"from", "to", "overwrite"}` | | `POST /v1/sandboxes/{id}/uploads` | Begin a large upload: `{"path", "size", "sha256"}` | | `PUT /v1/sandboxes/{id}/uploads/{uploadId}?offset=` | One chunk of raw bytes; chunks may go in parallel | | `POST /v1/sandboxes/{id}/uploads/{uploadId}:commit` | Check the digest and move the file into place atomically | | `POST /v1/sandboxes/{id}/uploads/{uploadId}:abort` | Give up | Paths are absolute. File errors name the path: `file_not_found`, `is_a_directory`, `not_a_directory`, `permission_denied`. ## Other products | Method and path | Product | | ----------------------------------------------------------------------------------------------------------------------- | ------------------- | | `POST /v1/sandboxes/{id}/interpreter:run`, `…/interpreter/contexts` | Code interpreter | | `POST`, `GET /v1/sandboxes/{id}/previews`, `…/previews/{port}`, `…/previews/{port}:rotate` | Previews | | `POST /v1/sandboxes/{id}/desktop:start`, `:stop`, `:act`, `GET …/desktop/screenshot` | Desktop | | `GET`, `PUT /v1/sandboxes/{id}/network` | Network rules | | `POST`, `GET /v1/feedback` | Feedback | | `POST /v1/support/messages`, `GET /v1/support/conversations/{id}` | Support | | `GET /v1/me`, `GET /v1/usage`, `GET /v1/limits` | Account | | `POST`, `GET /v1/images`, `GET /v1/images/{id}`, `…/logs`, `:delete` | Custom images | | `POST`, `GET /v1/volumes`, `GET /v1/volumes/{id}`, `:delete` | Volumes | | `POST /v1/sandboxes/{id}:fork`, `POST /v1/sandboxes/{id}:snapshot`, `GET /v1/snapshots`, `…/{id}`, `:extend`, `:delete` | Forks and snapshots | The OpenAPI document has every field of every route. ## Account `GET /v1/me` says who a key is: `orgId`, `principalId`, `credentialId`. `GET /v1/usage` says what the account has and has used. Money is integer microdollars in strings (1,000,000 is one dollar), exact however large: | Field | What it is | | ----------- | ----------------------------------------------------------------- | | `credited` | Every credit ever added: purchases and grants | | `spent` | Settled usage, including what refunds and disputes took back | | `held` | Reserved against running and paused work, not yet settled | | `expired` | Credit that expired, or grant credit taken back | | `available` | What can still be spent: `credited - spent - expired - held` | | `takenBack` | The part of `spent` that refunds and disputes took | | `trial` | `{totalMs, usedMs, reservedMs, availableMs}`, or null | | `resources` | The newest hundred resources, with what each used and was charged | `GET /v1/limits` says what this key may do and what its agent may still spend: ```json { "access": "full", "daily": { "limitMicros": "25000000", "usedMicros": "3100000", "remainingMicros": "21900000", "window": "24h" } } ``` `access` is `full` (every action in every product), `read` (a read-only key: every read, no change and no spending) or `selected` (the actions the key names). `daily.limitMicros` is the most the key's agent may commit in any 24 hours, or null when the owner set no limit. `usedMicros` counts settled charges plus money still on hold. Past the limit, a create, wake, extension or renewal fails with 402 `spending_limit_reached` and charges nothing. Only an account owner sets, changes or removes a limit, at [API keys](https://withruntime.com/account/keys); no key can. ## Pages Every list, in every product, answers `{"data": [...], "nextCursor": "..."}`. Pass `nextCursor` back as `cursor`; `null` ends the list. `limit` is 1 to 100. ## Versions This is 0.2.0. The 0.1.0 routes under `/v1/resources` were removed on 22 September 2026: each answers 410 `upgrade_required`, and `details.replacement` names the route that replaced it. [The migration guide](./migrate) maps each old call to its new one. The OpenAPI document's `info.version` is the version you are talking to. --- # How to choose a sandbox for AI agents An agent sandbox is an isolated execution environment where an AI agent can run programs, install dependencies and work with files. The best sandbox for a project is the one that completes its real jobs safely, reliably and at an acceptable total cost. A low hourly rate or a fast empty boot does not establish that. Runtime provides Firecracker microVM sandboxes on Runtime-operated dedicated servers, with API, CLI, JavaScript, Python and MCP interfaces. New accounts get a 20-hour free trial. Use the checks below to evaluate Runtime or another provider against your requirements. ## Start with the workload Write down one representative task: for example, install a repository's pinned dependencies, run its test suite, make one change, and export the test report. Record the language version, CPU and memory needs, required network destinations, files to preserve, expected duration and acceptable failure rate. | Requirement | What to verify | Runtime's current position | | ----------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | Isolated code execution | Boundary between guest, host and other tenants | Firecracker microVMs; limited private hardware tests, no completed external audit claimed | | Agent integration | Actual remote execution, structured results, file round trip | API, CLI, Python, JavaScript and MCP; one Runtime Cloud SDK with product namespaces | | Easy authentication | Connection without secrets in prompts or project files | Browser-approved CLI connection; local MCP reuses it | | Outbound network | Your package registries and service destinations work | Controlled public-web egress; per-sandbox rules; `host:port` access for paid accounts | | Public app hosting | Reachability, ingress auth and abuse controls | Previews: an HTTPS address per port, private by default | | Pause and restoration | Files and memory survive; expiry and failed wake are explicit | Same-host pause/wake; a full or unavailable host can prevent restoration | | Durable outputs | Backups, replication and recovery evidence | Export important data; a sandbox is not a replicated backup service | | Predictable cost | CPU, memory, idle time, storage, retries and fees | Measured CPU with a floor, reserved memory, separately quoted paused storage | | Production readiness | Access, operational evidence and service commitments | Open signup with a free trial; check current guarantees against the workload | For exact terms, read [security](./security), [pricing](./pricing), [trial access](./trial), and the [API reference](./api). ## Measure completed jobs Run the same inputs, dependency versions and output checks on every candidate. Separate cold starts from warm or resumed runs. Use enough repetitions to reveal slow runs and failures; publish the sample count instead of hiding it behind an average. Record: - Time from request to an executable environment. - Dependency setup, task execution and artifact export time. - Median and 95th-percentile end-to-end completion time. - Successful, failed and retried jobs, including exhausted capacity. - CPU-seconds, allocated memory, running duration and retained storage. - Total charge divided by successful jobs, including failed attempts. State hardware, region, concurrency, dates, SDK versions, CPU guarantees and all pricing assumptions. A shared CPU ceiling is not a reserved physical core. Do not call equal CPU counts equal performance without measuring them. ## Exercise the failures you will have to handle Try a lost create response, an execution timeout, a nonzero exit code, a network destination that is refused, an expired lease, and unavailable capacity on wake. Check that retrying the same operation does not create duplicate work or charges. Verify that revoking one agent's credential stops its access without breaking another agent's connection. For a pause test, write a file and keep a small in-memory value, pause, restore, then verify both. Report failed restoration honestly. A new clean boot is not proof that memory restoration succeeded. ## When Runtime is worth evaluating Runtime is a candidate for coding agents, test execution, file processing and multi-step jobs that fit its Linux and public-web network constraints. Its CPU metering is worth measuring for workloads that spend time waiting on model or network responses. The CPU floor and reserved memory still accrue charges while running; pause can introduce separate storage costs and restoration constraints. If you need arbitrary outbound TCP, replicated durable storage, GPUs or established production service guarantees today, do not infer those features from the presence of an SDK method. Check availability first. ## Compared with a specific provider Side-by-side pages, with published rates and where each rival is stronger: [E2B](./e2b-alternative), [Daytona](./daytona-alternative), [Vercel Sandbox](./vercel-sandbox-alternative) and [Modal Sandboxes](./modal-sandbox-alternative). ## Migrate only after the comparison passes Keep the existing provider available for rollback. Verify output correctness, cleanup, cost and failures before increasing traffic. The [migration guide](./migrate) describes a reversible rollout; the [getting-started guide](./start) covers a new project. Runtime does not claim universal performance or cost superiority. --- # The runtime CLI The CLI drives every Runtime Cloud product from a terminal or a script. It comes with the JavaScript SDK, needs Node 22 or later, has no dependencies and starts in about 30 ms. Run it without installing, or install it once: ```bash no-run npx withruntime run -- python3 -c 'print(6 * 7)' # without installing npm install --global withruntime # then the command is runtime runtime run -- python3 -c 'print(6 * 7)' ``` The examples below use `runtime`, the installed command. Without installing, write `npx withruntime` in its place. Commands read `runtime `, with account commands at the top. `runtime` on its own says whether this machine is connected and what to run first. ## One command in a fresh sandbox ```bash runtime run -- python3 -c 'print(6 * 7)' runtime run --keep --vcpu 2 -- nproc # keeps it, and prints how to run more ``` `run` creates a sandbox, runs the command, prints its output, stops the sandbox and exits with the command's exit code. It takes the same options as `sandbox create`. `--keep` leaves the sandbox running for more commands. ## Connect once Any command connects this machine the first time it needs to, so there is no separate step. To connect ahead of time: ```bash no-run runtime login # opens your browser; approve Connect agent runtime login --no-browser # prints a link and a code for another device, and exits runtime login --wait # waits for an approval already asked for runtime login --with-key # or paste a key from withruntime.com/account/keys runtime whoami ``` The CLI receives and saves its own credential automatically. You do not copy an API key. At a terminal, login waits for the approval. Run by an agent (no terminal), it waits up to 50 seconds, then prints the link and code and exits with `connection_pending`; `--no-browser` exits at once. The request is saved, so once it is approved, the same command again, or any other, finishes the connection with no new link. A request expires after 15 minutes. `--with-key` reads the key from standard input (piped, or typed without echo), never from the command line. `RUNTIME_API_KEY` overrides the saved connection when it is set, which is how CI and scripts run; in CI (the `CI` variable set) or with `RUNTIME_NO_LOGIN=1`, a missing key is an error instead of a browser approval. ## Sandboxes ```bash id=$(runtime sandbox create --name demo --label team=search) runtime sandbox exec "${id}" -- python3 -c 'print(6 * 7)' runtime sandbox exec "${id}" --cwd /workspace --env API_TOKEN="${API_TOKEN}" -- env runtime sandbox ls runtime sandbox get "${id}" runtime sandbox stop "${id}" ``` `create` prints only the id, so it composes with `$(...)`, and waits until the sandbox is running (`--no-wait` returns at once). Its options: `--vcpu`, `--memory` and `--disk` in MiB, `--timeout `, `--on-timeout pause|stop`, `--trial` or `--paid`, `--name`, `--label k=v` (repeatable), and network rules: `--no-internet`, `--allow `, `--deny ` and `--connect `. `exec` streams output as it happens and exits with the command's own exit code (124 when it timed out), so `set -e` scripts behave. Pass secrets with `--env` from a variable, never written into the command. Background work, logs and a terminal: ```bash id=$(runtime sandbox create) pid=$(runtime sandbox spawn "${id}" -- python3 -m http.server 8000) runtime sandbox ps "${id}" runtime sandbox logs "${id}" "${pid}" runtime sandbox kill "${id}" "${pid}" runtime sandbox stop "${id}" ``` ```bash no-run runtime sandbox shell "${id}" # an interactive terminal, like ssh runtime sandbox logs "${id}" "${pid}" -f # follow until the process exits ``` Files. Name a sandbox path as `:/path`; directories copy whole: ```bash id=$(runtime sandbox create) mkdir -p project && echo "print('hi')" > project/main.py runtime sandbox cp ./project "${id}:/workspace/project" runtime sandbox files "${id}" /workspace --depth 2 runtime sandbox cat "${id}" /workspace/project/main.py runtime sandbox cp "${id}:/workspace/project" ./project-copy runtime sandbox stop "${id}" ``` Lifecycle: `pause`, `wake`, `restart`, `extend `, `stop`. ## Code, previews, network rules and the desktop ```bash no-run runtime sandbox run-code "${id}" analysis.py --out-dir charts # a notebook cell; charts saved as PNG runtime sandbox preview "${id}" 3000 --public # an HTTPS address for a port runtime sandbox previews "${id}" runtime sandbox unshare "${id}" 3000 runtime sandbox network "${id}" # show its rules runtime sandbox network "${id}" --allow pypi.org --allow '*.pythonhosted.org' runtime sandbox network "${id}" --no-internet runtime sandbox desktop "${id}" start # prints a link to watch it runtime sandbox desktop "${id}" open https://example.com runtime sandbox desktop "${id}" screenshot screen.png ``` A preview's address is under `runtimehost.com`, the domain for everything sandboxes serve, kept apart from Runtime's own site. ## Images, volumes and snapshots Forks and snapshots are paused while we fix an issue: for now `fork`, `snapshot` and a create naming `snapshot` answer 503 `fork_unavailable`. Your sandboxes are unaffected. ```bash no-run img=$(runtime image build --pip pandas --apt jq --name data) # or --dockerfile ./Dockerfile, or --from python:3.12-slim vol=$(runtime volume create --size-mib 10240 --name cache) id=$(runtime sandbox create --image "${img}" --volume "${vol}:/data") base=$(runtime sandbox create --image "${img}") runtime sandbox fork "${base}" --count 3 # three running copies of it snap=$(runtime sandbox snapshot "${base}" --name ready) runtime sandbox create --snapshot "${snap}" # a copy, any time later runtime image ls; runtime volume ls; runtime snapshot ls ``` A Dockerfile build sends the files its `COPY` and `ADD` lines name, from the Dockerfile's folder: at most 256 files and 1 MiB. A volume lives on one server and is not backed up off it. A fork or snapshot pauses a running sandbox for the moment it takes, then wakes it; a sandbox with volumes cannot be snapshotted. ## Scripts and agents: `--json` Every command takes `--json` and prints one JSON value. Errors become `{"error": {"code", "message", "hint", "requestId"}}` on standard error with a non-zero exit. ```bash id=$(runtime sandbox create --trial) runtime sandbox get "${id}" --json runtime sandbox exec "${id}" --json -- uname -a runtime sandbox stop "${id}" --json ``` `runtime sandbox get --json` answers the sandbox as the API does: ```json { "id": "0b8f3c52-6d8e-4b1f-9c67-2f4e6c1d9a10", "kind": "sandbox", "name": null, "labels": {}, "state": "running", "region": "us-east-vin", "funding": "trial", "vcpu": 2, "memoryMiB": 4096, "diskMiB": 4096, "cpu": "shared", "cpuFloorMillis": 50, "timeoutSeconds": 1800, "onLeaseEnd": "pause" } ``` ## Account ```bash runtime whoami runtime usage runtime ls ``` `runtime limits` says whether this machine's key is read-only and what its daily spending limit is, with what was used in the last 24 hours and what is left; it arrived in 0.3.1. Only an account owner sets or changes the limit, at [API keys](https://withruntime.com/account/keys); see [security](./security). `runtime ls` lists everything the account runs, every product. `runtime docs ` prints any page of these docs. `runtime mcp` serves Runtime's MCP tools on stdio for agents that want a local command; see [MCP](./mcp). When something is broken, missing or confusing, say so; it goes straight to the people building Runtime: ```bash no-run runtime feedback "exec output lost its colours" --kind bug ``` ```bash no-run runtime support "my sandbox will not wake" ``` See [feedback and support](./feedback-and-support). ## Sign out ```bash no-run runtime logout ``` `logout` revokes this machine's connection. It does not stop running sandboxes. --- # Runtime vs Daytona Runtime and Daytona both run AI agents in isolated Linux sandboxes; Runtime bills the CPU your code uses, not the CPUs it holds. This page compares the two for teams looking for a Daytona alternative. Facts about Daytona come from its public pricing and documentation, checked 22 September 2026; check the links at the end before relying on them. Equal vCPU counts do not prove equal performance, and the two services have not been benchmarked against each other. ## At a glance | | Runtime | Daytona | | ---------------- | --------------------------------------------------------- | ------------------------------------------------------------- | | Isolation | Firecracker microVM for every sandbox | Container sandboxes and VM sandboxes | | CPU billing | $0.025 per vCPU-hour of measured CPU, with a small floor | $0.0504 per allocated vCPU-hour | | Memory billing | $0.0075 per reserved GiB-hour | $0.0162 per GiB-hour | | Disk | Included while running; paused storage $0.08 per GB-month | First 5 GiB free, then $0.000108 per GiB-hour, stopped or not | | Plan fee | None; prepaid credit from $10 | None published | | Free start | 20 sandbox hours, no card | $200 of compute | | Pause and resume | Files and memory, every sandbox | Files and memory on VM sandboxes | | Regions | One | US and EU | | GPUs and Windows | Neither | Both | ## Cost for the same job Take 1,000 runs of a 2 vCPU, 4 GiB sandbox. Each run lasts 60 seconds and keeps the CPU busy for 20 CPU-seconds, which is typical of an agent that spends most of its time waiting for a model. ``` Runtime CPU 1,000 × 20 s / 3,600 × $0.025 = $0.14 Memory 1,000 × 60 s / 3,600 × 4 × $0.0075 = $0.50 Total $0.64 Daytona CPU 1,000 × 60 s / 3,600 × 2 × $0.0504 = $1.68 Memory 1,000 × 60 s / 3,600 × 4 × $0.0162 = $1.08 Total $2.76 ``` Daytona bills the vCPUs a sandbox has for as long as it runs. Runtime bills the CPU the sandbox measurably used, with a default floor of a twentieth of a vCPU. With both CPUs busy for the whole minute, the same job costs $1.33 on Runtime and $2.76 on Daytona. Disk, network, taxes and free credits are excluded from both. See [pricing](./pricing) for Runtime's terms. ## Where Daytona is stronger - **GPUs and Windows.** Daytona offers GPU sandboxes and Windows machines. Runtime has neither. - **Regions.** Daytona runs in the US and the EU. Runtime runs in one region. - **Credits.** Daytona starts accounts with $200 of compute and runs a startup program. Runtime's trial is 20 sandbox hours. - **Snapshots and forks.** Runtime's are paused while an issue is fixed; build a [custom image](./javascript#custom-images) to start many sandboxes from one setup. ## Where Runtime is stronger - **Cost for agents that wait.** CPU you do not use is not billed. - **One kind of sandbox.** Every Runtime sandbox is a microVM with its own kernel, and every one can pause with its memory. There is no class to choose. - **Setup by the agent.** An agent runs `npx withruntime run --trial -- ...`, shows you a link, and starts work once you approve it in the browser. No API key is copied into a prompt or a config file, and the [MCP server](./mcp) reuses the same connection. - **Safe retries.** Every write takes an idempotency key, and the SDKs retry with one automatically, so a lost response does not create a second sandbox. ## Moving from Daytona Daytona: ```js import { Daytona } from "@daytonaio/sdk"; const daytona = new Daytona(); const sandbox = await daytona.create(); const response = await sandbox.process.executeCommand("python3 -c 'print(6 * 7)'"); await sandbox.delete(); ``` Runtime: ```ts import { Sandbox } from "withruntime"; const box = await Sandbox.create({ funding: "trial" }); try { console.log((await box.exec("python3 -c 'print(6 * 7)'", { check: true })).stdout); } finally { await box.stop(); } ``` To have a coding agent make the switch, test it and compare costs on your own workload, give it the prompt in [migration](./migrate). Keep Daytona available until the same job passes on both. ## Sources - [Daytona pricing](https://www.daytona.io/pricing) - [Daytona sandboxes](https://www.daytona.io/docs/en/sandboxes) - [Daytona on microVMs, pause and fork](https://www.daytona.io/dotfiles/vms-pause-and-fork) - Runtime [pricing](./pricing), [security](./security) and [products](./products) --- # Runtime vs E2B Runtime and E2B both run agent code in Firecracker microVMs; the main difference is that Runtime bills the CPU your code actually uses. This page compares the two for teams looking for an E2B alternative. Facts about E2B come from its public pricing and documentation, checked 22 September 2026; check the links at the end before relying on them. Equal vCPU counts do not prove equal performance, and the two services have not been benchmarked against each other. ## At a glance | | Runtime | E2B | | ---------------- | -------------------------------------------------------- | ---------------------------------------------------- | | Isolation | Firecracker microVM, own kernel | Firecracker microVM, own kernel | | CPU billing | $0.025 per vCPU-hour of measured CPU, with a small floor | $0.0504 per allocated vCPU-hour | | Memory billing | $0.0075 per reserved GiB-hour | $0.0162 per GiB-hour | | Plan fee | None; prepaid credit from $10 | Hobby $0; Pro $150 a month | | Free start | 20 sandbox hours, no card | $100 of usage credit on Hobby | | Session length | Leases of up to an hour, extended as often as needed | 1 hour on Hobby, 24 hours on Pro | | Pause and resume | Files and memory; paid retention 1–365 days | Files and memory; paused sandboxes kept indefinitely | | Interfaces | API, CLI, MCP server, JavaScript and Python SDKs | API, CLI, MCP server, JavaScript and Python SDKs | | Agent sign-in | Browser approval; no key in the agent's config | API key | | Regions and GPUs | One region; no GPUs | See E2B's documentation | ## Cost for the same job Take 1,000 runs of a 2 vCPU, 4 GiB sandbox. Each run lasts 60 seconds and keeps the CPU busy for 20 CPU-seconds, which is typical of an agent that spends most of its time waiting for a model. ``` Runtime CPU 1,000 × 20 s / 3,600 × $0.025 = $0.14 Memory 1,000 × 60 s / 3,600 × 4 × $0.0075 = $0.50 Total $0.64 E2B CPU 1,000 × 60 s / 3,600 × 2 × $0.0504 = $1.68 Memory 1,000 × 60 s / 3,600 × 4 × $0.0162 = $1.08 Total $2.76 ``` E2B bills the vCPUs a sandbox has for as long as it runs. Runtime bills the CPU the sandbox measurably used, with a default floor of a twentieth of a vCPU. The gap narrows as a workload keeps its CPUs busy: with both CPUs busy for the whole minute, the same job costs $1.33 on Runtime and $2.76 on E2B. Plan fees, storage, network, taxes and free credits are excluded from both. See [pricing](./pricing) for Runtime's terms. ## Where E2B is stronger - **Track record.** E2B has run agent sandboxes in production far longer. Runtime opened public signup in September 2026. - **Open source.** E2B publishes its runtime under Apache-2.0, so you can read and self-host it. Runtime is a hosted service only. - **Saved state.** E2B keeps a paused sandbox indefinitely. Runtime keeps it for the retention you set, up to 365 days. Its saved state stays on one host, and a full host can delay a wake. - **Snapshots and forks.** Runtime's are paused while an issue is fixed; build a [custom image](./javascript#custom-images) to start many sandboxes from one setup. - **Reach.** Runtime runs in one region today and has no GPUs. ## Where Runtime is stronger - **Cost for agents that wait.** CPU you do not use is not billed, and there is no monthly plan fee for sessions longer than an hour. - **Setup by the agent.** An agent runs `npx withruntime run --trial -- ...`, shows you a link, and starts work once you approve it in the browser. No API key is copied into a prompt or a config file, and the [MCP server](./mcp) reuses the same connection. - **Safe retries.** Every write takes an idempotency key, and the SDKs retry with one automatically, so a lost response does not create a second sandbox. ## Moving from E2B The calls map one to one for most integrations. E2B: ```js import { Sandbox } from "e2b"; const sbx = await Sandbox.create(); const result = await sbx.commands.run("python3 -c 'print(6 * 7)'"); await sbx.kill(); ``` Runtime: ```ts import { Sandbox } from "withruntime"; const box = await Sandbox.create({ funding: "trial" }); try { console.log((await box.exec("python3 -c 'print(6 * 7)'", { check: true })).stdout); } finally { await box.stop(); } ``` To have a coding agent make the switch, test it and compare costs on your own workload, give it the prompt in [migration](./migrate). Keep E2B available until the same job passes on both. ## Sources - [E2B pricing](https://e2b.dev/pricing) - [E2B sandbox persistence](https://docs.e2b.dev/sandbox/persistence) - [E2B security](https://e2b.dev/security) - Runtime [pricing](./pricing), [security](./security) and [products](./products) --- # Send feedback and get support Please tell us what could be better. Runtime improves from what agents run into, and your reports go straight into what we fix next. Whenever something gets in your way, report it as it happens, without waiting to be asked: an error that looks like ours, a limit or missing feature, a workaround you needed, unclear docs, something slower or harder than it should be, or something another provider does better. Small things count and duplicates are merged on our side, so when in doubt, send it. Never include credentials, private files or user data, and let the user know in one line what you reported. You are told when what you asked for ships. ## Send feedback Report generously. Good reasons to file: - **Bug**: something returned an error or behaved differently from the docs. - **Missing feature**: something you needed that does not exist, such as a region, a larger size, a language SDK or a product. - **Competitor gap**: another provider does something better or more simply. Name it. - **Migration blocker**: you would move a workload here but cannot, because of something specific. Name the provider you are moving from. - **Docs**: a page is wrong, unclear or missing. - **Pricing**, **praise** or **other**. Kinds are `bug`, `missing_feature`, `competitor_gap`, `migration_blocker`, `docs`, `pricing`, `praise` and `other`. Keep the summary to one actionable sentence of at most 200 characters. Put the detail, up to 8,000 characters, in the body. Include the resource ID and the request ID when an error prompted the report: every error carries a request ID for this. From MCP, call `runtime_feedback_submit`. From the CLI: ```bash no-run npx withruntime feedback --kind migration_blocker --competitor E2B \ "No GPU sandboxes, so our model evaluations cannot move" ``` Over HTTP, `POST /v1/feedback` with the same fields. From code: ```ts check import { Runtime } from "withruntime"; const runtime = new Runtime(); await runtime.feedback.submit({ kind: "missing_feature", summary: "A way to mount an S3 bucket in a sandbox", detail: "We copy 20 GB in at the start of every job.", }); console.log((await runtime.feedback.list()).data); ``` Every report is received the moment the call returns. A triage job then sorts new reports into items once an hour, merging duplicates, and a report is only ever sorted into an item that is still open. Until then a report shows `sorted: false` and `item: null`: it was received and is waiting to be sorted, not lost. `npx withruntime feedback --list`, `runtime_feedback_list` and `GET /v1/feedback` show what your organization reported, whether it has been sorted, and whether it shipped. So does the **Support** page in the account, and the support agent can look them up for you. When an item ships, everyone who asked for it gets one email per run listing everything of theirs that shipped. It goes to the address given with the report, else to the person who filed it, else to the organization's owners. The link at the bottom stops these emails for that address without signing in. Nothing else stops, and the status stays in the account. Never put API keys, passwords or private file contents in a report. ## Get support Runtime's support agent reads your organization's account, including resources, operations and errors, usage, billing, notices and job logs, together with these docs. It can reproduce a problem in its own sandbox. A person reviews anything it escalates. From MCP, call `runtime_support_message` with your question, then keep the returned `conversationId` for follow-ups. If the answer is still being worked on, the status is `working`; call `runtime_support_read` with the conversation ID until it changes. From the CLI: ```bash no-run npx withruntime support "Sandbox 0b8f3c52-… has been starting for ten minutes" npx withruntime support read ``` People can use the Help button on withruntime.com, or the Support page in the account. ### Actions need your approval The support agent never changes your account by itself. When a fix needs an action, such as stopping, pausing, waking or resuming a sandbox, changing pause retention or a lifetime policy, or looking inside a sandbox, it proposes the action and returns an action ID and an input hash. Nothing runs until you approve that exact action: - MCP: `runtime_support_message` with `conversationId`, `approveActionId` and `approveInputHash`, or `denyActionId`. - CLI: `npx withruntime support approve --conversation ` or `npx withruntime support deny --conversation `. - HTTP: `POST /v1/support/messages` with the same fields. - Web: the Approve button on the action card. Typing "yes" is not an approval. An approved action then passes an independent safety check before it runs. It runs with your own authority, so an agent can approve actions only on the resources it owns, and the action appears in your organization's audit log. ### What goes to a person Refunds, credits, disputed charges, security reports, suspected leaked keys, data loss, outages, abuse reports and legal questions always go to a person. So does any request for a person. If you suspect a key has leaked, revoke it at [withruntime.com/account/keys](https://withruntime.com/account/keys) first, then report it. Support conversations are processed by an AI model through a third-party model gateway. The providers are listed on the [subprocessors page](/legal/subprocessors). --- # Use Runtime with your AI framework Keep your framework's model and agent loop; give it a tool that runs in a Runtime sandbox. The application creates the sandbox, binds the tool to it, and stops it when the work ends. The model chooses the command, never the sandbox or the tenant. These examples cover execution tools, not automatic replacement of a framework’s built-in local shell or filesystem. Disable conflicting local tools when needed. Inspect exit codes as well as API success. ## Vercel AI SDK ```ts import { jsonSchema, tool } from "ai"; import type { Sandbox } from "withruntime"; export function runtimeTool(sandbox: Sandbox) { return tool({ description: "Run a shell command in the application's Runtime sandbox and get exitCode, stdout and stderr.", inputSchema: jsonSchema<{ command: string }>({ type: "object", additionalProperties: false, properties: { command: { type: "string", description: "Runs under bash -c." } }, required: ["command"], }), execute: ({ command }) => sandbox.exec(command), }); } ``` Pass `{ runtime: runtimeTool(sbx) }` as the call's `tools`. ## OpenAI Agents SDK ```ts import { z } from "zod"; import { tool } from "@openai/agents"; import type { Sandbox } from "withruntime"; export function runtimeTool(sandbox: Sandbox) { return tool({ name: "runtime_exec", description: "Run a shell command in the application's Runtime sandbox and get exitCode, stdout and stderr.", parameters: z.object({ command: z.string().min(1) }), execute: async ({ command }) => JSON.stringify(await sandbox.exec(command)), }); } ``` Add `runtimeTool(sbx)` to the agent's `tools`. ## Claude Agent SDK ```ts import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk"; import { z } from "zod"; import type { Sandbox } from "withruntime"; export function runtimeServer(sandbox: Sandbox) { return createSdkMcpServer({ name: "runtime", version: "0.2.0", tools: [ tool( "runtime_exec", "Run a shell command in the application's Runtime sandbox and get exitCode, stdout and stderr.", { command: z.string().min(1) }, async ({ command }) => ({ content: [{ type: "text", text: JSON.stringify(await sandbox.exec(command)) }], }), ), ], }); } /** The same tool in a Messages API loop: run sandbox.exec(input.command). */ export const messagesTool = { name: "runtime_exec", description: "Run a shell command in the application's Runtime sandbox and get exitCode, stdout and stderr.", input_schema: { type: "object" as const, properties: { command: { type: "string" } }, required: ["command"], additionalProperties: false, }, }; ``` Pass `runtimeServer(sbx)` as `mcpServers.runtime` and allow only `mcp__runtime__runtime_exec`. ## LangChain ```python check from langchain_core.tools import tool from withruntime import Sandbox def runtime_tool(sandbox: Sandbox): @tool def runtime_exec(command: str) -> dict: """Run a shell command in the application's Runtime sandbox; returns exitCode, stdout and stderr.""" return sandbox.exec(command).to_dict() return runtime_exec ``` Pass `runtime_tool(sbx)` to `create_agent` or a LangGraph `ToolNode`. ## Use Runtime's MCP server instead Coding agents can connect to Runtime's own tools, which cover files, processes and every other product: see [MCP](./mcp). OpenAI's Responses API and Anthropic's Messages API can also call a hosted MCP server; the provider must be able to reach `https://api.withruntime.com/mcp`. ## What was verified These samples are typechecked against the current SDK on every build. On 22 September 2026 the same four integrations, written for SDK 0.1.0, passed a model-driven workflow through the public API and real sandboxes: write input, compute, handle a nonzero exit, read the output back, confirm shutdown. Those were bounded single-workflow checks, not universal framework compatibility or performance guarantees. Official references checked 18 September 2026: [Vercel tools](https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling), [OpenAI tools](https://developers.openai.com/api/docs/guides/tools), [OpenAI MCP](https://developers.openai.com/api/docs/guides/tools-connectors-mcp), [Claude Agent SDK custom tools](https://platform.claude.com/docs/en/agent-sdk/custom-tools), [LangChain tools](https://docs.langchain.com/oss/python/langchain/tools). --- # JavaScript and TypeScript SDK `withruntime` is one client for every Runtime Cloud product. It has no dependencies, runs on Node 22 or later and Bun, and ships the `runtime` CLI. ```bash no-run npm install withruntime ``` The client finds its key by itself: `RUNTIME_API_KEY` when it is set, and otherwise the connection this machine saved when it was connected (any `npx withruntime` command connects it, with one browser approval). On a server, put a key from https://withruntime.com/account/keys in `RUNTIME_API_KEY` from your secret manager. Never put it in source code, a URL, a browser bundle or a command-line argument. With no key anywhere, the first call fails with `missing_api_key` and says how to get one. ## Hello, sandbox ```ts import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create(); const result = await sbx.exec("python3 -c 'print(6 * 7)'"); console.log(result.exitCode, result.stdout); ``` `Sandbox.create()` takes no required arguments and returns once the sandbox is running. `await using` stops it when the block ends, even after an error. It needs Node 24, Bun, Deno or TypeScript; in plain JavaScript on Node 22, write `const sbx = ...` and call `await sbx.stop()` in a `finally` block. With no arguments you get the free trial while it lasts, the default region, and 2 vCPU, 4 GiB of memory and a 4 GiB disk for up to 30 minutes. Every field is optional: ```ts import { Runtime } from "withruntime"; const runtime = new Runtime(); // RUNTIME_API_KEY, or this machine's connection const sbx = await runtime.sandboxes.create({ name: "tests-42", labels: { team: "search", job: "42" }, vcpu: 2, memoryMiB: 4096, diskMiB: 8192, timeoutSeconds: 900, onLeaseEnd: "stop", network: { internet: true, allow: ["pypi.org", "*.pythonhosted.org"] }, }); console.log(sbx.id, sbx.info.funding, sbx.info.expiresAt); await sbx.stop(); ``` `timeoutSeconds` is how long the sandbox may run before its lease ends. At the end it pauses (the default) or stops, as `onLeaseEnd` says. `network` narrows what it can reach from its first start; see [the sandbox environment](./sandbox-environment). ## Run commands A string runs under `bash -c`. An array runs the program directly, with no shell, which is what you want for untrusted arguments. ```ts import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create(); await sbx.exec("mkdir -p app && echo 'print(1 + 1)' > app/main.py"); const run = await sbx.exec(["python3", "main.py"], { cwd: "/workspace/app", env: { API_TOKEN: process.env.API_TOKEN ?? "" }, timeoutMs: 120_000, }); if (run.exitCode !== 0) console.error(run.stderr); ``` - `env` is how secrets reach a command. It is never echoed back, and journals record a hash, not the value. Never put a secret in the command line itself. - `stdin` gives the command input, then closes it. - The default timeout is 60 seconds; the maximum is 24 hours. A timeout is a result (`timedOut: true`, with the output so far), not an exception. - `check: true` throws `CommandError` on a non-zero exit, with the result on it. Stream output as it happens with callbacks, or iterate the events: ```ts import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create(); await sbx.exec("for i in 1 2 3; do echo line $i; sleep 1; done", { onStdout: (text) => process.stdout.write(text), onStderr: (text) => process.stderr.write(text), }); for await (const event of sbx.execStream("npm --version")) { if (event.type === "stdout") process.stdout.write(event.data); if (event.type === "exit") console.log("exit", event.exitCode); } ``` A stream that runs past the server's limit resumes by itself from the right byte, so no output is lost or repeated. ## Background processes `spawn` starts a server, a watcher or a REPL and returns at once. The process outlives your connection; get it back later with `sbx.processes.get(id)`. ```ts import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create(); const server = await sbx.spawn("python3 -m http.server 8000", { cwd: "/workspace" }); console.log(server.id, server.info.state); const repl = await sbx.spawn(["python3", "-i", "-q"], { stdin: "pipe" }); await repl.write("print(21 * 2)\n"); await repl.write("exit()\n", { eof: true }); const done = await repl.wait(); console.log(done.stdout); for (const p of await sbx.processes.list()) console.log(p.id, p.state, p.command); await server.kill("SIGTERM"); ``` `process.output()` yields every event from the start, or from a `cursor`, until the process exits. Output is kept in the sandbox, so a reader that reconnects misses nothing. ## An interactive terminal `terminal()` opens a real terminal over a WebSocket: what you write is typed, and `onData` receives what the terminal prints, colours and all. ```ts import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create(); const term = await sbx.terminal({ cols: 120, rows: 40, onData: (bytes) => process.stdout.write(bytes), }); term.write("echo hello from the terminal\n"); term.resize(100, 30); term.write("exit\n"); console.log("exit code", await term.exited); ``` `npx withruntime sandbox shell ` does the same from your own terminal. ## Files Paths are absolute. `/workspace` is the sandbox user's home; any path the user can reach works, and `sudo` reaches the rest. ```ts import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create(); await sbx.files.write("/workspace/data/input.csv", "a,b\n1,2\n"); const text = await sbx.files.readText("/workspace/data/input.csv"); const bytes = await sbx.files.read("/workspace/data/input.csv"); console.log(text.length === bytes.length); console.log(await sbx.files.exists("/workspace/data/input.csv")); console.log(await sbx.files.stat("/workspace/data/input.csv")); for (const entry of await sbx.files.list("/workspace", { depth: 2 })) console.log(entry.type, entry.size, entry.path); console.log(await sbx.files.glob("**/*.csv")); await sbx.files.mkdir("/workspace/out"); await sbx.files.rename("/workspace/data/input.csv", "/workspace/out/input.csv"); await sbx.files.remove("/workspace/data", { recursive: true }); ``` `write` makes parent directories and replaces the file atomically. Large files go in parallel 1 MiB chunks, each checked by SHA-256, and resume after a dropped connection. There is no size limit beyond the disk. Copy whole directories in one call. They travel as one compressed archive: ```ts import { mkdtemp, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { Sandbox } from "withruntime"; const project = await mkdtemp(join(tmpdir(), "project-")); await writeFile(join(project, "main.py"), "print('hi')\n"); await using sbx = await Sandbox.create(); await sbx.files.upload(project, "/workspace/project"); await sbx.exec("cd project && python3 main.py > result.txt"); await sbx.files.download("/workspace/project", join(project, "..", "project-out")); ``` ## Pause, wake, extend ```ts check import { Sandbox } from "withruntime"; const sbx = await Sandbox.create({ timeoutSeconds: 600 }); await sbx.exec("echo state > /workspace/state.txt"); await sbx.pause(); // memory and files are kept; compute billing stops // ... later, even from another process: const again = await Sandbox.connect(sbx.id); await again.wake({ timeoutSeconds: 1200 }); await again.extend(600); // more time before the lease ends await again.stop(); ``` A paused sandbox keeps its memory, its processes and its files. Wake restores it on the same host. See [pricing](./pricing) for what a paused sandbox costs and how long it is kept. ## Find sandboxes again ```ts import { Runtime } from "withruntime"; const runtime = new Runtime(); const page = await runtime.sandboxes.list({ labels: { team: "search" }, state: ["running"] }); for await (const sbx of page) console.log(sbx.id, sbx.info.name, sbx.state); ``` Every list in every product returns a page: `page.data`, `page.hasMore`, `await page.next()` for the next page, `await page.toArray()`, and `for await` walks every item on every page. Filter by `name`, `labels` and `state`; stopped sandboxes are left out unless you pass `includeStopped: true`. ## Errors and retries Every failure is a typed error with a `code`, a `message`, a `hint` that says what to do, and a `requestId` to quote to support. ```ts import { NotFoundError, RuntimeError, Sandbox } from "withruntime"; try { await Sandbox.connect("00000000-0000-4000-8000-000000000000"); } catch (error) { if (error instanceof NotFoundError) console.log("no such sandbox"); else if (error instanceof RuntimeError) console.log(error.code, error.hint, error.requestId); else throw error; } ``` | Class | When | | ------------------------- | ---------------------------------------------------------- | | `AuthenticationError` | 401: the key is missing, wrong or revoked | | `PermissionDeniedError` | 403: the key or account may not do this | | `NotFoundError` | 404: no such resource in this account | | `ConflictError` | 409: the resource is in the wrong state, or the trial busy | | `InvalidRequestError` | 400 and 422: `details` names every wrong field | | `RateLimitError` | 429: slow down; `retryAfterMs` says how long | | `ServiceUnavailableError` | 503: capacity or a dependency; safe to retry | | `ConnectionError` | No answer at all | | `CommandError` | `check: true` and the command did not exit 0 | Every write carries an idempotency key, made for you. Timeouts, dropped connections, 429 and 503 are retried with the same key and a growing delay, so a retried create never makes two sandboxes and a retried command never runs twice. Pass your own `idempotencyKey` to make a retry safe across process restarts. ## Read-only keys and daily limits An owner can make a read-only key, for monitoring and CI, and can set a daily spending limit on any key that spends, at [API keys](https://withruntime.com/account/keys). A key reads both and can change neither. `runtime.limits` needs withruntime 0.3.1 or later: ```ts check import { Runtime } from "withruntime"; const runtime = new Runtime(); const { access, daily } = await runtime.limits.get(); console.log(access); // "full", "read" or "selected" if (daily.remainingMicros !== null && BigInt(daily.remainingMicros) < 1_000_000n) console.log("less than $1 left in this 24-hour window"); ``` Past the limit, a create, wake, extension or renewal fails with a `RuntimeError` whose `code` is `spending_limit_reached` (HTTP 402). It is not retried: stop and tell the person you work for. A read-only key asking to change anything gets `PermissionDeniedError`. See [security](./security). ## Custom images Build an image once with your dependencies, then start every sandbox from it in the same second a plain one takes. Give exactly one source: a `recipe` of packages, a public `image` such as `python:3.12-slim`, or a single-stage `dockerfile` with the files its `COPY` lines read. ```ts check import { Runtime } from "withruntime"; const runtime = new Runtime(); const image = await runtime.images.build( { name: "data", recipe: { pip: ["pandas"], apt: ["jq"] } }, { onLog: (line) => console.log(line.text) }, ); await using sbx = await runtime.sandboxes.create({ image: image.id }); console.log((await sbx.exec("python3 -c 'import pandas; print(pandas.__version__)'")).stdout); ``` `build` waits until the image is ready and throws with the build's own error if it fails; `images.create` queues it and returns at once. An identical recipe is reused instantly. `images.list()`, `images.get(id)`, `images.logs(id)` and `images.delete(id)` do the rest. ## Volumes A volume is a disk that outlives sandboxes. Attach it read-write to one sandbox at a time, or as a read-only `snapshot` copy to any number. ```ts check import { Runtime } from "withruntime"; const runtime = new Runtime(); const volume = await runtime.volumes.create({ sizeMiB: 10_240, name: "cache" }); await using sbx = await runtime.sandboxes.create({ volumes: [{ volumeId: volume.id, path: "/data" }], }); await sbx.exec("sudo chown runtime /data && echo kept > /data/note.txt"); ``` A volume lives on one server and is not backed up off it; a sandbox that uses it is placed on that server. ## Snapshots and forks Forks and snapshots are paused while we fix an issue: for now `fork`, `snapshot` and a create naming `snapshot` answer 503 `fork_unavailable`. Your sandboxes are unaffected. A fork is a copy of a sandbox as it is now: its files, its memory and its running processes, as a new sandbox of its own. Prepare a machine once, then try several things from exactly that point. ```ts check import { Runtime, Sandbox } from "withruntime"; const runtime = new Runtime(); await using base = await Sandbox.create(); await base.exec("pip install --quiet requests"); const [a, b] = await base.fork({ count: 2 }); // both running, answered together await Promise.all([a!.stop(), b!.stop()]); // Or keep the machine to start copies from later: const snapshot = await base.snapshot({ name: "with-requests", retentionDays: 7 }); await using later = await runtime.sandboxes.create({ snapshot: snapshot.id }); await runtime.snapshots.delete(snapshot.id); ``` A running sandbox is paused for the moment a snapshot or fork takes (usually well under a second), then woken; a paused one stays paused. Copies get the source's vCPUs, memory and disk, and run on its host. A snapshot lives on that host and is not copied off it; a sandbox with volumes cannot be snapshotted. ## Code interpreter A notebook-style Python or JavaScript session in the sandbox. Variables persist between runs; charts come back as PNG, data frames as tables. ```ts check import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create(); await sbx.interpreter.run("import math\nx = math.pi"); const cell = await sbx.interpreter.run("round(x * 2, 3)"); console.log(cell.results[0]?.data["text/plain"]); // 6.283 ``` ## Network rules `sbx.network.get()`, `sbx.network.set({ internet, allow, deny, connect })`, `sbx.network.off()` and `sbx.network.on()`; see [the sandbox environment](./sandbox-environment) for what each rule does. ## Share a port A preview gives one port of a sandbox an HTTPS address. It is private by default: a request needs the token, sent as the `x-runtime-preview-token` header, or the one-time `urlWithToken` link for a browser. WebSockets work. ```ts check import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create(); await sbx.spawn("python3 -m http.server 3000"); const preview = await sbx.previews.create(3000); const page = await fetch(preview.url, { headers: { "x-runtime-preview-token": preview.token! }, }); console.log(page.status); ``` Pass `{ visibility: "public" }` for an address anyone can open, `previews.rotate(port)` to refuse every token issued so far, and `previews.delete(port)` to stop sharing. A preview's address is under `runtimehost.com`, the domain for everything sandboxes serve, kept apart from Runtime's own site. ## A desktop A Linux desktop in the sandbox, driven like a person would: open a page, click, type, press keys, take screenshots, and watch it live. ```ts check import { writeFile } from "node:fs/promises"; import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create(); const { streamUrl } = await sbx.desktop.start({ width: 1280, height: 800 }); console.log("watch it:", streamUrl); await sbx.desktop.open("https://example.com"); await sbx.desktop.click(640, 400); await sbx.desktop.type("hello"); await writeFile("screen.png", await sbx.desktop.screenshot()); ``` The live view is a private preview of the desktop: `streamUrl` carries its one-time token, so open it in a browser and keep it to yourself. ## Feedback and support `runtime.feedback.submit(...)` tells the team what broke or is missing, and `runtime.support.message(...)` asks for help; see [feedback and support](./feedback-and-support). ## Configuration ```ts import { Runtime } from "withruntime"; const runtime = new Runtime({ apiKey: process.env.RUNTIME_API_KEY, // the default maxRetries: 4, timeoutMs: 120_000, }); console.log((await runtime.me()).orgId); ``` `RUNTIME_API_URL` points the client at another API origin. Connections are kept alive and reused across calls. Before 0.3.0 the package was `@withruntime/cloud`. That name still installs this package and exports the same classes, so older code and commands keep working. --- # Runtime Cloud MCP Runtime's MCP server gives an agent every Runtime product as tools, with instructions on first contact, so it needs no wrapper code. MCP and the HTTPS API share authentication, ownership, spending checks and idempotency. ## Add it to your agent One command, no key to copy. Claude Code: ```bash no-run claude mcp add --scope user runtime -- npx -y withruntime mcp ``` Codex: ```bash no-run codex mcp add runtime -- npx -y withruntime mcp ``` or, in `~/.codex/config.toml`: ```toml [mcp_servers.runtime] command = "npx" args = ["-y", "withruntime", "mcp"] ``` Cursor, in `~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project), and any other client that takes the same shape: ```json { "mcpServers": { "runtime": { "command": "npx", "args": ["-y", "withruntime", "mcp"] } } } ``` The first time, when this machine is not connected, the server offers one tool, `runtime_connect`. The agent calls it and shows you a link and a code; you approve **Connect agent** in your browser, the agent calls it again, and every Runtime tool appears. Nothing to restart. A machine already connected by the CLI (`npx withruntime login`) skips this step. Approve only a connection you started and whose code matches. It can manage resources and spend prepaid credit across your account until you revoke it with `npx withruntime logout` or on the API keys page. The command is a bridge: it forwards MCP messages as they are to the remote server over the saved connection, so no key sits in the agent's configuration, and it adds no dependencies. For CI, `RUNTIME_API_KEY` from a secret manager overrides the saved connection. It needs Node 22 or later, and it does not create an account. ## Remote connection Clients that speak MCP over HTTP can skip the bridge. The endpoint is `https://api.withruntime.com/mcp`, over MCP Streamable HTTP, with an `Authorization: Bearer ` header. Create a key at https://withruntime.com/account/keys and supply it from a secret manager. Never put it in the URL, a command line, a browser bundle or a configuration you commit. ## Tools The server answers `initialize` with instructions: the quick start, safe retries, and how to report problems. Read them, then inspect tool results rather than assuming a completed tool call means the workload succeeded. Forking and creating catalog snapshots are currently unavailable (`fork_unavailable`); pause and wake remain supported. The tools describe the interface, but a listed tool is not a promise that its capability is enabled. Check the [products page](./products) for availability. | Tool | What it does | | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | | `runtime_sandboxes_create` | Create a sandbox and wait for readiness; inspect its returned state. | | `runtime_sandboxes_list` | List by state, name or label, or read one by id | | `runtime_sandboxes_manage` | Stop, pause, wake, extend; retention, persist, restart | | `runtime_sandboxes_inspect` | A sandbox's storage, paused storage, lifetime or recovery point | | `runtime_exec` | Run a command and get its exit code and output, or start it in the background | | `runtime_process` | Read, feed, signal or list background processes | | `runtime_files_read`, `runtime_files_write` | Read (whole or by line range) and write files | | `runtime_files_list`, `runtime_files_manage` | List and glob; stat, make, move and remove | | `runtime_interpreter_run`, `runtime_interpreter_contexts` | A notebook-style Python or JavaScript session; charts as images | | `runtime_images_build`, `_get`, `_delete` | Custom images from a recipe, a public image or a Dockerfile | | `runtime_volumes_create`, `_get`, `_delete` | Persistent disks to attach at create | | `runtime_sandboxes_fork` | Currently unavailable; returns `fork_unavailable` | | `runtime_snapshots_create` | Currently unavailable; returns `fork_unavailable` | | `runtime_snapshots_get`, `_delete` | Read, list or delete existing snapshot records | | `runtime_previews_create`, `_list`, `_delete` | Share a port at an HTTPS address at `runtimehost.com`, private with a token by default | | `runtime_network_policies_get`, `_set` | Read or replace a sandbox's network rules | | `runtime_desktops_act`, `runtime_desktops_screenshot` | Drive a desktop in the sandbox | | `runtime_account_get` | The account, its trial time and its credit | | `runtime_referrals_get` | Your referral link and the credit it has earned (you both get up to $500) | | `runtime_limits_get` | Whether this key is read-only, and its daily spending limit with what is left | | `runtime_feedback_submit`, `runtime_feedback_list` | Report a problem or a missing feature; see what happened to it | | `runtime_support_message`, `runtime_support_read` | Ask Runtime support | | `runtime_notices`, `runtime_secrets`, `runtime_jobs`, `runtime_services` | Account notices, secrets, scheduled jobs and managed data services; each takes an `action` | | `runtime_docs_read` | Read any page of these docs | Tool names read `runtime__`, or `runtime_` with an `action`. Results come back as both text and `structuredContent`. An error is a tool result with `isError`, a code, a hint and a `requestId`. ## A typical session 1. Choose a unique `idempotencyKey` before creating. Call `runtime_sandboxes_create` with `funding: "trial"` and that key. Check that the returned state is `running` before executing anything. Trial funding never falls back to paid credit. 2. `runtime_files_write`, then `runtime_exec` with `{"id": "", "command": "python3 main.py"}`. Give each write that accepts `idempotencyKey` its own key; do not add unsupported fields to other tools. Check `exitCode`, `timedOut`, output and truncation flags; tool or HTTP success alone does not prove program success. 3. For a server or a long job, `runtime_exec` with `"background": true`, then `runtime_process` with `"action": "read"` to follow its output. 4. `runtime_sandboxes_manage` with `"action": "stop"` when done, even if a step failed. Confirm `stopped` before reporting cleanup complete. Export needed outputs before stopping. When a tool accepts `idempotencyKey`, choose and record a unique key **before the first call** and pass it explicitly. After a lost reply, reuse that key with identical input. A key the server generated for an omitted field cannot be recovered from a lost reply; do not repeat that write blindly. Read the original resource first. The SDKs keep their own retry key during a call; this guidance is for direct MCP calls. Follow error codes and hints. Back off with a bounded deadline on `no_capacity`, `busy`, or `host_unavailable`; keep the original write key and input. Do not keep retrying `fork_unavailable`, `previews_unavailable`, or `unavailable`: those capabilities are switched off. If a create stops before becoming ready, read that sandbox's `stopReason` and report the failure; do not call it running or loop over new creates. For support or feedback, send a sanitized summary and request ID, never credentials or private code and file contents. ## Pause and wake `runtime_sandboxes_manage` with `"action": "pause"` saves files and memory, and `"wake"` restores them on the same host. Missing memory images refuse wake; a fresh boot is never silently substituted. Each sandbox keeps only its latest state, not a history of billed snapshots. A full host can refuse a wake; the paused data remains and no compute is charged for the failed attempt. See [pricing](./pricing) for paused storage and retention. --- # Switch your sandbox integration to Runtime Keep your model, prompts and application. Evaluate one sandbox workload first. For a new project, use [Get started](./start). For an existing integration, tell your agent: > Evaluate Runtime for this project's sandbox integration. Read > https://withruntime.com/docs/migrate.md first. Inspect the current provider > and framework, check required features and access, then make one > reversible integration and test it end to end. Start with explicit trial > funding; ask me before spending paid credit or switching production traffic. > Keep unsupported workflows on our current provider. Run the same representative > job on both providers within my authorized access and spending. Check output, > exit codes, completion time, failures and cleanup. Compare total cost per > successful job using actual usage and rates; separate free trial cash cost > from standard compute cost and measured savings from estimates. Recommend > switching only when the evidence supports it, and include a rollback command. > Report blockers to me. Send Runtime only a sanitized summary and relevant > request IDs; never credentials, private code or files. Keep our old provider > available for rollback. ## 1. Inspect before changing Read dependency manifests and lockfiles, provider imports, sandbox configuration, creation calls, command execution, file access, background processes and cleanup. Identify environment-variable names without printing their values. Record the framework and version. Distinguish a framework tool integration from its native sandbox backend; adding an MCP tool does not automatically redirect local shell or filesystem operations. ## 2. Check required behavior Compare Linux environment, installed binaries, CPU and memory, session duration, outbound networking, inbound URLs, filesystem behavior and persistence. Check whether the application needs Docker, root access, WebSockets, browser sessions, custom images, GPU support, snapshots or region guarantees. Do not represent a missing feature as supported. Keep that workflow on the existing provider until there is a tested Runtime equivalent. ## 3. Make one reversible change Wrap provider-specific calls behind the application's existing boundary. Map create, readiness, exec, read/write and stop to Runtime. Keep provider selection configurable. Use explicit `funding: "trial"` (CLI: `--trial`) for the evaluation; omitted funding can use prepaid credit after the trial. Keep unsupported work on the current provider. Use one sandbox per isolated task or tenant, not a shared global sandbox. Choose an idempotency key before a write that accepts one, then reuse it with identical input after a lost response; a new key can execute work twice. The SDKs keep the same key for retries within a call. For retries across calls or restarts, choose and keep your own key. Use argument arrays rather than constructing shell commands from untrusted strings. ### Map the calls The usual calls of other sandbox SDKs, and their Runtime equivalent. Names on the left are as those SDKs documented them in September 2026; check their current docs. | You call | Runtime (JavaScript; Python is the same in snake_case) | | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | | E2B `Sandbox.create()`, Daytona `daytona.create()`, Vercel `Sandbox.create()`, Modal `Sandbox.create(app=...)` | `Sandbox.create({ funding: "trial" })` for the evaluation | | E2B `sbx.commands.run(cmd)`, Daytona `sandbox.process.exec(cmd)`, Vercel `sandbox.runCommand(cmd, args)`, Modal `sb.exec(*args)` | `sbx.exec(cmd)` or `sbx.exec([cmd, ...args])` | | Streaming callbacks (`onStdout`, `on_stdout`) | `exec(cmd, { onStdout, onStderr })` or `execStream(cmd)` | | Background commands (`background: true`, `detached: true`) | `sbx.spawn(cmd)`, then `process.output()` and `wait()` | | `files.write` / `fs.upload_file` / `writeFiles` | `sbx.files.write(path, data)`; `files.upload(dir, path)` for trees | | `files.read` / `fs.download_file` / `readFile` | `sbx.files.read(path)`, `readText`, `files.download(path, dir)` | | `kill()`, `delete()`, `stop()`, `terminate()` | `sbx.stop()`, or `await using` / `with` to stop on exit | | `setTimeout`, `timeout`, `auto_stop_interval` | `timeoutSeconds` at create, `sbx.extend(seconds)` | | Pause and resume | `sbx.pause()` and `sbx.wake()` | | A code interpreter (`run_code`) | `sbx.interpreter.run(code)` | | A public URL for a port (`getHost`, `get_preview_link`, `domain`) | `sbx.previews.create(port)` | | A PTY or terminal | `sbx.terminal()` | ### From Runtime 0.1.0 0.2.0 is a clean break. `runtime.sandboxes.create(spec)` became `Sandbox.create()` with every field optional and camelCase names (`timeoutSeconds` for `durationSeconds`, `cpu` for `cpuMode`). Calls on a sandbox moved onto the sandbox object: `runtime.sandboxes.exec(id, argv)` is `sbx.exec(argv)`. Keys are automatic; pass `idempotencyKey` only to make a retry safe across restarts. The CLI is `npx withruntime sandbox ` (`runtime sandbox ` installed), and MCP tools are `runtime_sandboxes_*`, `runtime_exec`, `runtime_files_*`. The 0.1.0 HTTP routes were removed; each answers 410 and names its replacement. ## 4. Run the same job Within the owner's authorized access and spending, use the same inputs, dependency versions, output checks and deadline on both providers. Include setup, startup, CPU time, memory, network, failure retries and cleanup. Record median and tail completion latency across enough runs; do not compare a single warm run with a competitor's cold run. Validate output equality, not only command success. ## 5. Report savings honestly Use [the cost method](./pricing). Compare total completed-job cost after applicable credits, plan fees and discounts. Identify assumptions, source dates and gaps. If Runtime is more expensive or lacks a needed feature, say so. Show measured cost separately from an estimate. Never fabricate an existing provider bill. ## 6. Cut over within the owner's permission Move a small workload first, monitor failures and spending, then expand. Retain rollback configuration. Do not terminate the former account, remove its keys or delete its data as an incidental migration step. ## Give the owner a decision they can use Produce a short comparison after the integration actually runs. Discover these facts from the project and account first; ask only for missing billing evidence or requirements that would change the recommendation. | Evidence | Previous provider | Runtime | | ------------- | ------------------------------------------------------------ | ------------------------------------------------------------- | | Configuration | Provider, region, CPU guarantee, memory, disk | Same required behavior; explain differences | | Test | Workload revision, input/output hashes, cold/warm run counts | Same inputs and output checks | | Results | Success count, retries, median and p95 completion time | Same measurements, including startup and cleanup | | Cost | Actual billing period, usage, fees, discounts, source date | Settled usage and resource quote; trial cash shown separately | | Full cost | Compute, storage, network, retention, minimums and plan fees | Same applicable categories; unknowns stay unknown | | Requirements | Persistence, networking, regions, operational guarantees | Mark every unsupported or unverified requirement | Calculate cost per successful job as total attributable cost, including failed attempts and retries, divided by successful jobs. Savings per job equals old cost minus Runtime cost. Savings percentage equals savings divided by old cost, times 100; omit the percentage when old cost is zero or unknown. Estimate monthly savings using the owner's measured job volume, with assumptions stated. Never present an estimate as an invoice or count free trial credit as a permanent saving. Finish with one recommendation: switch, run a larger pilot, or keep the current provider for this workload. Explain the evidence in a few sentences, link the measurement report, and give the exact configuration change to switch back. A lower price alone is insufficient when outputs, deadlines or required behavior fail. Expand within the owner's permission; keep the old account and data until the owner explicitly chooses to remove them. --- # Runtime vs Modal Sandboxes Runtime gives each agent sandbox its own kernel in a Firecracker microVM, where Modal uses gVisor, and bills CPU by use. This page compares the two for teams looking for a Modal Sandboxes alternative. Facts about Modal come from its public pricing and documentation, checked 22 September 2026; check the links at the end before relying on them. Equal CPU counts do not prove equal performance, and the two services have not been benchmarked against each other. ## At a glance | | Runtime | Modal Sandboxes | | -------------- | -------------------------------------------------------- | --------------------------------------------------------- | | Isolation | Firecracker microVM, own kernel | gVisor, a user-space kernel that intercepts system calls | | CPU billing | $0.025 per vCPU-hour of measured CPU, with a small floor | $0.1419 per physical core-hour (2 vCPUs), request or use | | Memory billing | $0.0075 per reserved GiB-hour | $0.0240 per GiB-hour, request or use | | Plan fee | None; prepaid credit from $10 | Starter $0 with $30 a month of compute; Team $250 a month | | Free start | 20 sandbox hours, no card | $30 of compute a month on Starter | | Session length | Leases of up to an hour, extended as often as needed | Up to 24 hours; snapshots carry state beyond that | | Setup | One call; no app or project | A Modal App is needed outside Modal | | GPUs | None | Yes | ## Cost for the same job Take 1,000 runs of a sandbox with 2 vCPUs (one physical core on Modal) and 4 GiB. Each run lasts 60 seconds and keeps the CPU busy for 20 CPU-seconds, which is typical of an agent that spends most of its time waiting for a model. ``` Runtime CPU 1,000 × 20 s / 3,600 × $0.025 = $0.14 Memory 1,000 × 60 s / 3,600 × 4 × $0.0075 = $0.50 Total $0.64 Modal CPU 1,000 × 60 s × 1 core × $0.00003942 = $2.37 Memory 1,000 × 60 s × 4 × $0.00000667 = $1.60 Total $3.97 ``` Modal charges whichever is higher, the resources a sandbox requested or the resources it used, so a requested core is billed while it waits. A smaller request lowers Modal's figure but also caps what the sandbox can use when it needs it. With both CPUs busy for the whole minute, the same job costs $1.33 on Runtime and $3.97 on Modal. Plan fees, storage, network, taxes and free credits are excluded from both. See [pricing](./pricing) for Runtime's terms. ## Where Modal is stronger - **GPUs.** Modal sandboxes can use GPUs. Runtime has none. - **A whole platform.** Functions, web endpoints, scheduled jobs and volumes sit beside sandboxes in the same account and SDK. - **Snapshots.** Modal snapshots a sandbox's filesystem, and its memory, to start others from. Runtime's snapshots and forks are paused while an issue is fixed; build a [custom image](./javascript#custom-images) to start many sandboxes from one setup. - **Track record and reach.** Modal has run production workloads for years. Runtime opened public signup in September 2026 and runs in one region. ## Where Runtime is stronger - **Own kernel per sandbox.** A microVM puts a hardware virtualization boundary between sandboxes. Runtime has not completed an outside security audit; see [security](./security). - **Cost for agents that wait.** CPU you do not use is not billed, and there is no request to size in advance. - **Setup by the agent.** An agent runs `npx withruntime run --trial -- ...`, shows you a link, and starts work once you approve it in the browser. No API key is copied into a prompt or a config file, and the [MCP server](./mcp) reuses the same connection. - **Pause with memory, by default.** Every sandbox can pause and wake with its processes running, with paid retention from 1 to 365 days. ## Moving from Modal Modal: ```python check import modal app = modal.App.lookup("my-app", create_if_missing=True) sb = modal.Sandbox.create(app=app) process = sb.exec("python3", "-c", "print(6 * 7)") print(process.stdout.read()) sb.terminate() ``` Runtime: ```python from withruntime import Sandbox with Sandbox.create(funding="trial") as box: print(box.exec("python3 -c 'print(6 * 7)'", check=True).stdout) ``` To have a coding agent make the switch, test it and compare costs on your own workload, give it the prompt in [migration](./migrate). Keep Modal available until the same job passes on both. ## Sources - [Modal pricing](https://modal.com/pricing) - [Modal resources and billing](https://modal.com/docs/guide/resources) - [Modal Sandboxes](https://modal.com/docs/guide/sandbox) - [Modal security](https://modal.com/docs/guide/security) - Runtime [pricing](./pricing), [security](./security) and [products](./products) --- # Understand your sandbox bill Standard compute rates: **$0.025 per active vCPU-hour** and **$0.0075 per reserved GiB-hour of memory**. Memory is billed while compute is running. A GiB is 1,073,741,824 bytes; it is not a decimal GB. For 2 vCPUs and 4 GiB, both CPUs continuously busy cost $0.08 per hour. Memory alone costs $0.03 per hour. With the default CPU floor of 50 millicores (a twentieth of a vCPU), an idle sandbox costs $0.03125 per hour, or $31.25 for 1,000 total running hours. Fully busy, those 1,000 hours cost $80. There is no monthly plan fee. A higher `cpuFloorMillis`, or `cpu: "reserved"`, raises the idle charge; “idle” must not imply every configuration has a zero CPU charge. Read the resource's immutable usage-pricing quote. Keep a bounded lifetime and confirm shutdown when the job ends; `onLeaseEnd` pauses or stops it for you. Every new account gets the [20-hour free trial](./trial). At these rates, 20 fully busy hours of a 2 vCPU, 4 GiB sandbox would cost $1.60. After the trial, add prepaid credit by card from your account page, any amount from $10 to $10,000. There is no subscription. Refer a company and you both get credit equal to its first top-up, up to $500 each ([referrals](./referrals)). Use these standard rates when comparing workloads; a free trial is not evidence of a permanently zero-cost service. ## Paused storage Paid paused storage initially costs **$0.08 per decimal GB per 30-day month**. The immutable resource quote controls the actual rate. It counts disk and memory-snapshot blocks uniquely owned by the sandbox, excluding shared base-image blocks and snapshot safety overhead. Where the filesystem cannot report sharing, allocated blocks are the conservative fallback and may overestimate exclusive usage. This is not the provisioned disk allowance or the sum of live file sizes. Compute billing continues through confirmed VM shutdown, including snapshot creation, then storage billing begins. Storage billing ends on confirmed resume or deletion. Compute has no one-minute minimum. Paid retention defaults to 30 days from each successful pause and can be set to 1–365 days. Each pause replaces the previous saved state. Trial sandboxes keep their free seven-day retention and never fall back to paid storage. Read the resource's `pausedExpiresAt` and paused-storage receipt for its actual terms. ## Per 1,000 runs Example assumptions: 2 vCPUs, 4 GiB, 60 seconds of running time per run, 20 total CPU-seconds per run, 1,000 completed runs. A CPU-second is one core busy for one second; two cores busy for ten seconds use 20 CPU-seconds. Runtime's estimated CPU and memory charge is: ``` CPU: 1,000 × 20 / 3,600 × $0.025 = $0.138889 Memory: 1,000 × 60 / 3,600 × 4 × $0.0075 = $0.500000 Total: $0.638889, approximately $0.64 per 1,000 runs ``` This is a calculation, not a benchmark or a final invoice. It assumes the CPU floor does not exceed measured CPU, and excludes taxes, paid disk retention, network charges if applicable, plan fees, free credits and retries. Include all of those in a decision for a real workload. Count startup and dependency setup in running time if they are inside the billable interval. ## Published rate comparison Rates checked 21 September 2026. Equal CPU and memory counts do not prove equal performance or service guarantees. | Provider | CPU and memory estimate for the example | | ----------------------------------- | --------------------------------------: | | Runtime, approved launch rates | $0.64 | | E2B, published per-second rates | $2.76 | | Daytona, published CPU/memory rates | $2.76 | Sources: [E2B pricing](https://e2b.dev/pricing), [Daytona pricing](https://www.daytona.io/pricing), [Vercel's published pricing comparison](https://vercel.com/kb/guide/vercel-sandbox-vs-codesandbox). E2B and Daytona use $0.0504/vCPU-hour and $0.0162/GiB-hour for this calculation. Vercel's cited comparison uses $0.128/active-vCPU-hour and $0.0212/memory-GB-hour; its memory unit and regional rates must be checked against the user's actual configuration. Vercel is therefore omitted from the numerical table until units and region are matched. Plan costs, storage, network, free credits and negotiated prices are excluded in every row. Free credits can make another provider cheaper initially. ## Ask your agent for a useful answer “Compare my last 1,000 completed runs with Runtime. Use the same measured job behavior, include my current discounts and required features, show all inputs, and separate estimated savings from measured savings.” Runtime is a strong candidate when its supported environment fits your workload and lower compute rates survive a real completed-job comparison. Public performance superiority has not been established. --- # Your Runtime Cloud account One account owns every Runtime Cloud resource, its agent connections and its prepaid credit. One key, one API, one SDK and one CLI reach every product, with the same resource shape, errors, idempotency, pages and labels. The dashboard shows what you can use today; an SDK method is not a promise that another product is available. ## Products | Product | What it is | Status | | ---------------- | --------------------------------------------------------------------- | ---------------- | | Sandboxes | Linux microVMs: commands, processes, terminals, files, pause and wake | Available | | Code interpreter | A notebook-style Python or JavaScript session inside a sandbox | Available | | Network rules | Per-sandbox internet, allow and deny lists, and `host:port` access | Available | | Images | Custom sandbox images from a recipe, a public image or a Dockerfile | Available | | Volumes | Persistent disks attached to sandboxes | Available | | Snapshots | Saved sandboxes, disk and memory, to start and fork from | Paused for a fix | | Previews | An HTTPS address for a port in a sandbox | Available | | Desktop | A Linux desktop in a sandbox, driven by clicks and keys | Available | Snapshots and forks are paused while we fix an issue: `fork`, `snapshot` and a create naming `snapshot` answer `fork_unavailable`. To install dependencies once and start many sandboxes from them, build a [custom image](./javascript#custom-images) and create each sandbox from it. Start with [getting started](./start), then check [the sandbox environment](./sandbox-environment) and [security](./security) against your workload. New products join the same namespaces as they launch: `runtime.` in the SDKs, `runtime ` in the CLI, `/v1/` in the API and `runtime__*` in MCP. ## Overview, usage and keys The overview brings together running resources, monthly spend, available credit and daily spend. API keys have a separate management section. New accounts begin with a focused connection flow until their first sandbox has started. Spend is settled service usage, not a count of payments or reserved credit. Charges use integer microdollars: one million equals one US dollar. Daily and monthly summaries use UTC. Check the resource's immutable quote and the [pricing guide](./pricing) to understand what it is metering. `npx withruntime usage` and `GET /v1/usage` give the same figures. An agent connection has its own identity and revocable credential. Current owner-issued keys cover all Cloud products, including products enabled later, and last until revoked. Browser sign-out does not revoke them. See [security and access](./security) before connecting an agent. ## Labels and names Sandboxes, images, volumes and snapshots take a `name` and up to 32 `labels`, and their lists filter by them: `npx withruntime sandbox ls --label team=search`, `GET /v1/sandboxes?label=team:search`. Use them for tenants, jobs and cost attribution. --- # Python SDK `withruntime` is one client for every Runtime Cloud product, sync and async, method for method. It uses only the standard library, imports in about 15 ms and keeps its connections open between calls. Python 3.10 or later. ```bash no-run pip install withruntime ``` The client finds its key by itself: `RUNTIME_API_KEY` when it is set, and otherwise the connection this machine saved when it was connected (any `npx withruntime` command connects it, with one browser approval). On a server, put a key from https://withruntime.com/account/keys in `RUNTIME_API_KEY` from your secret manager. Never put it in source code, a URL or a command-line argument. With no key anywhere, the first call fails with `missing_api_key` and says how to get one. ## Hello, sandbox ```python from withruntime import Sandbox with Sandbox.create() as sbx: result = sbx.exec("python3 -c 'print(6 * 7)'") print(result.exit_code, result.stdout) ``` `Sandbox.create()` takes no required arguments and returns once the sandbox is running. Leaving the `with` block stops it, even after an exception. With no arguments you get the free trial while it lasts, the default region, and 2 vCPU, 4 GiB of memory and a 4 GiB disk for up to 30 minutes. Every field is optional and takes snake_case names: ```python from withruntime import Runtime runtime = Runtime() # RUNTIME_API_KEY, or this machine's connection sbx = runtime.sandboxes.create( name="tests-42", labels={"team": "search", "job": "42"}, vcpu=2, memory_mib=4096, disk_mib=8192, timeout_seconds=900, on_lease_end="stop", network={"internet": True, "allow": ["pypi.org", "*.pythonhosted.org"]}, ) print(sbx.id, sbx.info["funding"], sbx.info["expiresAt"]) sbx.stop() ``` The async client is the same with `await`: ```python import asyncio from withruntime import AsyncRuntime async def main(): async with AsyncRuntime() as runtime: async with await runtime.sandboxes.create() as sbx: results = await asyncio.gather(*(sbx.exec(f"echo {i}") for i in range(10))) print([r.stdout.strip() for r in results]) asyncio.run(main()) ``` ## Run commands A string runs under `bash -c`. A list runs the program directly, with no shell, which is what you want for untrusted arguments. ```python import os from withruntime import Sandbox with Sandbox.create() as sbx: sbx.exec("mkdir -p app && echo 'print(1 + 1)' > app/main.py") run = sbx.exec( ["python3", "main.py"], cwd="/workspace/app", env={"API_TOKEN": os.environ.get("API_TOKEN", "")}, timeout_ms=120_000, ) if run.exit_code != 0: print(run.stderr) ``` - `env` is how secrets reach a command. It is never echoed back, and journals record a hash, not the value. Never put a secret in the command line itself. - `stdin` gives the command input, then closes it. - The default timeout is 60 seconds; the maximum is 24 hours. A timeout is a result (`timed_out=True`, with the output so far), not an exception. - `check=True` raises `CommandError` on a non-zero exit. Stream output as it happens with callbacks, or iterate the events: ```python import sys from withruntime import Sandbox with Sandbox.create() as sbx: sbx.exec("for i in 1 2 3; do echo line $i; sleep 1; done", on_stdout=sys.stdout.write) for event in sbx.exec_stream("npm --version"): if event["type"] == "stdout": print(event["data"], end="") elif event["type"] == "exit": print("exit", event["exitCode"]) ``` ## Background processes ```python from withruntime import Sandbox with Sandbox.create() as sbx: server = sbx.spawn("python3 -m http.server 8000", cwd="/workspace") print(server.id, server.info["state"]) repl = sbx.spawn(["python3", "-i", "-q"], stdin="pipe") repl.write("print(21 * 2)\n") repl.write("exit()\n", eof=True) print(repl.wait().stdout) for process in sbx.processes(): print(process["id"], process["state"], process["command"]) server.kill("SIGTERM") ``` `process.output(cursor=0)` yields every event from the start until the process exits. A process outlives your connection; get it back with `sbx.process(id)`. ## An interactive terminal ```python from withruntime import Sandbox with Sandbox.create() as sbx: term = sbx.terminal(cols=120, rows=40) term.write("echo hello from the terminal\n") term.write("exit\n") while (chunk := term.recv()) is not None: print(chunk.decode(errors="replace"), end="") ``` ## Files ```python from withruntime import Sandbox with Sandbox.create() as sbx: sbx.files.write("/workspace/data/input.csv", "a,b\n1,2\n") text = sbx.files.read_text("/workspace/data/input.csv") data = sbx.files.read("/workspace/data/input.csv") # bytes print(sbx.files.exists("/workspace/data/input.csv"), sbx.files.stat("/workspace/data/input.csv")) for entry in sbx.files.list("/workspace", depth=2): print(entry["type"], entry["size"], entry["path"]) print(sbx.files.glob("**/*.csv")) sbx.files.mkdir("/workspace/out") sbx.files.rename("/workspace/data/input.csv", "/workspace/out/input.csv") sbx.files.remove("/workspace/data", recursive=True) ``` `write` makes parent directories and replaces the file atomically; large files go in parallel chunks checked by SHA-256. Whole directories travel as one archive: ```python import pathlib import tempfile from withruntime import Sandbox project = pathlib.Path(tempfile.mkdtemp()) (project / "main.py").write_text("print('hi')\n") with Sandbox.create() as sbx: sbx.files.upload(str(project), "/workspace/project") sbx.exec("cd project && python3 main.py > result.txt") sbx.files.download("/workspace/project", str(project.parent / "project-out")) ``` ## Pause, wake, extend ```python check from withruntime import Sandbox sbx = Sandbox.create(timeout_seconds=600) sbx.exec("echo state > /workspace/state.txt") sbx.pause() # memory and files are kept; compute billing stops again = Sandbox.connect(sbx.id) again.wake(timeout_seconds=1200) again.extend(600) again.stop() ``` ## Find sandboxes again ```python from withruntime import Runtime runtime = Runtime() for sbx in runtime.sandboxes.list(labels={"team": "search"}, state=["running"]): print(sbx.id, sbx.info["name"], sbx.state) ``` Every list returns a page: `page.data`, `page.has_more`, `page.next_page()`, `page.to_list()`, and a `for` loop walks every item on every page. ## Errors and retries ```python from withruntime import NotFoundError, RuntimeError, Sandbox try: Sandbox.connect("00000000-0000-4000-8000-000000000000") except NotFoundError: print("no such sandbox") except RuntimeError as error: print(error.code, error.hint, error.request_id) ``` The classes match the JavaScript SDK: `AuthenticationError`, `PermissionDeniedError`, `NotFoundError`, `ConflictError`, `InvalidRequestError`, `RateLimitError`, `ServiceUnavailableError`, `ConnectionError` and `CommandError`, all subclasses of `withruntime.RuntimeError`. Every write carries an idempotency key, made for you; transport failures, 429 and 503 are retried with the same key, so a retry never makes two sandboxes or runs a command twice. ## Read-only keys and daily limits An owner can make a read-only key and set a daily spending limit on a key at [API keys](https://withruntime.com/account/keys). A key reads both and can change neither. `runtime.limits` needs withruntime 0.3.1 or later: ```python check from withruntime import Runtime with Runtime() as runtime: limits = runtime.limits.get() print(limits["access"]) # "full", "read" or "selected" left = limits["daily"]["remainingMicros"] if left is not None and int(left) < 1_000_000: print("less than $1 left in this 24-hour window") ``` Past the limit, a create, wake, extension or renewal fails with a `RuntimeError` whose `code` is `spending_limit_reached` (HTTP 402), and it is not retried. A read-only key asking to change anything gets `PermissionDeniedError`. See [security](./security). ## Images, volumes and snapshots Forks and snapshots are paused while we fix an issue: for now `fork`, `snapshot` and a create naming `snapshot` answer 503 `fork_unavailable`. Your sandboxes are unaffected. ```python check from withruntime import Runtime runtime = Runtime() image = runtime.images.build(name="data", recipe={"pip": ["pandas"], "apt": ["jq"]}, on_log=lambda line: print(line["text"])) volume = runtime.volumes.create(size_mib=10_240, name="cache") with runtime.sandboxes.create(image=image["id"], volumes=[{"volume_id": volume["id"], "path": "/data"}]) as sbx: print(sbx.exec("python3 -c 'import pandas; print(pandas.__version__)'").stdout) forks = sbx.fork(count=2) # copies as it is now, running for fork in forks: fork.stop() snapshot = sbx.snapshot(name="with-pandas", retention_days=7) with runtime.sandboxes.create(snapshot=snapshot["id"]): pass runtime.snapshots.delete(snapshot["id"]) ``` `images.build` waits until the image is ready; `images.create` queues it and returns. A volume lives on one server and is not backed up off it. See [JavaScript](./javascript#custom-images) for what each does; the Python methods are the same in snake_case. ## Code interpreter and network rules ```python check from withruntime import Runtime runtime = Runtime() with runtime.sandboxes.create() as sbx: sbx.interpreter.run("import math\nx = math.pi") cell = sbx.interpreter.run("round(x * 2, 3)") print(cell["results"][0]["data"]["text/plain"]) sbx.network.set(internet=True, allow=["pypi.org", "*.pythonhosted.org"]) print(sbx.network.get()) ``` Feedback and support are on the client too: `runtime.feedback.submit(...)` and `runtime.support.message(...)`; see [feedback and support](./feedback-and-support). ## Previews and the desktop ```python check from withruntime import Sandbox with Sandbox.create() as sbx: sbx.spawn("python3 -m http.server 3000") preview = sbx.previews.create(3000, visibility="public") print(preview["url"]) sbx.desktop.start(width=1280, height=800) sbx.desktop.open("https://example.com") sbx.desktop.click(640, 400) with open("screen.png", "wb") as file: file.write(sbx.desktop.screenshot()) ``` A preview's address is under `runtimehost.com`, the domain for everything sandboxes serve, kept apart from Runtime's own site. See [JavaScript](./javascript#share-a-port) for what each does. ## Configuration ```python from withruntime import Runtime with Runtime(max_retries=4, timeout=120) as runtime: print(runtime.me()["orgId"]) ``` `RUNTIME_API_URL` points the client at another API origin. Before 0.3.0 the package was `withruntime-cloud`, imported as `runtime_cloud`. Both still work and give you the same classes, so older code keeps running. --- # Referrals Share your link. When a company signs up with it and adds $10 or more of credit for the first time, **you both get credit equal to what they added: at least $25 and at most $500 each**. Theirs is added on top of what they bought. A $10 top-up gives you both $25; $137.42 gives you both $137.42; $2,000 gives you both $500. - Your link is `https://withruntime.com/r/`, and `https://withruntime.com/?ref=` works too. Owners find it on the account's **Overview** and **Usage & billing** pages, or ask for it with the API, an SDK, the CLI or MCP (below). The code belongs to your organization and never changes. - Showing someone what you built? A preview link is a good way; the answer to creating one says so. Runtime adds nothing to the pages a preview serves: no badge, no banner. - The link has to be followed before the company is created. It is remembered for 30 days in a cookie that holds only the code, and it carries through Google and email sign-in. - The reward is paid once per referred company, on its first top-up of $10 or more, and matches that top-up. Later top-ups pay nothing more. Free trial time never counts. - You can earn up to **$10,000 of referral credit per calendar year** (UTC). Past that, the company you referred still gets its credit; yours is recorded as over the cap. - Referring yourself does not count: not your own organization, not another address of yours, not an address on the same company email domain (a shared provider such as gmail.com is not one company), and not a first top-up paid with a card your organization has paid with. A code that does not count never stops anyone signing up. - If the qualifying top-up is partly refunded, the reward is matched again to what stands, and both sides give back the difference. If less than $10 stands, or it is disputed, both rewards are taken back. Either way, only what is still unspent is taken. - Referral credit is spent like any other credit. It can't be exchanged for cash or refunded. ## Read your link and what it earned ```ts check import { Runtime } from "withruntime"; const runtime = new Runtime(); const r = await runtime.referrals.get(); console.log(r.link, r.signedUp, r.paid, r.earnedMicros); ``` ```python check from withruntime import Runtime r = Runtime().referrals.get() print(r["link"], r["capRemainingMicros"]) ``` ```sh no-run npx withruntime referrals ``` Over HTTP it is `GET /v1/referrals`, and over MCP the `runtime_referrals_get` tool. Both answer the same fields: | Field | Meaning | | --------------------------------- | ------------------------------------------------------------------- | | `code`, `link` | Your code and the link to share | | `signedUp` | Companies that signed up with your link | | `pending` | Of those, the ones that have not made a first top-up yet | | `paid`, `capped` | Rewards paid, and how many of those the yearly cap cut short | | `rewardMicros`, `maxRewardMicros` | The least and the most each side gets per referral | | `reversed` | Rewards taken back because the qualifying top-up was refunded | | `earnedMicros` | Referral credit you kept, all time | | `earnedThisYearMicros` | What counts against this year's cap | | `capRemainingMicros` | What you can still earn this year | | `referredBy` | Whether your own organization joined through a link, and its credit | Money is integer microdollars in strings: `"25000000"` is $25. --- # The sandbox environment Every sandbox is a Firecracker microVM with its own Linux kernel and disk, started from the same image. This page says what is in it, who you are inside it, and what it can reach. ## What is installed The image is Ubuntu 24.04.5 LTS (noble) for amd64, on a 6.1 kernel. | Kind | What | | ---------------- | -------------------------------------------------------------------------------------------- | | Python | Python 3.12 (`python3` and `python`), `pip` and `pip3`, `venv`, and uv 0.12.17 (`uv`, `uvx`) | | JavaScript | Node.js 24.21.0 (`node`, `npm`, `npx`); Bun 1.4.0 (`bun`) | | Build | `gcc`, `g++`, `make` (build-essential) | | Source and fetch | `git`, `curl`, `wget`, `ssh`, `zip`, `unzip`, `xz` | | Search and data | `rg` (ripgrep), `fd`, `jq`, `sqlite3` | | System | `sudo`, `chronyd` | Anything else installs the usual way: `sudo apt-get install -y ...`, `pip install ...`, `npm install ...`, `uv pip install ...`. To start every sandbox with your dependencies already there, build a [custom image](./javascript#custom-images). Java, Go and Rust are not installed. Install Ubuntu's packages with `sudo apt-get install`, or a newer release with the language's own installer, such as `rustup` for Rust. To have one in every sandbox, build a custom image whose recipe installs it, or start the image from a public image that already has it. Docker is not installed either, and running containers inside a sandbox has not been tested. To start sandboxes from a Dockerfile, build it as a custom image instead; single-stage Dockerfiles are supported. Browsers are not in the image, because every byte of it counts against each sandbox's disk. For Playwright's Chromium: ```bash no-run sudo npx playwright install-deps chromium npx playwright install chromium ``` ## Who you are - Commands run as the user `runtime` (uid 1000), with `bash`. - `HOME` is `/workspace`, which is also the default working directory. Files there belong to you. - `sudo` works without a password: you are root inside your own sandbox. Root cannot change what the sandbox may reach, how much CPU and memory it has, or what it costs; those are enforced on the host, outside the sandbox. - `PATH` starts with `/workspace/.local/bin`, then `/usr/local/bin`, `/usr/bin` and `/bin`. `LANG` is `C.UTF-8`. `pip install` works without a virtual environment. As `runtime` it installs to `/workspace/.local` (whose `bin` is first on `PATH`); under `sudo` it installs to `/usr/local`. The image's `/etc/pip.conf` sets `break-system-packages`, so the Ubuntu rule against installing into the system Python (PEP 668) does not stop you. Use `python3 -m venv` or `uv` when you want isolation. ## Disk, CPU and memory The disk you ask for with `diskMiB` includes the system image, which uses about 1 GiB of it, so the default 4 GiB sandbox has about 3 GiB free; ask for more when you install a lot. The smallest disk is 3072 MiB, the size of the image file. Each sandbox's disk reads and writes are limited in speed, to about 40 MB/s and 2,000 operations a second each way whatever the sandbox's size, so one sandbox cannot swamp the drive it shares with others. CPU is shared by default, with a guaranteed floor (`cpuFloorMillis`, 50 thousandths of a vCPU unless you ask for more) and bursts up to `vcpu` cores. Memory is what you ask for. See [pricing](./pricing) for what each costs. ## The network A sandbox has no network card. Everything outbound goes through a proxy on the host, and the image sets the environment every tool needs to find it: ```text HTTP_PROXY=http://127.0.0.1:10800 http_proxy=http://127.0.0.1:10800 HTTPS_PROXY=http://127.0.0.1:10800 https_proxy=http://127.0.0.1:10800 NO_PROXY=localhost,127.0.0.1,::1 NODE_USE_ENV_PROXY=1 ``` `sudo` keeps these. A program that ignores proxy settings and opens raw sockets does not reach the internet. By default a sandbox reaches the public web on ports 443 and 80. Private and internal addresses are refused. Mail ports (25, 465 and 587) are closed unless support enables them for your account, and a few ports are never reachable: telnet, Windows RPC, NetBIOS and SMB, and IRC. Each sandbox has its own rules, set at create (`network`) or at any time after, applied at once, to open connections too: ```ts check import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create({ network: { internet: true, allow: ["pypi.org", "*.pythonhosted.org"] }, }); await sbx.network.set({ internet: true, deny: ["example.com"] }); await sbx.network.set({ internet: false }); console.log(await sbx.network.get()); ``` - `internet: false` refuses every outbound connection. - `allow` narrows the web to a list: domains, `*.domain` for every name under one, addresses or CIDR ranges. - `deny` always wins. - `connect` opens `host:port` pairs beyond the web ports, such as your own database or `github.com:22`, for paid sandboxes of accounts that have made a purchase. Inside the sandbox, `python3 /usr/local/lib/runtime/guest-egress.py forward 5432 db.example.com:5432` gives a program a local port to use. The rules apply to root inside the sandbox too. Nothing can connect in to a sandbox except through a preview: an HTTPS address for one port that you share on purpose, private with a token by default. See [JavaScript](./javascript#share-a-port) or `npx withruntime sandbox preview`. A sandbox with no preview accepts no incoming connections. ## Time and lifetime The clock is kept on the host's clock, and set again after every wake. A sandbox runs until its lease ends (`timeoutSeconds`, at most an hour ahead, which `extend` moves), then pauses or stops as `onLeaseEnd` says. A host-side lease bounds execution even if management is unavailable. A stopped sandbox is not a backup; copy out what you need to keep. --- # Isolation and account boundaries Runtime uses Firecracker microVMs on Runtime-operated dedicated servers. Each sandbox has its own guest environment. Host credentials, control sockets and provider credentials are not customer tools. Private hardware tests have exercised guest separation, selected private-address refusals, restart recovery and host-side lease expiry. This is limited evidence, not a claim of complete isolation assurance or a completed outside security audit. ## Keys and secrets Keep keys in server-side secret storage. Use separate credentials for separate agents or applications. Current owner-issued keys cover every Cloud product, including products enabled later, and last until revoked. Never put a key in a public prompt, URL, browser bundle, repository, command-line argument or diagnostic log. A framework adapter does not expand what the key is allowed to do. Give a command its secrets through `env`, never in the command line. Runtime never echoes `env` values back, and its journals and request records keep only a hash of them. A command line is recorded as you sent it. ## Read-only keys and daily limits Both are optional. Without them a key reaches every product and is bounded by your prepaid balance, and nothing else. - **Read-only key.** Choose **Read only** when you create a key at [API keys](https://withruntime.com/account/keys). It sees the whole account: every sandbox and its state, spec and cost, the images, volumes, snapshots, previews and network rules beside them, account notices, the balance, and its own access and limit. That covers products added later too. It cannot create, start, stop, pause, wake, run a command, write a file, change anything or spend anything. It does not see inside sandboxes (files, command output), job runs or secrets. Use it for monitoring, dashboards and CI checks. - **Daily spending limit.** Set one on any key that can spend, when you create it or later from its row. It is the most that key's agent may commit in any 24 hours: settled charges plus money still on hold. It counts everything the agent's sandboxes cost, including renewals and parked storage. When a create, wake, extension or renewal would pass it, that request fails with `spending_limit_reached` (HTTP 402) and nothing is charged. A running sandbox keeps its current lease; one that needs a renewal past the limit stops or pauses when its lease ends. Room comes back as older spending leaves the 24-hour window. Only an account owner can create a read-only key or set, change or remove a limit, and only on the website. A key can read its own access and limit with `GET /v1/limits`, `runtime limits` or the `runtime_limits_get` tool. No key can raise, remove or set a limit, and no key can create another key. Set a limit above what the agent's paused sandboxes cost in a day. If storage for a paused sandbox cannot be paid for, it is treated like storage on an empty balance: you are notified, and after seven days unpaid it is deleted. ## Network access The sandbox network contract is controlled public-web egress. Outbound traffic leaves through an HTTP CONNECT proxy, and the guest must keep the proxy environment it is given; a tool that ignores it does not reach the internet. By default, outbound traffic is the public web. Only the public web ports 443 and 80 are allowed. A paid account can open named `host:port` pairs with `connect`; a few ports are never reachable. Private and internal addresses are refused. Each sandbox's rules can narrow this further, and they bind root inside the sandbox too. See [the sandbox environment](./sandbox-environment). Nothing connects in to a sandbox except through a preview you create: an HTTPS address for one port, private with an expiring token unless you make it public. Preview addresses are under `runtimehost.com`, never under `withruntime.com`, so content a sandbox serves never shares an origin with your account. A sandbox with no preview accepts no incoming connections. Per-sandbox limits on concurrent connections, bandwidth and bytes per day also apply; their values are host configuration and are not published here. A request to create a sandbox does not promise arbitrary outbound TCP or an unrestricted network interface. Check a migration's package managers and destination requirements explicitly. ## Root inside the sandbox The sandbox user has passwordless `sudo`. Root inside the guest controls the guest and nothing else: CPU, memory, disk, network rules, leases and billing are enforced on the host. ## Lifetimes and storage A host-side lease bounds execution even if management is unavailable. A stopped sandbox is not a separately promised backup. Export important results and do not assume a single-host sandbox has replicated customer storage. Persistent product guarantees require their own release and recovery tests. ## Browser-approved agent connections Run the [CLI login](./cli) to connect without copying a key. The browser uses your existing session when possible. Check the request code and agent name, then approve **Connect agent**. Only an account owner can approve a new connection. The request expires after 15 minutes. Do not approve unsolicited connection links. Approval creates a distinct agent identity and credential. It does not give the agent your browser session. The credential is encrypted for the initiating CLI when delivered and saved outside the project. On Unix, the credential directory is private to its user and the file has owner-only permissions. This is file storage, not an OS keychain or encrypted-at-rest secret vault; software running as your user may be able to read it. Keep the machine and its backups secure. Each connection can operate across the account and spend its prepaid balance. Separate agent identities make attribution and revocation possible; they do not make an untrusted agent safe to connect. No mandatory per-agent spending ceiling is imposed by the default connection flow. To bound one, set a daily limit on its row at [API keys](https://withruntime.com/account/keys) after it connects. The browser reports success after the CLI receives and verifies the credential. Signing out of the browser leaves agent connections active. Revoke an individual credential from **API keys**, or run `npx withruntime logout` on its machine (installed: `runtime logout`). Revocation does not itself stop running resources: inspect them and stop any work you no longer want. Billing follows confirmed resource state, not sign-in state. ## Before production use Public signup is open to anyone at https://withruntime.com/sign-in. Validate your actual package registries, workload, cleanup and recovery before adoption. Runtime does not currently claim arbitrary network access, replicated sandbox storage, a completed external security audit or universal protection against malicious code. See the [evaluation guide](./choosing-agent-sandbox) for concrete checks. --- # Get started with Runtime Cloud Runtime Cloud runs your code and your agents' code on machines we operate. Sandboxes come first: Linux microVMs with their own kernel, disk and toolchain, usually ready in under a second; startup varies with the workload and capacity. One API, one CLI, one MCP server and SDKs for JavaScript and Python cover every Runtime product. New accounts get 20 free hours with no card: see [the free trial](./trial). ## Run your first sandbox One line, with Node 22 or later: ```bash npx withruntime run --trial -- python3 -c 'print(6 * 7)' ``` The first time, it connects this machine: your browser opens a Runtime page (or the command prints a link and a code), you sign in with Google or an email link, check the code and choose **Connect agent**. Nothing to copy. Then a fresh sandbox runs the command, prints 42 and requests a stop. Check the exit code: zero means the command succeeded. Confirm cleanup with `npx withruntime sandbox ls --json`; if the test sandbox remains, stop it and verify its state. The explicit `--trial` refuses paid funding even if this account has credit. Later commands reuse the connection. An agent running it shows you the link and code. If you approve after the command has given up waiting (it waits 50 seconds when no one is at a terminal), the agent runs it again and it finishes, with no new link. For several commands in one sandbox, this shell block stops it on exit: ```bash ( id=$(npx withruntime sandbox create --trial) || exit trap 'npx withruntime sandbox stop "${id}"' EXIT npx withruntime sandbox exec "${id}" -- python3 -c 'print(6 * 7)' ) ``` Installed with `npm install --global withruntime`, the command is `runtime`: `runtime run --trial -- ls`, `runtime sandbox create --trial`. See [the CLI](./cli). ## Give an agent Runtime's tools Add Runtime's MCP server to your agent with one command: ```bash no-run claude mcp add --scope user runtime -- npx -y withruntime mcp # Claude Code codex mcp add runtime -- npx -y withruntime mcp # Codex ``` Cursor: add this to `~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project): ```json { "mcpServers": { "runtime": { "command": "npx", "args": ["-y", "withruntime", "mcp"] } } } ``` If this machine is not connected yet, the agent sees one tool, `runtime_connect`, which gives it a link for you to approve; then every Runtime tool appears. See [MCP](./mcp) for other clients and the remote endpoint. ## From code ```ts import { Sandbox } from "withruntime"; const sbx = await Sandbox.create({ funding: "trial" }); try { const result = await sbx.exec("python3 -c 'print(6 * 7)'", { check: true }); console.log(result.stdout); } finally { await sbx.stop(); } ``` ```python from withruntime import Sandbox with Sandbox.create(funding="trial") as sbx: print(sbx.exec("python3 -c 'print(6 * 7)'", check=True).stdout) ``` `npm install withruntime` or `pip install withruntime`. The SDKs use `RUNTIME_API_KEY` when it is set, and otherwise the key this machine saved when it connected, so after the one line above they need no setup. On a server, create a key at https://withruntime.com/account/keys and give it as `RUNTIME_API_KEY` from your secret manager. `create()` needs no arguments and waits for readiness and raises `start_failed` if it stops before becoming ready. The examples insist on trial funding, check the command result, and stop even after an error. Export anything you need before stopping. ## Connect once in your browser The browser approval gives this machine its own key, encrypted for it alone and saved outside your project: the CLI receives and saves its own credential automatically, and you do not copy an API key. Each connection can use everything the account can, including its prepaid credit, so approve only connections you started. Browser sign-out does not revoke one; `npx withruntime logout` or the API keys page does. See [security](./security). On a machine with no browser, `npx withruntime login --no-browser` prints the link to open on another device. To use a key you already have instead, `npx withruntime login --with-key` reads it from standard input, never from the command line. Trial requests never fall back to paid credit. Omitting `funding` can use prepaid credit after the trial is exhausted. Keep `funding: "trial"` for free use; choose `funding: "paid"` only when you intend to use credit. ## Give your agent one instruction > Set up Runtime Cloud for this project: read https://withruntime.com/llms.txt, > run `npx withruntime run --trial -- echo hello`, and when it prints a link, show > it and the matching code to me and wait for my browser approval. Do not ask > me for an API key or switch to paid credit. Check the output, exit code and > cleanup. Report any blocker without exposing credentials or private files. ## If the first run fails Keep the error code, request ID and sandbox ID. A lost response is not proof that nothing ran: inspect the original resource before trying another create. For `connection_pending`, approve the printed request and rerun the same command. For `trial_busy` or `trial_exhausted`, inspect your trial usage; do not add `--paid` without deciding to use credit. For `start_failed`, read the sandbox's state and `stopReason`; never treat a stopped sandbox as ready. See [troubleshooting](./troubleshooting) for safe retries and cleanup. ## What you can do next | You want to | Read | | -------------------------------------------------- | ------------------------------------------------ | | Run commands, stream output, start servers | [JavaScript](./javascript) or [Python](./python) | | Drive sandboxes from a terminal or a script | [CLI](./cli) | | Give an agent Runtime tools | [MCP](./mcp) | | Call the API from any language | [API reference](./api) | | Know what is installed and what the network allows | [The sandbox environment](./sandbox-environment) | | Plug into Vercel AI SDK, OpenAI, Claude, LangChain | [Frameworks](./frameworks) | | Move from E2B, Daytona, Vercel or Modal | [Migration](./migrate) | | Tell us what is missing, or get help | [Feedback and support](./feedback-and-support) | Keep important results outside the sandbox: a sandbox is not a backup. --- # The free trial Every new account gets **20 hours of sandbox time** after a verified sign-in, with no card. - Up to **three sandboxes at once**, each up to **2 vCPU and 4 GiB of memory**, with up to 10 GiB of disk (4 GiB by default) and shared CPU. - The 20 hours are shared across all your trial sandboxes and sessions. Running time counts, idle or busy; time while a sandbox is paused or stopped does not. - Each session can last up to one hour; the server enforces `timeoutSeconds` of at most 3600. Extend a running sandbox or wake a paused one to keep going, within the time you have left. - Model calls, persistent storage and paid features are not included. Use a region listed for your account. Leave `region` out for the default. Tell your agent: > Read https://withruntime.com/docs/trial.md and https://withruntime.com/docs/start.md. > Try my workload with `funding: "trial"` (CLI: `--trial`), check its output > and exit code, and stop the sandbox even if it fails. Do not switch to paid > funding without asking me. You do not have to ask for the trial: `create()` with no `funding` uses it while you have trial time left, sized to fit. Name `funding: "trial"` to insist on it; a trial request never falls back to paid credit, even when the account has some. If you omit `funding` after the trial is exhausted, available prepaid credit can be used instead. ```ts import { Runtime } from "withruntime"; const runtime = new Runtime(); const sbx = await runtime.sandboxes.create({ funding: "trial", vcpu: 1, memoryMiB: 2048 }); try { console.log(sbx.info.funding, sbx.info.expiresAt); } finally { await sbx.stop(); } ``` Over HTTP, a trial create is `POST /v1/sandboxes`. This body names the defaults; only `funding` needs to be there: ```json { "funding": "trial", "vcpu": 2, "memoryMiB": 4096, "diskMiB": 4096, "cpu": "shared", "cpuFloorMillis": 50, "timeoutSeconds": 1800 } ``` Time is reserved when a sandbox starts and settled from its confirmed running time when it ends, so stopping early gives the unused part back. Read what is left with `npx withruntime usage` (or `GET /v1/usage`): `trial.totalMs`, `trial.usedMs`, `trial.reservedMs` and `trial.availableMs`, in milliseconds, apart from dollars. When three trial sandboxes are running or paused, a fourth is refused with `trial_busy`, naming the three. Stop one first. Requests sent at the same moment are admitted in no set order, so any one of them may be the one refused; retry it after stopping a sandbox or once one has ended. When the time is used up, new trial sandboxes are refused with `trial_exhausted`. Free time cannot be cashed out, refunded or replenished by signing in again. To continue with paid compute, an account owner can add prepaid credit at [Usage & billing](https://withruntime.com/account/billing), then explicitly choose paid funding. Read [pricing](./pricing) first. At the standard rates, 20 fully busy hours of a 2 vCPU, 4 GiB sandbox would cost $1.60 in compute. See [pricing](./pricing) and, for what the sandbox may reach, [security](./security). --- # Diagnose a sandbox integration Start with the failing step: connection, creation, readiness, execution, pause, wake or cleanup. Every error carries a `code`, a `hint` and a `requestId`: follow the hint first, and keep the request id, the sandbox id, the time and the SDK version for a report. Never include API keys, connection secrets or private file contents in a report. Confirm you are using the correct account and API origin. If it looks like Runtime's fault, report it with `npx withruntime feedback "..." --request-id `; if you are stuck, ask `npx withruntime support "..."`. See [feedback and support](./feedback-and-support). ## Browser login does not finish Open the exact link the CLI printed, verify the matching code and agent name, then approve **Connect agent** as the account owner. If the command was stopped while it waited, run it again: it shows the same link and code, and picks up the approval. Approval and delivery are separate: the browser waits for the CLI to receive and verify the credential before showing success. Requests expire after 15 minutes. Start a new `npx withruntime login` after expiry. On a remote terminal, use `--no-browser` and open its link on your own device. If the CLI cannot reach Runtime, check its outbound HTTPS access; do not paste credentials into the browser or into your agent's prompt. Run `npx withruntime whoami` to check a saved connection. If it is refused, run login again. `RUNTIME_API_KEY` overrides the saved connection: an invalid environment key can fail even when browser login previously worked. Remove an unwanted override without printing its value. ## Creation is refused | Code | What it means | What to do | | --------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------- | | `trial_busy` | Three trial sandboxes are already running or paused; `details` names them | Stop one, or pass `funding: "paid"` | | `trial_exhausted` | The 20 hours are used, or `timeoutSeconds` is more than what is left | Shorten `timeoutSeconds`, or use paid credit | | `invalid_trial` | A trial sandbox is at most 2 vCPU, 4 GiB of memory and 10 GiB of disk | Omit the size fields for the default | | `invalid_request` | `details.issues` names every wrong field | Fix the named fields; unknown fields are refused | | `invalid_region` | `details.available` lists your regions | Use a region listed for your account, or omit it | | `no_capacity`, `busy` | No host has room right now | Retry with the same idempotency key and a growing delay | A trial request never silently becomes a paid one. Paid credit does not make an exhausted trial available. See [the trial](./trial). ## Creation returns `starting` The SDKs and `npx withruntime sandbox create` wait for `running` for you. Over raw HTTP, send `Prefer: wait=60`, or read the sandbox with `?waitFor=running`. Wait for running before issuing commands. A command on a sandbox that is still starting waits up to 30 seconds, then answers `sandbox_not_ready`. If the sandbox stops before it is ready, its `stopReason` says why; report that failure with its sandbox id and request id. Do not start a new sandbox repeatedly without checking the first request's outcome. ## A sandbox stopped on its own Read its `stopReason`. `insufficient_funds` means the credit ran out; add credit on the account page. `lifetime_cap` means the sandbox reached its own `maxTotalCostMicros`. `spending_limit` means an owner-set spending limit stopped it, usually this agent's daily limit: see what is left with `GET /v1/limits` or `runtime limits`, and only an owner can raise it, on the account's keys page. Do not create replacements in a loop; the limit counts the last 24 hours. ## The request timed out A missing response is not proof that a command failed to run. Use the same idempotency key and identical input. The server answers the first result again instead of running it twice. The SDKs do this for you. If a command's outcome is still unknown, inspect its files and `processes` before repeating a side effect. ## The service returns 401, 403, 409, 429 or 5xx | Response | What to check | Safe next step | | ------------------ | ------------------------------------------------------------------- | -------------------------------------------------------------------------- | | 401 | Missing, expired or revoked key; wrong API origin | Check `npx withruntime whoami` or the secret-manager configuration | | 403 | Owner approval, key scope, account suspension or resource ownership | Confirm the account and permitted action; do not work around a refusal | | 409 | The resource's state: paused, stopped, not ready, or the trial busy | Resolve that state, then retry | | 422 | The idempotency key was used before with a different body | Same key, same body; or a new key for genuinely new work | | 429 | Request rate or concurrent work exceeded the server's bound | Respect `Retry-After`, add a growing delay, and reduce concurrent requests | | 5xx or no response | The write may already have reached the server | Retry identical input with the same idempotency key and inspect state | [API errors and retries](./api) describes the full contract. ## The command succeeded over HTTP but the task failed Inspect the command's exit code, standard error and truncation flags. HTTP success means the operation was handled, not that the program returned zero. Use `check: true` (`check=True` in Python) to raise on a non-zero exit. A timeout is a result with `timedOut: true`; raise `timeoutMs`, or run long jobs in the background and follow their output. ## A file operation fails File errors name the path and the reason: `file_not_found`, `is_a_directory`, `not_a_directory`, `permission_denied`. Paths are absolute; `/workspace` is home. Outside your own files, use `sudo` in a command. ## Dependencies cannot reach a server Check the sandbox's network rules (`npx withruntime sandbox network `) and the destination port. Keep the guest's proxy environment. A tool that opens raw sockets or ignores proxy configuration does not fit the public-web egress model; see [the sandbox environment](./sandbox-environment). Do not disable security protections to make a migration appear successful. ## The bill differs from an estimate Compare total CPU-seconds, provisioned memory, the quoted CPU floor and actual running duration. Include startup, dependency installation, retries and cleanup. Wait for settlement before equating reserved credit with a final charge. The dashboard groups daily and monthly settled service charges in UTC. A payment adds credit; it is not service usage. A reservation can lower available credit without being a final charge. Paused storage is a separate meter, so stopping compute does not prove all retained-storage charges have ended. ## A paused sandbox will not wake Inspect `pausedExpiresAt`, available credit and the error code. Wake needs room on the original host for CPU, memory, the full disk allowance and its next snapshot. A full host can refuse restoration while preserving the saved state. Do not delete it as a retry step. Missing memory images are an error, not an instruction to claim a fresh boot restored the previous process. Keep important outputs outside a sandbox before relying on long retention. See [storage and retention terms](./pricing) and [isolation limits](./security). ## Stop returned, but the sandbox is still stopping Stop is asynchronous. The SDKs wait for `stopped`; over HTTP, send `Prefer: wait=60` or read the sandbox with `?waitFor=stopped`. Compute ends after confirmed VM shutdown. Do not report cleanup complete just because the request was accepted. The host-side lease provides a bound, but is not proof that your cleanup already completed. ## MCP is connected but execution is still local MCP tools and a framework's built-in shell are different paths. Configure the framework's execution backend or replace its tool registration explicitly. Run a probe that prints the sandbox id and verifies a remote file round trip before claiming the migration is complete. --- # Runtime vs Vercel Sandbox Runtime and Vercel Sandbox both use Firecracker microVMs and bill active CPU; Runtime charges less, and a pause keeps memory. This page compares the two for teams looking for a Vercel Sandbox alternative. Facts about Vercel come from its public pricing and documentation, checked 22 September 2026, at the rates of its default `iad1` region; check the links at the end before relying on them. Equal vCPU counts do not prove equal performance, and the two services have not been benchmarked against each other. ## At a glance | | Runtime | Vercel Sandbox | | --------------- | ---------------------------------------------------- | ---------------------------------------------------- | | Isolation | Firecracker microVM, own kernel | Firecracker microVM, own kernel | | CPU billing | $0.025 per active vCPU-hour, with a small floor | $0.128 per active CPU-hour | | Memory billing | $0.0075 per reserved GiB-hour | $0.0212 per provisioned GB-hour | | Plan | None; prepaid credit from $10 | Hobby allowance free; usage beyond it needs Pro | | Free start | 20 sandbox hours, no card | 5 active CPU-hours and 420 GB-hours a month on Hobby | | Session length | Leases of up to an hour, extended as often as needed | 45 minutes on Hobby, 24 hours on Pro | | Stop and resume | Pause keeps files and memory | Stop keeps the filesystem; processes start again | | Regions | One | 19 | ## Cost for the same job Take 1,000 runs of a 2 vCPU sandbox with 4 GiB on Runtime and 4 GB on Vercel. Each run lasts 60 seconds and keeps the CPU busy for 20 CPU-seconds, which is typical of an agent that spends most of its time waiting for a model. ``` Runtime CPU 1,000 × 20 s / 3,600 × $0.025 = $0.14 Memory 1,000 × 60 s / 3,600 × 4 × $0.0075 = $0.50 Total $0.64 Vercel CPU 1,000 × 20 s / 3,600 × $0.128 = $0.71 Memory 1,000 × 60 s / 3,600 × 4 × $0.0212 = $1.41 Total $2.12 ``` Both bill CPU by use, so the gap holds as a workload gets busier: with both CPUs busy for the whole minute, the same job costs $1.33 on Runtime and $5.68 on Vercel. A GiB is about 7% larger than a GB, so Runtime's memory figure covers slightly more. Creations, network, storage, plan fees, taxes and free allowances are excluded from both. See [pricing](./pricing) for Runtime's terms. ## Where Vercel Sandbox is stronger - **Regions and scale.** Vercel runs sandboxes in 19 regions, with up to 10,000 at once on Pro. Runtime runs in one region. - **Size.** Enterprise sandboxes go up to 32 vCPUs and 64 GB. - **Persistence.** Vercel sandboxes save their filesystem on every stop by default, and Drives add persistent storage that sandboxes can share. - **Platform.** If your application already runs on Vercel, sandboxes share its account, billing and observability, and Vercel reports SOC 2 Type II. Runtime has not completed an outside security audit. - **Snapshots.** Runtime's are paused while an issue is fixed; build a [custom image](./javascript#custom-images) to start many sandboxes from one setup. ## Where Runtime is stronger - **Price.** Active CPU costs about a fifth as much, and memory about a third. - **Memory survives a pause.** A paused Runtime sandbox wakes with its processes still running. See [pricing](./pricing) for paused storage. - **Setup by the agent.** An agent runs `npx withruntime run --trial -- ...`, shows you a link, and starts work once you approve it in the browser. No API key is copied into a prompt or a config file, and the [MCP server](./mcp) reuses the same connection. - **No platform to join.** Runtime needs no hosting plan or project; prepaid credit is the whole account. ## Moving from Vercel Sandbox Vercel Sandbox: ```js import { Sandbox } from "@vercel/sandbox"; const sandbox = await Sandbox.create(); const command = await sandbox.runCommand("echo", ["hello"]); console.log(await command.stdout()); await sandbox.stop(); ``` Runtime: ```ts import { Sandbox } from "withruntime"; const box = await Sandbox.create({ funding: "trial" }); try { console.log((await box.exec("echo hello", { check: true })).stdout); } finally { await box.stop(); } ``` To have a coding agent make the switch, test it and compare costs on your own workload, give it the prompt in [migration](./migrate). Keep Vercel Sandbox available until the same job passes on both. ## Sources - [Vercel Sandbox pricing and quotas](https://vercel.com/docs/sandbox/pricing) - [Understanding Vercel Sandboxes](https://vercel.com/docs/sandbox/concepts) - Runtime [pricing](./pricing), [security](./security) and [products](./products)