LelantosLelantos

E2B Compatibility

Lelantos implements the E2B REST API surface. Sandbox lifecycle, command execution (commands.run), process streaming, and filesystem operations work through the stock E2B SDK pointed at Lelantos.

Lelantos implements the E2B REST API surface plus the in-VM RPC surface the SDK uses. You can create, list, kill, and set timeouts on sandboxes, run commands, stream stdout/stderr, and read/write files using the stock E2B SDK or the REST API.

The SDK works in-VM, not just for lifecycle. commands.run(), process.start() (background), streaming stdout/stderr, and files.read()/files.write()/files.list() all work through the SDK — the sandbox subdomain proxy transcodes the SDK's Connect-RPC stream and forwards it to the in-guest envd daemon. A few advanced features (files.watch(), PTY resize) are not yet wired; see the coverage table below for exact verdicts.

Migration in Two Steps

  1. Get a Lelantos API key in e2b_ form — sign up at lelantos.ai and create a key. Lelantos issues keys with the lel_ prefix; for the E2B SDK use the same key in its e2b_ form (replace the leading lel_ with e2b_). The dashboard's API Keys → New API Key screen has a Lelantos / E2B SDK format toggle that shows both. The newer E2B SDK validates the key prefix client-side and rejects anything that isn't e2b_…, so always pass the e2b_ form to the SDK.
  2. Point the SDK at Lelantos — set the SDK's domain / api_url (apiUrl) to lelantos.ai instead of e2b.dev.

Quick Start

from e2b import Sandbox

sandbox = Sandbox.create(
    template="base",
    api_key="e2b_your_key_here",   # same key, e2b_ form for the E2B SDK
    api_url="https://lelantos.ai",
    domain="lelantos.ai",
)

sandbox.files.write("/hello.txt", "Hello from Lelantos!")
content = sandbox.files.read("/hello.txt")
print(content)

sandbox.kill()
import { Sandbox } from "e2b";

const sandbox = await Sandbox.create({
  template: "base",
  apiKey: "e2b_your_key_here",   // same key, e2b_ form for the E2B SDK
  apiUrl: "https://lelantos.ai",
  domain: "lelantos.ai",
});

await sandbox.files.write("/hello.txt", "Hello from Lelantos!");
const content = await sandbox.files.read("/hello.txt");
console.log(content);

await sandbox.kill();

Before & After

# Before (E2B)
from e2b import Sandbox
sandbox = Sandbox.create(api_key="e2b_key")
sandbox.files.write("/hello.txt", "Hello!")
sandbox.kill()

# After (Lelantos) — same key in e2b_ form, add api_url and domain
from e2b import Sandbox
sandbox = Sandbox.create(
    template="base",
    api_key="e2b_key",   # your Lelantos key with the e2b_ prefix
    api_url="https://lelantos.ai",
    domain="lelantos.ai",
)
sandbox.files.write("/hello.txt", "Hello!")
sandbox.kill()
// Before (E2B)
import { Sandbox } from "e2b";
const sandbox = await Sandbox.create({ apiKey: "e2b_key" });
await sandbox.files.write("/hello.txt", "Hello!");
await sandbox.kill();

// After (Lelantos) — same key in e2b_ form, add apiUrl and domain
import { Sandbox } from "e2b";
const sandbox = await Sandbox.create({
  template: "base",
  apiKey: "e2b_key",   // your Lelantos key with the e2b_ prefix
  apiUrl: "https://lelantos.ai",
  domain: "lelantos.ai",
});
await sandbox.files.write("/hello.txt", "Hello!");
await sandbox.kill();

Run Commands

result = sandbox.commands.run("echo Hello && uname -s")
print(result.stdout)   # Hello\nLinux
print(result.stderr)   # (empty)
print(result.exit_code) # 0
const result = await sandbox.commands.run("echo Hello && uname -s");
console.log(result.stdout);   // Hello\nLinux
console.log(result.stderr);   // (empty)
console.log(result.exitCode); // 0

File Operations

# Write
sandbox.files.write("/tmp/data.txt", "some data")

# Read
content = sandbox.files.read("/tmp/data.txt")

# List
entries = sandbox.files.list("/tmp")
for entry in entries:
    print(entry.name)
