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
npm install @caascade/sdkConnect 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:
export CAAS_API_KEY=<api-key>A client reads CAAS_API_KEY when you don't pass apiKey:
import { Caas } from "@caascade/sdk";
const caas = new Caas();Every request acts in the key's workspace.
Run a sandbox
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:
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
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
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:
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.
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:
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:
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:
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.