DOCSThe TypeScript SDK
DOCS

The TypeScript SDK

@caascade/sdk is the isolance API (The API) as typed TypeScript methods: sandboxes and their execs, files, logs, snapshots and access tokens; MCP servers; providers; connectors and their workspace accounts; your users' connections and sign-ins; 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");

MCP servers

typescript
const server = await caas.mcpServers.create({
  name: "search",
  sandbox: { image: "registry.caas.sh/<tenant-id>/search-mcp@sha256:…", egress: ["api.search.example"] },
  transport: { http: { port: 8080 } },
});

MCP servers explains what each field does.

Your users

caas.users signs your users in and makes their connect links, one user at a time (see Connect each user's account). A sandbox started for a user who hasn't made a sign-in it needs, such as Codex (ChatGPT)'s, answers their code in signIn (see Sign users in with a code), and waitForSignIn waits until they have, calling onCode with each code to show them:

typescript
const sandbox = await caas.sandboxes.create({ user: "user_123" });
if (sandbox.signIn) {
  await caas.users.waitForSignIn("user_123", sandbox.signIn.connector, {
    onCode: (signIn) => showToUser(signIn.code!, signIn.url!),
  });
}

waitForSignIn reads the user's connection again after each answer's pollAfterMs, never on a timer of its own, and calls onCode with each code it hasn't shown yet. It resolves with the connection once the user is signed in, and rejects with a CaasSignInError (its signInStatus, expired, failed or none, and reason) when the code expires or the sign-in fails, or when there's no sign-in to wait for. Pass a signal to stop waiting. caas.sandboxes.start and restart answer signIn as create does.

typescript
const { signIn } = await caas.users.signIn("user_123", "codex"); // the code under way, a new one, or a sign-in again for a user who's signed in
const { connectUrl } = await caas.users.link("user_123", { connector: "linear", returnUrl: "https://app.acme.com/settings" });
const connection = await caas.users.connection("user_123", "linear");
await caas.users.disconnect("user_123", "linear");

caas.users.logins manages a user's logins, their sign-ins to websites, which their sandboxes' browsers sign in with; caas.connectors.account.logins has the same methods for your workspace's logins, but link, its first argument the Logins connector's ID:

typescript
const login = await caas.users.logins.create("user_123", { site: "github.com", username: "ana@acme.com", methods: ["passkey", "typed"], password });
const { connectUrl } = await caas.users.logins.link("user_123", { site: "github.com" }, { returnUrl: "https://app.acme.com/settings" });
await caas.users.logins.session.upload("user_123", login.id, await context.storageState());
const { signedIn } = await caas.users.logins.check("user_123", login.id);

list, get, update and delete read and change logins; passkeys.rename and passkeys.delete a login's passkeys; session.get, session.upload and session.delete its saved sign-in, whose StorageState is Playwright's, so context.storageState() passes straight in (a Browserbase login takes no upload); and live.open, live.done and live.cancel a live view for a person to sign in by hand. A login never carries its password or code back. create sends an Idempotency-Key, its own unless you pass idempotencyKey, so a retry adds the login once.

Lists

Lists of sandboxes, MCP servers, logins 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 and creating an MCP server 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 and call the API from there.