// Write
await sandbox.files.write("/tmp/data.txt", "some data");

// Read
const content = await sandbox.files.read("/tmp/data.txt");

// List
const entries = await sandbox.files.list("/tmp");
for (const entry of entries) {
  console.log(entry.name);
}

REST API (No SDK)

For full control without the E2B SDK:

import os
import requests

API_KEY = os.environ["LELANTOS_API_KEY"]
BASE = "https://api.lelantos.ai"
headers = {"X-API-Key": API_KEY, "Content-Type": "application/json"}

# Create
resp = requests.post(f"{BASE}/sandboxes", headers=headers, json={"templateID": "base"})
sandbox_id = resp.json()["sandboxID"]

# List
sandboxes = requests.get(f"{BASE}/sandboxes", headers=headers).json()

# Kill
requests.delete(f"{BASE}/sandboxes/{sandbox_id}", headers=headers)
const API_KEY = process.env.LELANTOS_API_KEY;
const BASE = "https://api.lelantos.ai";
const headers = { "X-API-Key": API_KEY, "Content-Type": "application/json" };

// Create
const resp = await fetch(`${BASE}/sandboxes`, {
  method: "POST", headers, body: JSON.stringify({ templateID: "base" }),
});
const { sandboxID } = await resp.json();

// List
const sandboxes = await fetch(`${BASE}/sandboxes`, { headers }).then(r => r.json());

// Kill
await fetch(`${BASE}/sandboxes/${sandboxID}`, { method: "DELETE", headers });
# Create
curl -X POST https://api.lelantos.ai/sandboxes \
  -H "X-API-Key: lel_your_key" \
  -H "Content-Type: application/json" \
  -d '{"templateID": "base"}'

# List
curl https://api.lelantos.ai/sandboxes -H "X-API-Key: lel_your_key"

# Kill
curl -X DELETE https://api.lelantos.ai/sandboxes/SANDBOX_ID \
  -H "X-API-Key: lel_your_key"

API Endpoint Compatibility

EndpointMethodStatusNotes
/sandboxesPOSTCompatible
/sandboxesGETCompatible
/v2/sandboxesGETCompatibleIncludes paused sandboxes
/sandboxes/:idGETCompatible
/sandboxes/:idDELETECompatible
/sandboxes/:id/refreshesPOSTCompatible
/sandboxes/:id/timeoutPOSTCompatible
/sandboxes/:id/pausePOSTCompatibleIn-RAM freeze by default; pass durable:true for a snapshot that survives node restart (202)
/sandboxes/:id/resumePOSTCompatible
/sandboxes/:id/logsGETCompatibleLive sandbox: process stdout/stderr merged with lifecycle events, time-ordered (COMPAT-05)
/sandboxes/:id/metricsGETCompatible
/templatesGET, POSTCompatible
/templates/:idGET, DELETECompatible
/snapshotsGET, POSTCompatible

SDK Feature Coverage

This table maps the E2B SDK surface to the Lelantos implementation that backs it, verified against the source. The stock e2b SDK's command-exec and filesystem calls reach the VM through the sandbox subdomain proxy → envd path (internal/dataplane/sandboxproxy/proxy.go), which transcodes the SDK's Connect-RPC envelope to the JSON the in-guest daemon understands.

Legend: ✅ Supported · ⚠️ Partial / caveat · ❌ Not supported · 🔶 Lelantos-native (no E2B equivalent)

Sandbox lifecycle

SDK callStatusBacked byNotes
Sandbox.create (+ template)POST /sandboxes (openapi.yml:1592)templateID selects the template/rootfs
Sandbox.connect / reconnectPOST /sandboxes/{id}/connect (openapi.yml:2541)200 if running, 201 if resumed from pause
sandbox.kill() / destroyDELETE /sandboxes/{id} (openapi.yml:2391)
Sandbox.listGET /sandboxes (openapi.yml:1592); GET /v2/sandboxes (openapi.yml:2326)v2 also includes paused sandboxes
sandbox.setTimeout()POST /sandboxes/{id}/timeout (openapi.yml:2411)0–86400s
TTL refresh / keep-alivePOST /sandboxes/{id}/refreshes (openapi.yml:2444)

Commands / process

