Python SDK
Install the crownest package, construct sync and async clients, and run your first sandbox from Python.
The crownest package is the official Python client for CrowNest. It
ships sync and async clients with the same surface, snake_case keyword
arguments, and automatic idempotency keys for create operations. It
requires Python 3.11 or later and depends on httpx.
Install
Install from PyPI with pip or uv.
pip install crownest
# or
uv add crownestCreate a client
Construct a CrowNest client. All arguments are optional.
from crownest import CrowNest
client = CrowNest(
api_key="cn_live_...", # or env CROWNEST_API_KEY
base_url="https://api.crownest.dev", # default
timeout=660, # seconds
)| Argument | Default | Description |
|---|---|---|
api_key | CROWNEST_API_KEY env var | Org-scoped API key. |
base_url | https://api.crownest.dev | API base URL. |
timeout | 660 | Request timeout in seconds. |
The sync client works as a context manager and also exposes .close()
for manual cleanup:
with CrowNest() as client:
sandboxes = client.sandboxes.list()Async client
AsyncCrowNest exposes the same surface with await and works as an
async context manager.
import asyncio
from crownest import AsyncCrowNest
async def main() -> None:
async with AsyncCrowNest() as client:
sandbox = await client.sandboxes.create(template="python")
result = await sandbox.commands.run("python -c 'print(40 + 2)'")
print(result["exitCode"], result["stdout"])
await sandbox.kill()
asyncio.run(main())Every sync method has an async counterpart with the same name and arguments.
Error handling
Failed API calls raise crownest.CrowNestApiError, which carries
.status (HTTP status), .code (stable snake_case error code), and
.details. str(e) gives the human-readable message.
from crownest import CrowNest, CrowNestApiError
with CrowNest() as client:
try:
client.sandboxes.get("sbx_does_not_exist")
except CrowNestApiError as e:
print(e.status, e.code, e)[!NOTE] A command finishing with a non-zero exit code is not an API error.
commands.runreturns normally; check the result'sexitCodeandstatus.
SandboxHandle ergonomics
client.sandboxes.create() and .get() return a SandboxHandle. The
handle behaves like the sandbox object — it supports attribute access
(sandbox.id, sandbox.status), mapping access (sandbox["expiresAt"]),
and .to_dict() — and adds:
.kill()— destroy the sandbox..commands,.files,.artifacts,.previews— sub-clients bound to the sandbox, so you don't passsandbox_idon every call.
sandbox = client.sandboxes.create(template="node")
sandbox.files.write("app.js", "console.log('hi')")
result = sandbox.commands.run("node app.js")Worked example
This script creates a Python sandbox, writes a script, runs it, exports the output as an artifact, and cleans up.
from crownest import CrowNest
SCRIPT = """
with open("primes.txt", "w") as f:
primes = [n for n in range(2, 50)
if all(n % d for d in range(2, n))]
f.write("\\n".join(map(str, primes)))
print("wrote", "primes.txt")
"""
with CrowNest() as client:
sandbox = client.sandboxes.create(
template="python",
ttl_ms=10 * 60 * 1000,
metadata={"job": "primes-demo"},
)
try:
sandbox.files.write("primes.py", SCRIPT)
result = sandbox.commands.run("python primes.py")
print("exit code:", result["exitCode"])
print(result["stdout"])
artifact = sandbox.artifacts.create(path="primes.txt")
data = client.artifacts.download(artifact["id"])
print(data.decode())
finally:
sandbox.kill()Idempotency keys are generated automatically for sandboxes.create,
commands.run, commands.start, and artifacts.create, so safe retries
work out of the box. Pass idempotency_key= explicitly to control the
key yourself.
Next steps
- Python API reference — every client and method.
- Sandboxes concept — lifecycle, TTL, and templates.
- API error reference — error codes in depth.