Metalboxes

Preview

Start a Linux microVM for your code, run commands in it, and read and write its files.

Metalboxes is in preview. Talk to an engineer to turn it on for your organization. The API, the limits and the prices can change before general availability.

A sandbox is a Linux microVM for one program. Your code creates it with the API, runs commands in it, reads and writes its files, and deletes it. Each sandbox has its own kernel, disk and network, so code in one sandbox cannot reach another sandbox.

Use a sandbox for code that you do not trust, such as code that an AI agent writes, and for a test or a build that must start clean.

Value
Base URLhttps://api.munito.ai/sandboxes/v1
Key variableMUNITO_API_KEY
Templatesbase: Debian, Python 3.12, Node 24, git, curl and build tools. ubuntu: Ubuntu 24.04 with Docker, Claude Code, Codex and OpenCode. See Templates
Work folder/workspace, where each command starts unless you set cwd

Quick start

  1. Open API keys in the console.
  2. Create a key with the Inference permission.
  3. Run each step below in a terminal. Each step is one request with curl.

Create a sandbox

export MUNITO_API_KEY="munitoai_..."

curl -sS --fail-with-body https://api.munito.ai/sandboxes/v1/sandboxes \
  -H "Authorization: Bearer $MUNITO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"size": "small", "timeout": 600}'

The answer is the sandbox:

{
  "id": "sbx_01k8f6x7g3d2b9v5n4m1q0w8e6",
  "object": "sandbox",
  "status": "running",
  "template": "base",
  "size": "small",
  "vcpus": 1,
  "memory_mib": 1024,
  "disk_gib": 10,
  "reserved": false,
  "internet": true,
  "metadata": {},
  "created_at": 1793016000,
  "started_at": 1793016000,
  "expires_at": 1793016600,
  "ended_at": null,
  "end_reason": null,
  "command": null
}

Keep its id for the next steps:

export SANDBOX="sbx_01k8f6x7g3d2b9v5n4m1q0w8e6"

Run a command

curl -sS --fail-with-body "https://api.munito.ai/sandboxes/v1/sandboxes/$SANDBOX/exec" \
  -H "Authorization: Bearer $MUNITO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command": "python3 -c \"print(6 * 7)\""}'
{
  "exit_code": 0,
  "stdout": "42\n",
  "stderr": "",
  "duration_ms": 35,
  "timed_out": false,
  "truncated": false
}

A string runs in bash -lc. A list, such as ["python3", "-V"], is the argument vector itself. Set "stream": true to get NDJSON events while the command writes: stdout and stderr events, then one exit event.

To start a sandbox and run one command in one request, set command in the create. The sandbox runs it in /workspace with its env, for at most 60 seconds, and the answer has the result in command:

curl -sS --fail-with-body https://api.munito.ai/sandboxes/v1/sandboxes \
  -H "Authorization: Bearer $MUNITO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"size": "small", "command": "python3 -c \"print(6 * 7)\""}'

command is null when the command could not run, and for a retry with the same Idempotency-Key. The sandbox runs in each case, until its timeout or a delete.

Write, read and list files

curl -sS --fail-with-body -X PUT \
  "https://api.munito.ai/sandboxes/v1/sandboxes/$SANDBOX/files/content?path=/workspace/hello.py" \
  -H "Authorization: Bearer $MUNITO_API_KEY" \
  -H "Content-Type: application/octet-stream" \
  --data-binary 'print("Hello from the sandbox")'

curl -sS --fail-with-body \
  "https://api.munito.ai/sandboxes/v1/sandboxes/$SANDBOX/files/content?path=/workspace/hello.py" \
  -H "Authorization: Bearer $MUNITO_API_KEY"

curl -sS --fail-with-body \
  "https://api.munito.ai/sandboxes/v1/sandboxes/$SANDBOX/files?path=/workspace" \
  -H "Authorization: Bearer $MUNITO_API_KEY"

A write creates the parent folders, and replaces a file that exists.

Delete the sandbox

curl -sS --fail-with-body -X DELETE "https://api.munito.ai/sandboxes/v1/sandboxes/$SANDBOX" \
  -H "Authorization: Bearer $MUNITO_API_KEY"

The sandbox stops at once, and its files are deleted. You can still read the sandbox: its status is stopped and its end_reason is deleted.

Templates

Set template in the create. A template is the root file system that each new sandbox of it starts with.

TemplateSystemTools
baseDebian 12. The template of a create without templatePython 3.12, Node 24, git, curl, jq and build tools
ubuntuUbuntu 24.04 with systemdDocker with Compose and Buildx, Python 3.12, Node 24, git, curl, jq, build tools, Claude Code, Codex and OpenCode

pip install installs into the system Python in both templates.

Docker

In an ubuntu sandbox, Docker runs when the sandbox starts:

