CrowNest
Python SDK

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.

Terminal
pip install crownest
# or
uv add crownest

Create 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
)
ArgumentDefaultDescription
api_keyCROWNEST_API_KEY env varOrg-scoped API key.
base_urlhttps://api.crownest.devAPI base URL.
timeout660Request 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.run returns normally; check the result's exitCode and status.

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 pass sandbox_id on 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

On this page