DOCSThe TypeScript SDK
DOCS

The TypeScript SDK

@caascade/sdk is the caas.sh API (The 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

shell
npm install @caascade/sdk

Connect it to your workspace

The SDK sends an API key from your workspace. Create one in the console under Settings, as The API describes, and set it as CAAS_API_KEY:

shell
export CAAS_API_KEY=<api-key>

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

typescript
import { Caas } from "@caascade/sdk";

const caas = new Caas();

Every request acts in the key's workspace.

Run a sandbox

typescript
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:

typescript
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

typescript
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.

typescript
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

typescript
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 and MCP servers 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:

typescript
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:

typescript
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:

typescript
// 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);