SDK COMPATIBILITY MATRIX
E2B × CubeSandbox
Same workflow, not identical behavior.
Compare public SDK methods by name, see what works unchanged, and find the Cube alternative when behavior differs.
Python SDK quick start
Use the E2B Python SDK for existing E2B code, or the native CubeSandbox Python SDK for Cube APIs. Install and configure them as follows:
pip install "e2b==2.29.5"
pip install cubesandbox
export CUBE_API_URL="http://127.0.0.1:3000"
export CUBE_API_KEY="e2b_000000"
export CUBE_TEMPLATE_ID="tpl-xxx"
export E2B_API_URL="http://127.0.0.1:3000"
export E2B_API_KEY="e2b_000000"01 · VERSIONS
Every E2B release, tested
Each E2B Python SDK release from 1.0.0 through 2.62.0 was run against the full compatibility suite. Hover a block for its verdict; the bands below explain every judgment.
The full suite passes.
Passes after triage — the remaining failures are features those SDK versions lack, not platform issues.
Create, commands, files, and queries work; those SDK lines simply lack pause, network, and other newer options.
The suite does not pass on this range. Use 2.6.0 or later.
The suite does not pass on this range. For basics only, 1.6.0+ works.
- A few releases fail a small number of cases; each was checked case by case, and every remaining failure comes from the SDK itself or the test environment, never from Cube.
- run_code needs a matching e2b-code-interpreter: cores 2.38+ do not pair with interpreter 2.9.0, so use a 2.37.1 or older core.
02 · API MATRIX
SDK API matrix
Compatibility is assessed at the public method level. A matching name does not imply matching defaults, return types, or side effects.
All 156 releases were tested. Pin e2b 2.15.0 or later and everything passes; for run_code, pair the interpreter with a 2.37.1 or older core.
Examples run against Cube with a validated E2B Python SDK. Set E2B_API_KEY and CUBE_TEMPLATE_ID first. "Not available" entries show calls Cube does not implement.
Sandbox lifecycle
10Sandbox.create()View example ↓
import os
from e2b import Sandbox
# The context manager kills the sandbox when the block exits.
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"], timeout=60) as sandbox:
print(sandbox.sandbox_id)Sandbox.connect()View example ↓
import os
from e2b import Sandbox
sandbox = Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"], timeout=120)
try:
connected = Sandbox.connect(sandbox.sandbox_id, timeout=60)
print(connected.commands.run("echo connected").stdout)
finally:
sandbox.kill()Sandbox.list()View example ↓
from e2b import Sandbox
paginator = Sandbox.list(limit=10)
while paginator.has_next:
for info in paginator.next_items():
print(info.sandbox_id, info.state)sandbox.get_info()View example ↓
import os
from e2b import Sandbox
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as sandbox:
info = sandbox.get_info()
print(info.sandbox_id, info.state, info.started_at)sandbox.is_running()View example ↓
import os
from e2b import Sandbox
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as sandbox:
print("running:", sandbox.is_running())sandbox.kill()View example ↓
import os
from e2b import Sandbox
sandbox = Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"])
sandbox_id = sandbox.sandbox_id
removed = sandbox.kill()
print(sandbox_id, removed)sandbox.pause()View example ↓
import os
from e2b import Sandbox
sandbox = Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"])
sandbox_id = sandbox.sandbox_id
sandbox.pause() # Preserve state for a later resume.
resumed = Sandbox.connect(sandbox_id)
print(resumed.commands.run("echo resumed").stdout)
resumed.kill()sandbox.set_timeout()View example ↓
import os
from e2b import Sandbox
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"], timeout=60) as sandbox:
# Replace the current timeout with five minutes.
sandbox.set_timeout(300)
print(sandbox.get_info().end_at)sandbox.update_network()View example ↓
import os
from e2b import Sandbox
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as sandbox:
# Omitted fields are cleared because this replaces the policy.
sandbox.update_network({"deny_out": ["10.0.0.0/8"]})
print("network policy updated")sandbox.get_metrics()View example ↓
import os
from e2b import Sandbox
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as sandbox:
sandbox.commands.run("python -c 'sum(range(1000000))'")
for sample in sandbox.get_metrics():
print(sample)Commands, files & PTY
10sandbox.commands.run()View example ↓
import os
from e2b import Sandbox
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as sandbox:
result = sandbox.commands.run(
"python -c 'print(6 * 7)'",
timeout=30,
)
print(result.stdout, result.exit_code)commands.connect()View example ↓
import os
from e2b import Sandbox
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as sandbox:
process = sandbox.commands.run("sleep 2; echo done", background=True)
pid = process.pid
process.disconnect() # The process keeps running in the sandbox.
result = sandbox.commands.connect(pid).wait()
print(result.stdout)commands.listkillsend_stdinView example ↓
import os
from e2b import Sandbox
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as sandbox:
process = sandbox.commands.run("cat", background=True, stdin=True)
sandbox.commands.send_stdin(process.pid, "hello\n")
print([item.pid for item in sandbox.commands.list()])
sandbox.commands.kill(process.pid)files.readwritewrite_filesView example ↓
import os
from e2b import Sandbox
from e2b.sandbox.filesystem.filesystem import WriteEntry
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as sandbox:
sandbox.files.write("/tmp/one.txt", "one")
sandbox.files.write_files([
WriteEntry(path="/tmp/two.txt", data="two"),
WriteEntry(path="/tmp/three.bin", data=b"three"),
])
print(sandbox.files.read("/tmp/one.txt"))files.listexistsremoverenamemake_dirView example ↓
import os
from e2b import Sandbox
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as sandbox:
sandbox.files.make_dir("/tmp/demo")
sandbox.files.write("/tmp/demo/a.txt", "hello")
print(sandbox.files.exists("/tmp/demo/a.txt"))
print([entry.name for entry in sandbox.files.list("/tmp/demo")])
sandbox.files.rename("/tmp/demo/a.txt", "/tmp/demo/b.txt")
sandbox.files.remove("/tmp/demo")files.get_info()View example ↓
import os
from e2b import Sandbox
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as sandbox:
sandbox.files.write("/tmp/info.txt", "hello")
info = sandbox.files.get_info("/tmp/info.txt")
print(info.name, info.type, info.path)files.watch_dir()View example ↓
import os
from e2b import Sandbox
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as sandbox:
sandbox.files.make_dir("/tmp/watched")
watcher = sandbox.files.watch_dir("/tmp/watched")
try:
sandbox.files.write("/tmp/watched/new.txt", "hello")
print(watcher.get_new_events())
finally:
watcher.stop()sandbox.pty.*View example ↓
import os
from e2b import PtySize, Sandbox
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as sandbox:
terminal = sandbox.pty.create(PtySize(rows=24, cols=80))
sandbox.pty.send_stdin(terminal.pid, b"echo hello from PTY\n")
sandbox.pty.resize(terminal.pid, PtySize(rows=30, cols=100))
sandbox.pty.kill(terminal.pid)sandbox.git.*View example ↓
import os
from e2b import Sandbox
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as sandbox:
# The base sandbox-code template does not include Git.
sandbox.commands.run(
"apt-get update -qq && apt-get install -y -qq git",
timeout=120,
)
sandbox.git.clone(
"https://github.com/octocat/Hello-World.git",
path="/tmp/hello-world",
depth=1,
)
print(sandbox.git.status("/tmp/hello-world"))sandbox.run_code()View example ↓
import os
from e2b_code_interpreter import Sandbox
# Install e2b-code-interpreter and use its matching version.
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as sandbox:
execution = sandbox.run_code("x = 6 * 7; x")
print(execution.results[0].text)Snapshots, volumes & templates
6createlistdelete_snapshotView example ↓
import os
from e2b import Sandbox
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as sandbox:
sandbox.files.write("/tmp/state.txt", "snapshot me")
snapshot = sandbox.create_snapshot(name="docs-example")
paginator = Sandbox.list_snapshots(name="docs-example")
while paginator.has_next:
print([item.snapshot_id for item in paginator.next_items()])
Sandbox.delete_snapshot(snapshot.snapshot_id)sandbox.fork()View example ↓
import os
from e2b import Sandbox
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as parent:
parent.files.write("/tmp/shared.txt", "before fork")
children = parent.fork(count=2, timeout=60)
for child in children:
if isinstance(child, Exception):
print("fork failed:", child)
continue
print(child.files.read("/tmp/shared.txt"))
child.kill()Volume.createconnectlistdestroyView example ↓
from uuid import uuid4
from e2b import Volume
volume = Volume.create(f"docs-{uuid4().hex[:8]}")
connected = Volume.connect(volume.volume_id)
print(connected.volume_id)
print([(item.volume_id, item.name) for item in Volume.list()])
Volume.destroy(volume.volume_id)volume.read_filewrite_filelistView example ↓
from uuid import uuid4
from e2b import Volume
volume = Volume.create(f"docs-{uuid4().hex[:8]}")
try:
volume.write_file("/hello.txt", "hello", force=True)
print(volume.read_file("/hello.txt"))
print([entry.name for entry in volume.list("/")])
finally:
Volume.destroy(volume.volume_id)Template.build() fluent APIView example ↓
from uuid import uuid4
from e2b import Template
template = (
Template()
.from_python_image("3.12")
.run_cmd("python --version")
)
# Building can take several minutes and creates a project template.
Template.build(template, f"docs-python-{uuid4().hex[:8]}")Sandbox.create(template=snapshot_id)View example ↓
import os
from e2b import Sandbox
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as source:
source.files.write("/tmp/state.txt", "portable state")
snapshot = source.create_snapshot()
try:
# E2B snapshots act as templates; any node can restore them.
with Sandbox.create(template=snapshot.snapshot_id) as restored:
print(restored.files.read("/tmp/state.txt"))
finally:
Sandbox.delete_snapshot(snapshot.snapshot_id)Platform services
4sandbox.get_mcp_token()View example ↓
import os
from e2b import Sandbox
mcp = {
"github/modelcontextprotocol/servers": {
"run_cmd": "npx -y @modelcontextprotocol/server-everything"
}
}
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"], mcp=mcp) as sandbox:
print(sandbox.get_mcp_token())Sandbox.create(iam=...)View example ↓
import os
from e2b import Sandbox, Secret
identity = Secret.iam_token(
audience="sts.amazonaws.com",
token_type="JWT-SVID",
)
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"], iam={"tokens": {"aws": identity}}) as sandbox:
print(sandbox.sandbox_id)Secret.*View example ↓
from uuid import uuid4
from e2b import Secret
name = f"docs-{uuid4().hex[:8]}"
created = Secret.create(name, "initial-value")
Secret.update(name, "rotated-value")
print(Secret.get_info(name)) # Values are write-only.
print(Secret.list(limit=10).next_items())
Secret.destroy(created.secret_id)AsyncSandboxasync resourcesView example ↓
import os
import asyncio
from e2b import AsyncSandbox
async def main():
sandbox = await AsyncSandbox.create(template=os.environ["CUBE_TEMPLATE_ID"])
try:
result = await sandbox.commands.run("echo async")
print(result.stdout)
finally:
await sandbox.kill()
asyncio.run(main())Cube extensions
5sandbox.rollback()View example ↓
from cubesandbox import Sandbox
with Sandbox.create() as sandbox:
sandbox.files.write("/tmp/value.txt", "v1")
snapshot = sandbox.create_snapshot()
try:
sandbox.files.write("/tmp/value.txt", "v2")
sandbox.rollback(snapshot.snapshot_id)
print(sandbox.files.read("/tmp/value.txt"))
finally:
Sandbox.delete_snapshot(snapshot.snapshot_id)Volume.create(driver=...)VolumeMount(read_only=True)View example ↓
from cubesandbox import Sandbox, Volume, VolumeMount
# CUBE_VOLUME_DRIVER must name a configured Cube volume plugin.
volume = Volume.create("docs-data", driver=os.environ["CUBE_VOLUME_DRIVER"])
try:
with Sandbox.create(
volume_mounts={"/data": VolumeMount(volume, read_only=True)}
) as sandbox:
print(sandbox.files.list("/data"))
finally:
Volume.destroy(volume.volume_id)sandbox.update_network(rules=...)View example ↓
from cubesandbox import Action, Match, Rule, Sandbox
rule = Rule(
name="allow-example-api",
match=Match(host="api.example.com", path="/v1/"),
action=Action(allow=True, audit="metadata"),
)
with Sandbox.create() as sandbox:
sandbox.update_network({"rules": [rule]})Sandbox.create(distribution_scope=...)View example ↓
from cubesandbox import Sandbox
# Use a compute-node ID or host IP reported by CubeOps.
with Sandbox.create(
distribution_scope=[os.environ["CUBE_COMPUTE_NODE"]]
) as sandbox:
print(sandbox.sandbox_id)GET /v1/metrics/resourceView example ↓
import os
from urllib.request import urlopen
cubelet_url = os.environ.get("CUBELET_URL", "http://127.0.0.1:9998")
url = f'{cubelet_url.rstrip("/")}/v1/metrics/resource'
with urlopen(url, timeout=5) as response:
print(response.read().decode())03 · CUBE API
REST API compatibility
One level below the SDK: e2b client endpoints against CubeAPI paths, parameters, responses, and error codes.
Sandboxes
14POST/v2/sandboxesDiffersMissing secure/iam inputs; the response is otherwise aligned (alias included).
GET/v2/sandboxesDiffersstate/metadata filters work; order, start-time, and template filters are missing.
GET/sandboxes/{id}AlignedAll required fields present (endAt, alias, disk size).
DELETE/sandboxes/{id}DiffersSame route; e2b returns False on 404 while Cube raises not-found.
POST/v2/sandboxes/{id}/connectDiffersMissing on_resume — restore always resumes the saved state.
POST/sandboxes/{id}/pauseIncompatiblekeep_memory=false is rejected — pause always keeps the memory snapshot.
POST/sandboxes/{id}/timeoutAlignedIdentical; -1 means never timing out.
POST/sandboxes/{id}/forkAlignedIdentical per-fork results from one snapshot.
POST/sandboxes/{id}/snapshotsIncompatiblemode="filesystem" is rejected — snapshots always keep memory.
PUT/sandboxes/{id}/networkDiffersMissing egress_proxy and https_ports.
GET/sandboxes/{id}/metricsMissingNo such route; use Cubelet metrics instead.
POST/sandboxes/{id}/resumeCube onlyCube only: explicit resume.
POST/sandboxes/{id}/rollbackCube onlyCube only: in-place rollback.
POST/sandboxes/{id}/cloneCube onlyCube only: clone a sandbox.
Snapshots
3GET/snapshotsAlignedName filter and paging work.
DELETE/templates/{id or name}AlignedDeletes a snapshot by ID or name, returns bool; ambiguous names get 409.
DELETE/templates/{id}AlignedTemplate deletion aligned.
Templates
10GET/templatesDiffersFlat list; missing public/builds fields.
GET/templates/aliases/{alias}IncompatibleMissing the aliases field; the e2b model fails to parse.
GET/templates/{id}AlignedAll fields present.
POST/templatesIncompatibleDifferent build models: e2b takes a Dockerfile DSL, Cube takes an image reference.
GET/v2/templatesMissingNo v2 template routes (tags).
POST/templates/tagsMissingNo tags model.
GET/templates/{id}/builds/{bid}/statusDiffersMissing log_entries; adds progress/message.
GET/templates/{id}/builds/{bid}/logsCube onlyCube only: build logs.
POST/templates/{id}/rebuildCube onlyCube only: rebuild from the image.
PUT/templates/{id}/aliasCube onlyCube only: set alias.
Volumes
5POST/volumesAlignedAligned (name optional; response misses domain).
GET/volumesAlignedAligned.
GET/volumes/{id}AlignedAligned (misses domain).
DELETE/volumes/{id}AlignedAligned; 404 returns False.
*/volumecontent/**MissingThe whole volume-content group (7 endpoints); mount the volume and use sandbox.files.
ENVD data plane
1RPCprocess / filesystem / files / executeAlignedCommands, files, PTY, and code execution are all identical.
The data plane bypasses CubeAPI: CubeProxy forwards straight to envd inside the sandbox (compiled from upstream e2b-dev/infra), so request bodies, streaming frames, and error frames are identical; fixed-address traffic is routed via E2b-Sandbox-* headers.