ephemeral sandboxes.md
Sandboxes
An isolated machine that ends when you stop using it. Run your code on it. When it has been idle for a few minutes, or when its time limit passes, Maritime ends the machine and its disk. Each one is its own micro-VM on the sandbox engine, with its own kernel.
At a glance
| What it is | An isolated Linux micro-VM on the sandbox engine, ended with its disk when it has been idle for a few minutes or at its time limit. Its id is sbx-.... |
| Use it for | Code a model wrote, test runs, analysis of a file that must not stay on disk. |
| Use something else when | Files must outlive the run: Agent box. Many at once: Swarm. A screen: Linux desktop. |
| Start it | New sandbox (?type=ephemeral), POST /api/sandboxes/ephemeral, maritime sandboxes create, or ephemeral.createEphemeral in the SDK. |
| Lifecycle | Starts, runs your commands through the Sandboxes API, and the sandbox engine ends it with its disk after idleMinutes without activity (default 5), or at expiresAt, the time limit. POST /api/sandboxes/ephemeral/{id} restarts the time limit. |
| Price | $0.0055 per vCPU-hour + $0.00275 per GB-hour while running, per second, at any size (small, 2 vCPU and 4 GB, is $0.022 an hour). Disk is included. With onIdle: "pause", a paused sandbox costs $0.08 per GB-month of disk. |
| Limits | Idle time 1 to 60 minutes, time limit 5 to 1,440 minutes. Presets small (default), medium, large and xlarge, or exact vcpu 1 to 16, memoryMib 1,024 to 65,536 and diskGib 5 to 500. Counts toward awake at once while running. |
| Related | Agent boxes, Swarms, REST API. |
When to use it
- Running code a model wrote, where nothing should outlive the run.
- Tests for a coding agent, one clean machine per run.
- Analysis of an uploaded file that must not stay on disk.
If the machine should keep its files between runs, use an Agent box instead.
From the dashboard
Open New sandbox
(/dashboard/new-sandbox?type=ephemeral), name it, and pick a preset size or set your own vCPU,
memory and disk. Under How it ends, pick how long it may stay idle (1, 2, 5, 15 or 60 minutes,
default 2) and its time limit (1, 4 or 24 hours, default 24). Then click Create sandbox. For other values,
use the API below.
From the API
One call starts a sandbox: POST /api/sandboxes/ephemeral. When it has been idle for
idleMinutes, or at its time limit, the sandbox engine ends it, disk included. The key needs
the sandboxes scope (or manage).
# start it: it ends, disk and all, after 5 idle minutes or 60 minutes from now
curl -X POST https://api.maritime.sh/api/sandboxes/ephemeral \
-H "Authorization: Bearer $MARITIME_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "job-42", "idleMinutes": 5, "timeLimitMinutes": 60, "size": "small"}'
# 201: the Sandbox object plus when it ends
# {"id": "sbx-k3m7q2ab", "name": "job-42", "state": "running", "size": "small",
# "resources": {"vcpu": 2, "memoryMib": 4096, "diskGib": 12},
# "dataPlaneUrl": "https://api-sbx-k3m7q2ab.sb.mtm.sh/v1", "secret": "...",
# ..., "expiresAt": "2026-10-26T18:00:00+00:00", "endsWhenIdleSeconds": 300}
Every body field is optional. Three fields set how the sandbox ends:
idleMinutes: how long it may stay idle before it ends. 1 to 60, default 5.timeLimitMinutes: when it ends whatever its activity. 5 to 1,440, default 1,440 (24 hours).onIdle:kill, the default, orpause, which pauses an idle sandbox instead of ending it.
The other fields are those of POST /v1/sandboxes: name, from (a stock template such as
base or python, a saved snapshot, or an OCI image; default base), size (default
small), vcpu, memoryMib, diskGib, screen, network, endUser, forEndUser,
env, metadata and failFast. The /v1 timer fields idleTimeoutSeconds,
timeLimitSeconds and keepAwake are refused. A create with the name of a live sandbox returns
that sandbox with status 200. Send an Idempotency-Key header to retry safely.
lifetimeMinutes is a deprecated alias of timeLimitMinutes, kept for earlier clients. A body
with both returns 400 invalid_request.
The answer is the Sandbox object, the same as GET /v1/sandboxes/{id}, plus expiresAt, the
time limit's deadline, and endsWhenIdleSeconds, the idle time that ends it. Its id
(sbx-...) works with every /v1/sandboxes call and with the data plane. Refusals are the
Sandboxes Error object (code, message, hint, docUrl, requestId).
To extend the time limit, call POST /api/sandboxes/ephemeral/{id} with
{"timeLimitMinutes": 60} (5 to 1,440, default 1,440). The time limit restarts from that call.
The idle timer does not change. A sandbox without a time limit that ends it returns 400
invalid_request. To end it now, call DELETE /api/sandboxes/ephemeral/{id}; calling it
again is safe.
# restart the time limit: it now ends 30 minutes from now at the latest
curl -X POST https://api.maritime.sh/api/sandboxes/ephemeral/sbx-k3m7q2ab \
-H "Authorization: Bearer $MARITIME_API_KEY" \
-H "Content-Type: application/json" \
-d '{"timeLimitMinutes": 30}'
# end it now
curl -X DELETE https://api.maritime.sh/api/sandboxes/ephemeral/sbx-k3m7q2ab \
-H "Authorization: Bearer $MARITIME_API_KEY"
List your live sandboxes, newest first. The list also has sandboxes you made with
/v1/sandboxes or the E2B-compatible API, but not swarm sandboxes. expiresAt is null for a
sandbox without a time limit, and while a sandbox is not running. endsWhenIdleSeconds is null
when going idle pauses the sandbox instead of ending it.
curl -H "Authorization: Bearer $MARITIME_API_KEY" \
https://api.maritime.sh/api/sandboxes/ephemeral
# {"sandboxes": [{"id": "sbx-k3m7q2ab", ..., "expiresAt": "...", "endsWhenIdleSeconds": 300}], "nextCursor": null}
Commands and files
A sandbox you start with these calls is a sandbox of the Sandboxes API, so commands and files run through it: the
SDK's sandboxes client, maritime sandbox in the CLI, or the data plane at the sandbox's
dataPlaneUrl.
import { Maritime } from 'maritime-sdk'
const maritime = new Maritime({ apiKey: process.env.MARITIME_API_KEY })
const box = await maritime.ephemeral.createEphemeral({ idleMinutes: 5, timeLimitMinutes: 30 })
const sbx = await maritime.sandboxes.connect(box)
const { stdout } = await sbx.run('uname -a')
console.log(stdout, 'ends at', box.expiresAt)
await maritime.ephemeral.killEphemeral(box.id) // or let it end when idle
import os
from maritime import Maritime
client = Maritime(api_key=os.environ["MARITIME_API_KEY"])
box = client.ephemeral.create_ephemeral(idle_minutes=5, time_limit_minutes=30)
sbx = client.sandboxes.connect(box["id"])
print(sbx.run("uname -a").stdout, "ends at", box["expiresAt"])
client.ephemeral.kill_ephemeral(box["id"]) # or let it end when idle
maritime sandboxes create --name job-42 --idle 5m --time-limit 30m
maritime sandbox exec sbx-k3m7q2ab -- uname -a
maritime sandbox cp ./data.csv sbx-k3m7q2ab:/tmp/data.csv
maritime sandboxes kill sbx-k3m7q2ab
In the CLI, --time-limit sets the time limit (5m to 24h, default 24h) and --idle the idle time
(1m to 60m, default 5m); --on-idle pause pauses instead of ending. --lifetime still works as the
old name of --time-limit.
Over plain HTTP, send the command to {dataPlaneUrl}/commands with the sandbox's secret in
X-Maritime-Token. The answer is the Command object, with exitCode, stdout and stderr,
when the process exits.
curl -X POST "$DATA_PLANE_URL/commands" \
-H "X-Maritime-Token: $SANDBOX_SECRET" \
-H "Content-Type: application/json" \
-d '{"cmd": "uname -a"}'
E2B-compatible API
Code written for the stock E2B SDKs (e2b and @e2b/code-interpreter) works against Maritime.
Set E2B_DOMAIN to sb.mtm.sh and E2B_API_KEY to a Maritime key with the sandboxes
scope. The SDK then calls https://api.sb.mtm.sh, which serves Maritime's /e2b routes;
set E2B_API_URL only to point it somewhere else. A sandbox made this way is a Maritime sandbox,
billed at the same rates.
export E2B_DOMAIN=sb.mtm.sh
export E2B_API_KEY=$MARITIME_API_KEY
# your existing E2B code runs unchanged, for example:
python -c 'from e2b import Sandbox; print(Sandbox.create().commands.run("uname -a").stdout)'
How it ends
- The sandbox engine ends the sandbox, disk included, after
idleMinuteswithout activity or at the time limit, whichever comes first. No separate cleanup job is involved. - Activity is a data plane call, a foreground command whose caller still waits, an open terminal,
or a request or connection through a port URL. A command started with
background: truedoes not count. If the sandbox only runs a background job, it ends afteridleMinutes. To keep it running, wait on the command in the foreground, or raiseidleMinutes. idleMinutesis 1 to 60, default 5.timeLimitMinutesis 5 to 1,440 (24 hours), default 1,440. A value outside its range returns 400invalid_request.- The time limit counts running time only, from the create or the last resume. Extending it restarts it from now.
- A sandbox the idle timer ended has
endedReason: "idle". One the time limit ended hasendedReason: "timeout". GET /v1/sandboxes/{id}/usagereturnsidleSeconds, the seconds since the last activity, with the CPU, memory and disk in use. See the API reference.- You can end it earlier with
DELETE /api/sandboxes/ephemeral/{id}ormaritime sandboxes kill <id>. - A wrong id, or another account's sandbox, returns 404
sandbox_not_found.
Sizes, limits and billing
| Size | vCPU | Memory | Disk | Per hour |
|---|---|---|---|---|
| small (default) | 2 | 4 GB | 12 GB | $0.022 |
| medium | 4 | 8 GB | 50 GB | $0.044 |
| large | 8 | 16 GB | 125 GB | $0.088 |
| xlarge | 16 | 32 GB | 250 GB | $0.176 |
The sizes are presets. To set exact resources, send vcpu (1 to 16), memoryMib (1,024 to
65,536, in whole GiB) and diskGib (5 to 500), alone or on top of a size. Each exact field
overrides the preset's value. The Sandbox object shows them in resources, and its size is
custom when they match no preset. A sandbox with 1 vCPU and 2 GiB of memory costs $0.011 per
hour.
- It is billed per second while running, with no minimum: $0.0055 per vCPU-hour plus $0.00275 per GB-hour of memory. Disk is included. It draws from your plan's usage credit and counts toward the free trial's $20. See Billing & plans.
- While it runs, it counts toward your plan's machines awake at once.
- Without a plan or the free trial, a create returns 402
plan_required, whosecheckoutUrlis the billing page. At the awake limit it returns 429concurrency_limit.
Every number is in Limits & errors.