curl -sS --fail-with-body https://api.munito.ai/sandboxes/v1/sandboxes \
  -H "Authorization: Bearer $MUNITO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"template": "ubuntu", "size": "large", "command": "docker run --rm hello-world"}'

Use the default size or a larger one, because images take memory and disk. Docker Hub limits the image pulls of a user that is not logged in, so run docker login in the sandbox when you pull many images.

The base template has no Docker. Install it and start its daemon in the sandbox, one command at a time:

apt-get update && apt-get install -y docker.io
setsid dockerd --iptables=false --bridge=none >/var/log/dockerd.log 2>&1 < /dev/null &
docker run --rm --network none hello-world

The daemon keeps running after its command ends, so later commands can use it. These containers have no bridge network: use --network host for internet access, or --network none.

Agent harnesses

Claude Code (claude), Codex (codex) and OpenCode (opencode) are installed in ubuntu. Give each one its key in env, or use them with the open models of Munito AI: see Claude Code, Codex and OpenCode.

Sizes and prices

A size has a ceiling and a floor. The ceiling is the vCPUs and the memory that the sandbox sees. The floor is the smallest share that the sandbox pays for. Each second, a sandbox pays for the higher of its floor and its real use, for the CPU and for the memory.

PresetCeilingFloorIdle, per hourBusy at the ceiling, per hour
small1 vCPU, 1 GiB0.25 vCPU, 256 MiB$0.0072$0.0288
default2 vCPU, 4 GiB0.5 vCPU, 1 GiB$0.0162$0.0648
large4 vCPU, 8 GiB1 vCPU, 2 GiB$0.0324$0.1296
ResourcePrice
CPU$0.0252 per vCPU-hour
Memory$0.0036 per GiB-hour
  • A sandbox without size gets the default size.
  • For another size, set vcpus and memory_mib in place of size. The floor of a custom size is a quarter of its ceiling.
  • A preset starts from a snapshot of its template in less than a second. A custom size boots its template, which takes some seconds.
  • For a larger root disk, set disk_gib, such as {"size": "large", "disk_gib": 40}. The root file system grows to that size when the sandbox starts.
  • Set "reserved": true to pay for the ceiling all the time. The floor is then the ceiling.
  • The Metalboxes page of the console shows the vCPU-hours, the GiB-hours and the spend of the month.

Limits

LimitValue
vCPUs1 to 16
Memory512 to 32,768 MiB, in steps of 256 MiB, and at least 512 MiB for each vCPU
Disk10 to 100 GiB, set with disk_gib. The default is 10
Life of a sandbox10 to 86,400 seconds. The default is 3,600, one hour
Time of a command1 to 1,800 seconds. The default is 60
Output of a command1 MiB for each stream when you wait for the result. truncated is true when output is cut
File100 MiB
Environment100 variables. A value is at most 32 KiB
Metadata16 keys. A key is at most 64 characters, and a value at most 512
Running sandboxes50 at once for each organization. Above it, a create gets 429 sandbox_limit_reached. Talk to an engineer for more
CreatesNo limit on their number. API requests from one IP address are limited to 8,000 a minute

A sandbox has outbound internet access only. It cannot reach your other sandboxes or private addresses, and it cannot send mail over SMTP. Set "internet": false for a sandbox with no network. Inbound connections are not available yet.

Lifecycle and timeouts

StatusMeaning
pendingThe sandbox starts.
runningThe sandbox runs commands.
stoppedThe sandbox ended.
failedThe sandbox did not start, or an error stopped it.

A sandbox stops at the first of these events:

  • Its timeout ends. expires_at gives the time, and end_reason is timeout.
  • You delete it. end_reason is deleted.
  • An error stops it. end_reason is error.

The timeout counts from the create, or from the last change. To keep a sandbox longer, send a new timeout in seconds to POST /sandboxes/{sandbox_id}/timeout. A command does not change the timeout.

When a sandbox stops, its processes and its files are gone. The API keeps its record, so GET /sandboxes/{sandbox_id} and GET /sandboxes still show it.

API reference

An organization with Metalboxes on sees the reference page of each operation in the console docs.

OperationRequest
Create a sandboxPOST /sandboxes
List sandboxesGET /sandboxes
Get a sandboxGET /sandboxes/{sandbox_id}
Delete a sandboxDELETE /sandboxes/{sandbox_id}
Set the timeoutPOST /sandboxes/{sandbox_id}/timeout
Run a commandPOST /sandboxes/{sandbox_id}/exec
List filesGET /sandboxes/{sandbox_id}/files
Read a fileGET /sandboxes/{sandbox_id}/files/content
Write a filePUT /sandboxes/{sandbox_id}/files/content
Delete a fileDELETE /sandboxes/{sandbox_id}/files