SDK callStatusBacked byNotes
commands.run() (foreground)proxy → envd /process.Process/Start (process.go:88) via SandboxProxy (proxy.go)Proven by tests/integration/e2b_sdk_test.py (commands.run("echo pingpong"))
commands.run(background) / process.start()envd /process.Process/Start (process.go:148)Background processes stream as a Connect stream; List enumerates them (process.go:89)
Streaming stdout / stderrConnect-RPC transcode (proxy.go:298) → envd streaming StartNDJSON re-enveloped into the Connect stream the SDK expects
Exit codesend event ExitCode (process.go:408)Real exit code surfaced; reaper race fixed
stdin / sendStdinenvd /process.Process/SendInput (process.go:90)
Signals / kill processenvd /process.Process/SendSignal (process.go:91)
PTY (pty.create, start)⚠️pty.Start in handleStart (process.go:240)A PTY-backed process can be started; PTY resize is not implemented
process.connect (reattach to a running bg process stream)⚠️List only (process.go:89)Enumerable, but no per-process stream-reattach RPC

Filesystem

SDK callStatusBacked byNotes
files.read()envd GET /file (server.go:147) / ListDir+read pathProven in e2b_sdk_test.py (COPY'd payload read back)
files.write()envd /files upload (server.go:135)Also reachable via POST /sandboxes/{id}/files (files.go:20)
files.list()envd /filesystem.Filesystem/ListDir (filesystem.go:31)
files.exists / statenvd /filesystem.Filesystem/Stat (filesystem.go:29)
files.makeDir()envd /filesystem.Filesystem/MakeDir (filesystem.go:30)
files.remove()envd /filesystem.Filesystem/Remove (filesystem.go:32)
files.rename() / moveenvd /filesystem.Filesystem/Move (filesystem.go:33)
Upload / download (bulk)POST / GET /sandboxes/{id}/files (files.go:20) + envd /files/extract (server.go:136)
files.watch()No filesystem-watch RPC is registered in envd (filesystem.go); not supported

Pause / resume

SDK callStatusBacked byNotes
sandbox.pause() (in-RAM freeze)POST /sandboxes/{id}/pause → 204 (openapi.yml:2473)Default path: in-RAM vCPU freeze
sandbox.pause(durable)same endpoint → 202, durable:true (openapi.yml:2493)Durable pause shipped (COMPAT-06): VM snapshot to S3, survives node restart, lazy uffd resume
sandbox.resume()POST /sandboxes/{id}/resume (openapi.yml:2510)

Metrics / logs

SDK callStatusBacked byNotes
sandbox.getMetrics()GET /sandboxes/{id}/metrics (openapi.yml:2610)
sandbox.getLogs()GET /sandboxes/{id}/logs (sandboxes.go:1683, COMPAT-05)For a live sandbox, merges process stdout/stderr (pulled from envd) with lifecycle events, time-ordered; historical sandboxes return lifecycle events only

Templates

SDK callStatusBacked byNotes
Template.build()POST /v3/templates + builds (openapi.yml:2867)Lelantos-native build pipeline (RFC-005); from_image + run_cmd + copy proven in e2b_sdk_test.py
Template list / get / delete/templates, /templates/{id} (openapi.yml:2700, 2758)
Snapshots🔶/snapshots, /sandboxes/{id}/snapshots (openapi.yml:2635, 2666)Lelantos snapshot surface (not part of the public E2B SDK)

Auth

FeatureStatusBacked byNotes
e2b_-prefixed key acceptanceCanonicalizeAPIKey (shared/pkg/keys/keys.go:63)e2b_<body> is canonicalized to lel_<body> and hashes to the same stored key — the same key works in both forms (see the dual-key section above)

Key Differences

E2BLelantos
Default timeout5 minutes24 hours
BillingPer-minutePer-second
InfrastructureAWS (US)Hetzner bare metal (EU/Germany)
Warm pool-Live pre-warmed VM pool, ~130 ms handoff (measured ~394 ms p50 EU start)
GDPRUS-basedEU-native, data never leaves Germany
ResourcesFixedConfigurable per template (1-8 vCPU, 128-8192 MB)

If your E2B code relies on US-based infrastructure or E2B-only features not listed above, test thoroughly before migrating production workloads.

On this page