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.
Run 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. The end-to-end script is at the bottom of this page.
For archive-based repo/test work, use client.workspace_runs instead of
creating a sandbox directly. Workspace Runs upload an existing .tar.gz or
.tgz, run a command, stream execution events, and persist durable evidence:
import hashlib
from pathlib import Path
from crownest import CrowNest
archive = Path("repo.tgz").read_bytes()
with CrowNest() as client:
run = client.workspace_runs.run_archive(
archive,
sha256=hashlib.sha256(archive).hexdigest(),
size_bytes=len(archive),
template="python-node",
command="pytest -q",
)See Workspace Runs for the full SDK lifecycle.
Prerequisites
- 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
-
Install the package from PyPI.
Terminal pip install crownestWith uv:
uv add crownest. -
Export your bearer credential. The client reads
CROWNEST_BEARER_TOKENfrom the environment, withCROWNEST_API_KEYstill supported for developer API keys.Terminal export CROWNEST_BEARER_TOKEN="cn_live_..." -
Create a client. The sync client supports use as a context manager, which closes the underlying connection for you; outside a
withblock, call.close()when you're done.main.py from crownest import CrowNest client = CrowNest( credential="cn_live_...", # or env CROWNEST_BEARER_TOKEN base_url="https://api.crownest.dev", timeout=660, # seconds )All constructor arguments are optional;
CrowNest()works on its own whenCROWNEST_BEARER_TOKENis set.
Walk through the lifecycle
Each step below builds on the previous one inside the same with block.
-
Create a sandbox.
createreturns aSandboxHandle— the sandbox resource with.id, dict-style and attribute access,.to_dict(), a.kill()method, and bound sub-clients (.commands,.files,.artifacts,.previews,.code), so you don't repeat the sandbox ID on every call.with CrowNest() as client: sandbox = client.sandboxes.create(template="python-node")python-nodeis the default broad repository runner. Usenodefor a lean JavaScript and TypeScript environment. You can also passttl_msto request a lifetime andmetadatato attach small string labels — see Sandboxes. Idempotency keys are generated for you automatically. -
Run a command.
runwaits for the process to exit and returns the full command record, includingexitCode,stdout, andstderr.result = sandbox.commands.run("python3 -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.CrowNestApiErroris reserved for API-level failures.Stream output as it happens. Use callbacks when you want live stdout and stderr while still getting the final Command record from
run.import sys sandbox.commands.run( "python3 -c \"import time; [print('step', i, flush=True) or time.sleep(0.2) for i in range(3)]\"", on_stdout=lambda chunk: print(chunk, end=""), on_stderr=lambda chunk: print(chunk, end="", file=sys.stderr), )Run code, not just commands. Use Code Runs for notebook-style snippets and rich interpreter outputs. The Python SDK exposes the same Sandbox-bound Code helpers with snake_case option names.
code_result = sandbox.code.run("print(40 + 2)") print(code_result["stdout"]) -
Write and read a file. All file paths are inside
/workspace.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 through..segments are rejected with thepath_outside_workspaceerror code. -
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(path="notes.txt") print(artifact["id"])You can download it later — even after the sandbox is gone — with
client.artifacts.download(artifact["id"]), which returnsbytes. -
Expose a preview. When your sandbox hosts an HTTP service, create a preview to get an authenticated URL like
https://p-a1b2c3.crownest.dev.sandbox.commands.start("python3 -m http.server 8000") preview_create = sandbox.previews.create(port=8000) preview = preview_create["preview"] print(preview["url"])startlaunches the server without waiting for it to exit. Previews require authentication in v1. Useauth_mode="token"when you need a browser-openable link; the one-timepreviewTokenis returned only from the create response. See Previews. -
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.
from crownest import CrowNest
with CrowNest() as client:
sandbox = client.sandboxes.create(template="python-node")
result = sandbox.commands.run("python3 -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(path="notes.txt")
print("artifact:", artifact["id"])
sandbox.commands.start("python3 -m http.server 8000")
preview_create = sandbox.previews.create(port=8000)
preview = preview_create["preview"]
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.
import asyncio
from crownest import AsyncCrowNest
async def main() -> None:
async with AsyncCrowNest() as client:
sandbox = await client.sandboxes.create(template="python-node")
result = await sandbox.commands.run("python3 -c 'print(40 + 2)'")
print(result["exitCode"], result["stdout"])
await sandbox.kill()
asyncio.run(main())Next steps
- Learn how lifetimes, templates, and statuses work in Sandboxes.
- Stream logs and collect outputs with Commands.
- Browse every method in the Python SDK reference.
- Prefer the terminal? Try the CLI quickstart.
TypeScript SDK quickstart
Create a sandbox, run a command, work with files, export an artifact, and expose a preview with the @crownest/sdk package.
CLI quickstart
Drive CrowNest sandboxes from your terminal with the crownest CLI — create a sandbox, run commands, move files, export artifacts, and stream logs.