Python SDK
The ibby package for Python 3.13 and later. The same calls as the TypeScript SDK, in snake_case.
pip install ibby
The package needs Python 3.13 or later. Request and response shapes are the API’s own, as pydantic models.
Make a client
import os
from ibby import Ibby
client = Ibby(
os.environ["IBBY_API_KEY"],
# Optional, shown with their defaults:
max_retries=2,
timeout=150.0,
)
client.close()
Use the client in a with block, or call close() when you are done. max_retries is how many times a call is retried after a dropped connection or a 503. Set it to 0 to turn retries off. A command is never retried on its own. timeout is the most one read or write may wait; None turns it off.
The key is read once. Surrounding spaces and line breaks are ignored; a key with a space or a control character inside raises ValueError, without showing the key.
Sandboxes
create() returns a Sandbox. Its methods act on that sandbox.
import os
from ibby import Ibby
with Ibby(os.environ["IBBY_API_KEY"]) as client:
sandbox = client.create(
workdir="/work",
name="report-run-1",
lifetime_ms=60_000,
resources={"cpu": 0.5, "memory_mib": 512},
)
print(sandbox.id, sandbox.info.status, sandbox.info.resources)
sandbox.make_directories(["/work/in", "/work/out"])
sandbox.write_files({"/work/in/data.csv": "a,b\n1,2\n", "/work/in/logo.bin": b"\x00\x01\x02"})
for entry in sandbox.list_files("/work/in").entries:
print(entry.type, entry.size_bytes, entry.path)
print(sandbox.read_file("/work/in/data.csv", max_bytes=1024).decode())
sandbox.remove()
To act on a sandbox you made earlier, use client.sandbox(sandbox_id). It makes no network call; its info is None until you call refresh(). client.get(sandbox_id) fetches the record as well. To remove a sandbox by its name after a crash, when you no longer have its id, use client.remove_by_name(name). An unknown name is fine.
create() also takes image= to run on an image you built (see Images), and store={"mount_path": "/work/in", "key_prefix": "runs/1/"} to attach part of the lane’s store folder read-only; the concepts page gives the rules.
A sandbox’s status and a listing entry’s type are plain strings, so a value added later never breaks your code’s parsing.
Running commands
import os
from ibby import ExecTimedOut, Ibby
with Ibby(os.environ["IBBY_API_KEY"]) as client:
sandbox = client.create(workdir="/work")
result = sandbox.exec(
["sh", "-c", "echo $GREETING"],
cwd="/work",
env={"GREETING": "hello"},
timeout_ms=30_000,
)
if isinstance(result, ExecTimedOut):
print("timed out")
else:
print(result.exit_code, result.stdout, result.stdout_truncated)
sandbox.remove()
command is a list of arguments. A string raises TypeError, so "ls -la" is never sent as single characters. A timeout is a result, not an exception. stdout is None when the output is unavailable: when you repeat a finished command with the same idempotency_key (the outcome comes back, the output does not), or, in this version, when the command timed out. stdout_truncated is False whenever stdout is None.
Errors
Every failed call raises IbbyError.
import os
from ibby import Ibby, IbbyError
with Ibby(os.environ["IBBY_API_KEY"]) as client:
try:
client.get("sbx_missing")
except IbbyError as error:
print(error.status, error.code, error.message, error.request_id)
code is the API’s stable code, such as not_found or sandbox_stopped, and message is the API’s own sentence. When no answer came back at all, status is 0 and code is connection_error or timeout. A redirect is never followed: it is an IbbyError with code unexpected_response. A request that breaks the schema’s own rules, such as a relative path, raises pydantic’s ValidationError before anything is sent. A rule between two fields, such as store.mount_path sitting inside workdir, is checked by the API, which answers 400 invalid_request.
Safe retries
Calls that create or write send a fresh Idempotency-Key. To retry one yourself, for example after your process restarts, pass your own key both times:
import os
from ibby import Ibby
with Ibby(os.environ["IBBY_API_KEY"]) as client:
sandbox = client.create(workdir="/work", idempotency_key="nightly-2026-11-13")
again = client.create(workdir="/work", idempotency_key="nightly-2026-11-13")
assert again.id == sandbox.id
sandbox.remove()
Images
build_image() starts a build from a recipe and returns its job. When the job is done, its result names the image to create sandboxes from. The concepts page explains recipes. A recipe that breaks the rules, such as a version range, raises pydantic’s ValidationError before anything is sent. get_image() raises IbbyError with code image_not_built (status 409) when the image has no build on the lane’s current provider, which happens only after a lane changes provider; rebuild it from its recipe.
import os
import time
from ibby import Ibby
HASH = "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
recipe = {
"base": {"reference": "python:3.13-slim", "digest": HASH},
"packages": [{"name": "numpy", "version": "2.3.1", "hashes": [HASH]}],
"import_check": ["numpy"],
}
with Ibby(os.environ["IBBY_API_KEY"]) as client:
job = client.build_image(recipe)
while job.status in ("accepted", "running"):
time.sleep(5)
job = client.get_job(job.id)
if job.status == "done" and job.result is not None:
image_id = job.result["image_id"]
print(client.get_image(image_id).status)
sandbox = client.create(workdir="/work", image=image_id)
sandbox.remove()
Jobs
import os
from ibby import Ibby
with Ibby(os.environ["IBBY_API_KEY"]) as client:
job = client.get_job("job_...")
if job.status == "running":
client.cancel_job(job.id)