# The TypeScript SDK

`@caascade/sdk` is the caas.sh API ([The API](/docs/api)) as typed TypeScript methods: sandboxes and their execs, files, logs, snapshots, access tokens and terminals; MCP servers; workflow runs; providers; credentials; and your workspace's settings, audit log and billing. It has no runtime dependencies and runs on Node 22 or later, Deno, Bun and Cloudflare Workers.

## Install the SDK

```sh
npm install @caascade/sdk
```

## Connect it to your workspace

The SDK sends an API key from your workspace. Create one in the [console](https://console.caas.sh/) under **Settings**, as [The API](/docs/api) describes, and set it as `CAAS_API_KEY`:

```sh
export CAAS_API_KEY=<api-key>
```

A client reads `CAAS_API_KEY` when you don't pass `apiKey`:

```ts
import { Caas } from "@caascade/sdk";

const caas = new Caas();
```

Every request acts in the key's workspace.

## Run a sandbox

```ts
const sandbox = await caas.sandboxes.create({ name: "build-agent", size: "m", egress: ["registry.npmjs.org"] });

const result = await caas.sandboxes.exec(sandbox.id, { command: ["node", "--version"] });
console.log(result.exitCode, result.stdout);

await caas.sandboxes.files.write(sandbox.id, "/workspace/hello.txt", "hello\n");
const text = await caas.sandboxes.files.readText(sandbox.id, "/workspace/hello.txt");

await caas.sandboxes.stop(sandbox.id);
await caas.sandboxes.delete(sandbox.id);
```

Every method that names a sandbox takes its ID or its name. An exec's command is an array, which runs as it is, or a string, which runs through `/bin/sh -c`. A command that exits nonzero isn't an error: read its `exitCode`.

### Long-running commands

Pass `detach: true` to get a handle back at once, and wait for the command or cancel it later:

```ts
const job = await caas.sandboxes.exec("build-agent", { command: ["make", "release"], detach: true, timeoutSeconds: 86_400 });
const done = await job.wait();
```

A detached command's output arrives when it ends.

### Files, logs and snapshots

```ts
await caas.sandboxes.files.write("build-agent", "/workspace", tarGz, { extract: true });
const listing = await caas.sandboxes.files.list("build-agent", "/workspace");

for await (const entry of caas.sandboxes.followLogs("build-agent")) console.log(entry.message);

await caas.sandboxes.snapshots.create("build-agent", { name: "before-upgrade" });
await caas.sandboxes.stop("build-agent");
await caas.sandboxes.snapshots.restore("build-agent", "before-upgrade");
```

## Open a terminal

A terminal is a shell in the sandbox over a WebSocket. Its session outlives a dropped connection for ten minutes, and the SDK attaches again with a fresh ticket, replaying the session's recent output.

```ts
const terminal = await caas.sandboxes.terminal.open("build-agent", { cols: 120, rows: 40 });
terminal.onData((bytes) => process.stdout.write(bytes));
terminal.onExit(({ code }) => console.log(`shell exited ${code}`));

terminal.write("ls -la\r");
terminal.resize(100, 30);
```

`close()` drops the connection and leaves the session running, so `resume()` can attach again; `end()` ends the session.

On Node 20, pass a WebSocket for terminals: `new Caas({ WebSocket: (await import("ws")).WebSocket })`.

## Workflow runs and MCP servers

```ts
const run = await caas.runs.create({ file: workflowYaml, inputs: { repo: "github.com/acme/app" } });
const finished = await caas.runs.wait(run.id);

const server = await caas.mcpServers.create({
  name: "search",
  sandbox: { size: "s", egress: ["api.search.example"] },
  transport: { stdio: { command: ["node", "server.js"] } },
  source: { git: { url: "https://github.com/acme/search-mcp" } },
});
```

[Workflows](/docs/workflows) and [MCP servers](/docs/mcp) explain what each field does.

## Lists

Lists of sandboxes, MCP servers, runs and audit log entries come in pages. Await a list for one page, or iterate it for every item:

```ts
const page = await caas.sandboxes.list({ limit: 20 });

for await (const sandbox of caas.sandboxes.list()) console.log(sandbox.name);
```

## Errors and retries

Every failure is a `CaasError` with the HTTP `status`, the API's `code`, a `message` and the `requestId`, which you can quote when reporting a problem. Subclasses name the status, such as `NotFoundError`, `ConflictError` and `PaymentRequiredError`:

```ts
import { ConflictError } from "@caascade/sdk";

try {
  await caas.sandboxes.create({ name: "build-agent" });
} catch (error) {
  if (error instanceof ConflictError && error.code === "name_taken") console.log("pick another name");
  else throw error;
}
```

The SDK retries a request that is safe to send again, twice by default, after a `429`, a `5xx` or no answer. Creating a sandbox, running an exec, creating an MCP server and starting a run each send an `Idempotency-Key`, so a retry never creates anything twice.

## Use it from a browser

The API doesn't answer browsers' cross-origin requests, so keep the API key on your server. For a terminal in a browser, your server creates the session and hands the browser its ticket, which is good for one connection within 60 seconds:

```ts
// On your server
const ticket = await caas.sandboxes.terminal.create("build-agent", { cols: 120, rows: 40 });

// In the browser
import { connectTerminal } from "@caascade/sdk";
const terminal = await connectTerminal(ticket);
```
