# TypeScript SDK quickstart (/docs/quickstart/sdk)



Walk the full sandbox lifecycle with the TypeScript SDK: create a sandbox,
run a command, write and read a file, export an artifact, expose a preview,
and clean up. The result is a complete script you can drop into your own
project.

For archive-based repo/test work, use `client.workspaceRuns` instead of
creating a sandbox directly. Workspace Runs upload an existing `.tar.gz` or
`.tgz`, stream execution events, and persist durable evidence:

```ts
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";

const archive = await readFile("repo.tgz");
const sha256 = createHash("sha256").update(archive).digest("hex");

const run = await client.workspaceRuns.runArchive({
  archive: {
    body: archive,
    sha256,
    sizeBytes: archive.byteLength,
  },
  template: "python-node",
  command: "pnpm test",
});
```

See [Workspace Runs](/docs/sdk/workspace-runs) for streaming events,
staged transfers, cancelation, and evidence reads.

## Prerequisites [#prerequisites]

* Node.js 18 or later.
* A CrowNest API key, created in the dashboard at
  [https://crownest.dev](https://crownest.dev). See
  [API keys](/docs/concepts/api-keys) for scopes and presets.

<Callout type="warn" title="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.
</Callout>

## Set up [#set-up]

Follow these steps to install the SDK and authenticate.

1. Install the package.

   ```bash title="Terminal"
   pnpm add @crownest/sdk
   ```

   npm and yarn work too: `npm install @crownest/sdk` or
   `yarn add @crownest/sdk`.

2. Export your API key. The client reads `CROWNEST_API_KEY` from the
   environment when you don't pass `apiKey` explicitly; if both are missing,
   creating the client throws.

   ```bash title="Terminal"
   export CROWNEST_API_KEY="cn_live_..."
   ```

3. Create a client.

   ```ts title="main.ts"
   import { createCrowNestClient } from "@crownest/sdk";

   const client = createCrowNestClient();
   ```

   You can also pass options: `createCrowNestClient({ apiKey, baseUrl,
   fetch })`. The `baseUrl` defaults to `https://api.crownest.dev`.

## Walk through the lifecycle [#walk-through-the-lifecycle]

Each step below builds on the previous one inside the same script.

1. Create a sandbox. `create` returns a `SandboxHandle` — the sandbox
   resource plus bound helpers (`sandbox.commands`, `sandbox.code`, `sandbox.files`,
   `sandbox.artifacts`, `sandbox.previews`, and `sandbox.kill()`), so you
   don't repeat the sandbox ID on every call.

   ```ts
   const sandbox = await client.sandboxes.create({ template: "python-node" });
   ```

   `python-node` is the default broad repository runner. Use `node` for a lean
   JavaScript and TypeScript environment. You can also set `ttlMs` to request
   a lifetime and `metadata` to attach small string labels — see
   [Sandboxes](/docs/concepts/sandboxes).

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

   ```ts
   const result = await sandbox.commands.run("python3 -c 'print(40 + 2)'");
   console.log(result.exitCode, result.stdout);
   ```

   <Callout type="warn" title="Important">
     A non-zero exit code does not throw. The command completed; your code inside it
     failed. Always check `result.exitCode` when the outcome matters. `CrowNestApiError`
     is reserved for API-level failures.
   </Callout>

   **Stream output as it happens.** Use callbacks when you want live stdout
   and stderr while still getting the final Command record from `run`.

   ```ts
   await sandbox.commands.run(
     `python3 -c "import time; [print('step', i, flush=True) or time.sleep(0.2) for i in range(3)]"`,
     {
       onStdout: (chunk) => process.stdout.write(chunk),
       onStderr: (chunk) => process.stderr.write(chunk),
     },
   );
   ```

   **Run code, not just commands.** Use Code Runs for notebook-style
   snippets and rich interpreter outputs. See [Code](/docs/sdk/code) for
   stateful Code Contexts, streaming Code Run events, and Artifact
   promotion.

   ```ts
   const codeResult = await sandbox.code.run({ code: "print(40 + 2)" });
   console.log(codeResult.stdout);
   ```

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

   ```ts
   await sandbox.files.write("notes.txt", "hello from crownest");
   const content = await sandbox.files.read("notes.txt");
   console.log(content);
   ```

   <Callout type="info" title="Note">
     File APIs are confined to `/workspace`. Paths that resolve outside it through `..`
     segments are rejected with the `path_outside_workspace` error code.
   </Callout>

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

   ```ts
   const artifact = await sandbox.artifacts.create({ path: "notes.txt" });
   console.log(artifact.id);
   ```

   You can download it later — even after the sandbox is gone — with
   `client.artifacts.download(artifact.id)`, which returns a `Uint8Array`.

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

   ```ts
   await sandbox.commands.start("python3 -m http.server 8000");
   const { preview } = await sandbox.previews.create({ port: 8000 });
   console.log(preview.url);
   ```

   `start` launches the server without waiting for it to exit. Previews
   require authentication in v1. Use `authMode: "token"` when you need a
   browser-openable link; the one-time `previewToken` is returned only from
   the create response. See [Previews](/docs/concepts/previews).

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

   ```ts
   await sandbox.kill();
   ```

## Handle errors [#handle-errors]

API failures throw `CrowNestApiError`, which carries the HTTP status, a
stable machine-readable code, a message, and optional details.

```ts
import { createCrowNestClient, CrowNestApiError } from "@crownest/sdk";

const client = createCrowNestClient();

try {
  await client.sandboxes.get("sbx_does_not_exist");
} catch (error) {
  if (error instanceof CrowNestApiError) {
    console.error(error.status, error.code, error.message, error.details);
  } else {
    throw error;
  }
}
```

See the [error reference](/docs/api/errors) for the full list of codes.

## Complete script [#complete-script]

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

```ts title="main.ts"
import { createCrowNestClient } from "@crownest/sdk";

const client = createCrowNestClient();

const sandbox = await client.sandboxes.create({ template: "python-node" });

const result = await sandbox.commands.run("python3 -c 'print(40 + 2)'");
console.log(result.exitCode, result.stdout);

await sandbox.files.write("notes.txt", "hello from crownest");
const content = await sandbox.files.read("notes.txt");
console.log(content);

const artifact = await sandbox.artifacts.create({ path: "notes.txt" });
console.log("artifact:", artifact.id);

await sandbox.commands.start("python3 -m http.server 8000");
const { preview } = await sandbox.previews.create({ port: 8000 });
console.log("preview:", preview.url);

await sandbox.kill();
```

## Next steps [#next-steps]

* Learn how lifetimes, templates, and statuses work in
  [Sandboxes](/docs/concepts/sandboxes).
* Stream logs and collect outputs with [Commands](/docs/concepts/commands).
* Run archive-based workflows with [Workspace Runs](/docs/sdk/workspace-runs).
* Browse every method in the [TypeScript SDK reference](/docs/sdk).
* Prefer the terminal? Try the [CLI quickstart](/docs/quickstart/cli).
