E2B
loam speaks E2B's API as well as its own, so code written for E2B's official SDKs (Python
e2b and JavaScript e2b, unmodified) creates, uses, pauses, resumes and kills sandboxes on
loam. It's checked against both SDKs at 2.52.0.
Setup
Two environment variables, with your loam project key as the E2B key:
export E2B_DOMAIN=e2b.loam.example.com
export E2B_API_KEY=$LOAM_TOKEN # loam_...
| Control plane | https://api.e2b.loam.example.com |
| A sandbox's port P | https://<P>-<sandbox id>.e2b.loam.example.com |
| Key | X-API-Key: loam_... (the SDKs send it) |
import { Sandbox } from 'e2b'
const sbx = await Sandbox.create({ timeoutMs: 5 * 60_000, metadata: { owner: 'docs' } })
console.log('sandbox', sbx.sandboxId)
const r = await sbx.commands.run('echo hello from $(hostname); id -un')
console.log(r.exitCode, r.stdout.trim())
await sbx.files.write('/home/user/hello.txt', 'hi\n')
console.log('read:', (await sbx.files.read('/home/user/hello.txt')).trim())
await sbx.commands.run('python3 -m http.server 3000', { background: true })
await new Promise((r) => setTimeout(r, 1500))
const host = sbx.getHost(3000)
console.log('host', host, (await fetch(`https://${host}/`)).status)
await sbx.pause()
const again = await Sandbox.connect(sbx.sandboxId)
console.log('after resume:', (await again.commands.run('cat /home/user/hello.txt')).stdout.trim())
try {
await Sandbox.create('some-other-template')
} catch (e) {
console.log('refused:', e.constructor.name, e.message)
}
await again.kill()
$ node e2b.mjs
sandbox sb-nlucxvii5em3
0 hello from rustic-meadow-7517
user
read: hi
host 3000-sb-nlucxvii5em3.e2b.loam.example.com 200
after resume: hi
refused: SandboxError 404: template 'some-other-template' not found
Python is the same: Sandbox.create(), sbx.commands.run(...), sbx.files.write(...), and so on,
with the same two variables.
An E2B sandbox is a loam sandbox
The adapter makes ordinary loam sandboxes in your key's project, through loam's own rules, so
loam ls and the native API see them and can act on them:
| E2B | loam |
|---|---|
| sandbox ID | the loam sandbox ID, sb-… |
template base (the only one) |
the loam template e2b |
metadata k: v |
the label e2b/k: v |
envVars |
the sandbox's env |
timeout |
the sandbox's deadline, action delete (with autoPause, pause); idle is none, so an E2B sandbox runs until its timeout |
pause() / connect() |
loam's pause (or stop, with autoPauseMemory: false) and wake |
allow_internet_access, network.allowOut / denyOut |
the sandbox's egress rules |
A sandbox the adapter didn't make isn't an E2B sandbox: E2B's API answers 404 for it and doesn't list it.
What works
Create (Sandbox.create, with timeout, metadata, envVars, autoPause, autoPauseMemory,
autoResume, secure, allow_internet_access, network), connect and resume, pause,
kill, setTimeout, getInfo, list (with metadata and state filters, paged), getMetrics (one
current sample), and everything the SDK does in the sandbox through envd: commands (streaming,
background, stdin, PTY, signals), files (read, write, list, watch, signed upload and download
URLs), and getHost(port) for a sandbox's ports. Processes survive a pause and are listed after
the resume.
What's refused
Answered 404 Not found: this E2B-compatible server does not implement …: templates and their
build API (/v3/templates, /templates/…), snapshots and fork, volumes, sandbox logs and
events, GET /sandboxes/metrics, teams and API key management.
Refused at create with a 400 that names the field: any template but base, mcp, iam,
volume mounts, egress proxies and transform rules (network.rules, network.egressProxy), IP
addresses and CIDRs in allowOut/denyOut (except 0.0.0.0/0, "all traffic"),
allowPublicTraffic: false, secure: false, and a denyOut name that an allowOut wildcard
covers.
$ curl -sS -H "X-API-Key: $LOAM_TOKEN" https://api.e2b.loam.example.com/v3/templates
{"code":404,"message":"Not found: this E2B-compatible server does not implement GET /v3/templates"}
$ curl -sS -H "X-API-Key: $LOAM_TOKEN" -H 'Content-Type: application/json' \
https://api.e2b.loam.example.com/v2/sandboxes -d '{"templateID":"base","mcp":{}}'
{"code":400,"message":"mcp: not supported by this server"}
$ curl -sS -H "X-API-Key: nope" https://api.e2b.loam.example.com/v2/sandboxes
{"code":401,"message":"Invalid API key: use a loam project key (loam_...) as E2B_API_KEY"}
loam's own refusals come through in E2B's terms: no node with room is a 429 with Retry-After
(the SDKs retry it), something loam finds invalid (a label, an env name) a 400 with loam's
message, a sandbox that can't be woken a 503.
Egress
E2B's options become loam egress rules, which are names, decided by the most specific:
allow_internet_access: false, or0.0.0.0/0indenyOut, denies everything; then eachallowOutname is allowed (on ports 80 and 443) and eachdenyOutname denied.- Without that,
allowOutlimits nothing, and a name indenyOutis refused: loam can't allow everything except a name. - Addresses and CIDRs (other than
0.0.0.0/0) are refused.
Sandbox.updateNetwork (PUT /sandboxes/{id}/network) replaces the rules and applies them at
once. The SDK's own traffic to envd and to your ports comes in through loam, not the network, so
it works whatever the rules.
Differences from hosted E2B
- Ports are public.
https://<port>-<id>.e2b.loam.example.comneeds no credential, as on hosted E2B. The same port through loam's own URL (https://<port>--<name>--<project>.loam.example.com) keeps loam's access setting, private by default. - A server on
127.0.0.1is reachable; hosted E2B needs0.0.0.0. templateIDandaliasare bothbase.envdVersionis0.9.0.- Resources are the
e2btemplate's: 2 CPUs, 1024 MB, 20 GB of disk. - Metrics are one sample read when asked,
[]for a sandbox asleep in loam; there is no history. - The hostname is the loam sandbox's name (
rustic-meadow-7517), note2b. - A timeout is a loam deadline, acted on within a second of
endAt; a sandbox killed by its timeout is deleted, disk and all. A paused sandbox has no timeout and is kept until killed. - A pause is loam's: the memory snapshot can wake on any node, with its processes.
- No
X-RateLimit-*headers; nothing is rate limited but capacity.
The commands run as user (uid 1000, home /home/user, passwordless sudo), as on E2B. Using
the same sandbox through loam's own API, exec runs as loam (uid 1001) unless you give user.