CrowNest
Quickstart

Python SDK quickstart

Create a sandbox, run a command, work with files, export an artifact, and expose a preview with the crownest package for Python.

This quickstart walks you through the full sandbox lifecycle with the Python SDK: create a sandbox, run a command, write and read a file, export an artifact, expose a preview, and clean up. By the end you have a complete script you can drop into your own project.

Prerequisites

You need two things before you start.

  • Python 3.11 or later. The SDK depends on httpx.
  • A CrowNest API key, created in the dashboard at https://crownest.dev. See API keys for scopes and presets.

[!WARNING] The raw API key is shown once, at creation time. Copy it immediately and store it somewhere safe — you can't view it again later.

Set up

Follow these steps to install the SDK and authenticate.

  1. Install the package from PyPI.

    Terminal
    pip install crownest

    With uv: uv add crownest.

  2. Export your API key. The client reads CROWNEST_API_KEY from the environment when you don't pass api_key explicitly.

    Terminal
    export CROWNEST_API_KEY="cn_live_..."
  3. Create a client. The sync client supports use as a context manager, which closes the underlying connection for you; outside a with block, call .close() when you're done.

    main.py
    from crownest import CrowNest
    
    client = CrowNest(
        api_key="cn_live_...",      # or env CROWNEST_API_KEY
        base_url="https://api.crownest.dev",
        timeout=660,                 # seconds
    )

    All constructor arguments are optional; CrowNest() works on its own when CROWNEST_API_KEY is set.

Walk through the lifecycle

Each step below builds on the previous one inside the same with block.

  1. Create a sandbox. create returns a SandboxHandle — the sandbox resource with .id, dict-style and attribute access, .to_dict(), a .kill() method, and bound sub-clients (.commands, .files, .artifacts, .previews), so you don't repeat the sandbox ID on every call.

    with CrowNest() as client:
        sandbox = client.sandboxes.create(template="python")

    Templates available today are base, node, python, and python-node. You can also pass ttl_ms to request a lifetime and metadata to attach small string labels — see Sandboxes. Idempotency keys are generated for you automatically.

  2. Run a command. run waits for the process to exit and returns the full command record, including exitCode, stdout, and stderr.

    result = sandbox.commands.run("python -c 'print(40 + 2)'")
    print(result["exitCode"], result["stdout"])

    [!IMPORTANT] A non-zero exit code does not raise. The command completed; your code inside it failed. Always check result["exitCode"] when the outcome matters. CrowNestApiError is reserved for API-level failures.

  3. Write and read a file. All file paths are inside /workspace, the sandbox's working filesystem area.

    sandbox.files.write("notes.txt", "hello from crownest")
    content = sandbox.files.read("notes.txt")
    print(content)

    [!NOTE] File APIs are confined to /workspace. Paths that resolve outside it, including through symlinks, are rejected with the path_outside_workspace error code.

  4. Export an artifact. The workspace disappears with the sandbox, so copy anything you want to keep to durable storage with an explicit export.

    artifact = sandbox.artifacts.create("notes.txt")
    print(artifact["id"])

    You can download it later — even after the sandbox is gone — with client.artifacts.download(artifact["id"]), which returns bytes.

  5. Expose a preview. When your sandbox hosts an HTTP service, create a preview to get an authenticated URL like https://p-a1b2c3.preview.crownest.dev.

    sandbox.commands.start("python -m http.server 8000")
    preview = sandbox.previews.create(port=8000)
    print(preview["url"])

    start launches the server without waiting for it to exit. Previews require authentication in v1; see Previews.

  6. Kill the sandbox. This stops billing and releases the environment. Sandboxes also expire automatically at their TTL.

    sandbox.kill()

Handle errors

API failures raise crownest.CrowNestApiError, which carries the HTTP status, a stable machine-readable code, and optional details. str(e) gives a readable summary.

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.details)
        print(str(e))

See the error reference for the full list of codes.

Complete script

The end-to-end version of the steps above.

main.py
from crownest import CrowNest

with CrowNest() as client:
    sandbox = client.sandboxes.create(template="python")

    result = sandbox.commands.run("python -c 'print(40 + 2)'")
    print(result["exitCode"], result["stdout"])

    sandbox.files.write("notes.txt", "hello from crownest")
    content = sandbox.files.read("notes.txt")
    print(content)

    artifact = sandbox.artifacts.create("notes.txt")
    print("artifact:", artifact["id"])

    sandbox.commands.start("python -m http.server 8000")
    preview = sandbox.previews.create(port=8000)
    print("preview:", preview["url"])

    sandbox.kill()

Use the async client

AsyncCrowNest exposes the same surface with await on every call, and works as an async context manager.

main_async.py
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())

Next steps

On this page