Skip to content

SDK Usage

The Runra SDK (@runra/sdk) is the primary way to interact with Runra Sandbox from your application. This guide covers all SDK methods with examples and patterns.

import { Runra } from "@runra/sdk";
const runra = new Runra({
sandbox: {
provider: "runra-sandbox",
apiKey: process.env.RUNRA_API_KEY,
apiUrl: "https://api.box.runra.dev",
defaults: {
image: "node:22",
resources: { cpu: 2, memoryMb: 4096 },
timeoutMs: 300_000,
},
},
observability: {
provider: "axiom",
token: process.env.AXIOM_TOKEN,
dataset: "runra-events",
},
});

Create a new sandbox:

const sandbox = await runra.sandboxes.create({
image: "node:22",
resources: {
cpu: 2,
memoryMb: 4096,
diskMb: 10240,
},
env: {
NODE_ENV: "production",
},
timeoutMs: 300_000,
idleTimeoutMs: 120_000,
metadata: {
userId: "user_123",
},
network: {
egress: {
allow: ["github.com", "registry.npmjs.org"],
default: "deny",
},
},
});
console.log(sandbox.id); // "sb_x7k2m9p4q1"
console.log(sandbox.state); // "running"

Retrieve a sandbox by ID:

const sandbox = await runra.sandboxes.get("sb_x7k2m9p4q1");
console.log(sandbox.state); // "running"
console.log(sandbox.createdAt); // "2026-06-15T10:30:00Z"

List sandboxes with filters:

const sandboxes = await runra.sandboxes.list({
state: "running",
limit: 20,
offset: 0,
metadata: {
userId: "user_123",
},
});
for (const sb of sandboxes) {
console.log(`${sb.id}: ${sb.state}`);
}

Pause a running sandbox, preserving its state:

await sandbox.pause();
console.log(sandbox.state); // "paused"

Resume a paused sandbox:

const resumed = await runra.sandboxes.resume("sb_x7k2m9p4q1");
console.log(resumed.state); // "running"

Permanently destroy a sandbox:

await sandbox.terminate();
console.log(sandbox.state); // "terminated"

Run a shell command inside the sandbox:

const result = await sandbox.exec("ls -la");
console.log(result.stdout);
console.log(result.stderr);
console.log(result.exitCode); // 0
console.log(result.durationMs); // 45

With options:

const result = await sandbox.exec("npm run build", {
cwd: "/workspace/my-app",
env: {
CI: "true",
NODE_ENV: "production",
},
timeoutMs: 60_000,
onStdout: (line) => console.log(line),
onStderr: (line) => console.error(line),
});
interface ExecutionResult {
stdout: string;
stderr: string;
exitCode: number;
durationMs: number;
signal: string | null;
}

Write content to a file:

await sandbox.files.write("/workspace/app.ts", `
import express from "express";
const app = express();
app.get("/", (_, res) => res.send("Hello!"));
app.listen(3000);
`);

Read a file’s contents:

const content = await sandbox.files.read("/workspace/app.ts");
console.log(content);

List files in a directory:

const entries = await sandbox.files.list("/workspace");
for (const entry of entries) {
console.log(`${entry.isDirectory ? "D" : "F"} ${entry.name} (${entry.sizeBytes} bytes)`);
}

Check if a file or directory exists:

const exists = await sandbox.files.exists("/workspace/app.ts");
if (!exists) {
await sandbox.files.write("/workspace/app.ts", "// New file");
}

Delete a file or directory:

await sandbox.files.delete("/workspace/temp.log");
await sandbox.files.delete("/workspace/old-build", { recursive: true });

Create a directory:

await sandbox.files.mkdir("/workspace/src/components");
await sandbox.files.mkdir("/workspace/src/utils", { recursive: true });

Expose a port for public access:

const port = await sandbox.exposePort(3000);
console.log(port.url); // "https://3000-ab12cd.box.runra.dev"
console.log(port.port); // 3000

List all exposed ports:

const ports = await sandbox.getPorts();
for (const port of ports) {
console.log(`${port.port} -> ${port.url}`);
}

Close an exposed port:

await sandbox.closePort(3000);

Register event handlers for sandbox lifecycle events:

sandbox.on("started", () => {
console.log("Sandbox is ready");
});
sandbox.on("paused", (event) => {
console.log(`Sandbox paused after ${event.runtimeDurationMs}ms`);
});
sandbox.on("resumed", (event) => {
console.log(`Sandbox resumed after ${event.pausedDurationMs}ms pause`);
});
sandbox.on("terminated", (event) => {
console.log(`Sandbox terminated. Reason: ${event.reason}`);
});
sandbox.on("error", (event) => {
console.error(`Sandbox error: ${event.error.message}`);
});

Register handlers for detailed observability events:

runra.on("exec.completed", (event) => {
if (event.data.exitCode !== 0) {
console.error(`Command failed: ${event.data.command}`);
}
});
runra.on("file.created", (event) => {
console.log(`File created: ${event.data.path}`);
});
runra.on("port.exposed", (event) => {
console.log(`Port exposed: ${event.data.port} -> ${event.data.url}`);
});
runra.on("cost.runtime", (event) => {
trackers.addCost(event.sandboxId, event.data.cost);
});
interface Sandbox {
id: string;
state: "creating" | "running" | "paused" | "terminated";
image: string;
resources: ResourceLimits;
env: Record<string, string>;
metadata: Record<string, string>;
createdAt: string;
startedAt: string | null;
pausedAt: string | null;
terminatedAt: string | null;
exec(command: string, options?: ExecOptions): Promise<ExecutionResult>;
files: FileOperations;
exposePort(port: number): Promise<Port>;
getPorts(): Promise<Port[]>;
closePort(port: number): Promise<void>;
pause(): Promise<void>;
terminate(): Promise<void>;
on(event: string, handler: (event: any) => void): void;
}
interface SandboxConfig {
image: string;
resources?: {
cpu?: number; // 2, 4, or 8
memoryMb?: number; // 2048–16384
diskMb?: number; // 5120–51200
};
env?: Record<string, string>;
workdir?: string;
timeoutMs?: number;
idleTimeoutMs?: number;
metadata?: Record<string, string>;
network?: {
egress?: {
allow?: string[];
default?: "allow" | "deny";
};
ingress?: {
allowPorts?: number[];
};
};
}
import { RunraError } from "@runra/sdk";
try {
await sandbox.exec("invalid-command");
} catch (error) {
if (error instanceof RunraError) {
console.error(`Error code: ${error.code}`);
console.error(`Error message: ${error.message}`);
switch (error.code) {
case "SANDBOX_NOT_FOUND":
// Sandbox doesn't exist or was terminated
break;
case "SANDBOX_TIMEOUT":
// Sandbox exceeded its timeout
break;
case "RATE_LIMITED":
// Too many requests
break;
case "RESOURCE_EXHAUSTED":
// No capacity available
break;
case "INVALID_CONFIG":
// Bad configuration
break;
}
}
}
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries = 3,
baseDelayMs = 1000
): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxRetries - 1) throw error;
if (error instanceof RunraError && error.retryable) {
const delay = baseDelayMs * Math.pow(2, attempt);
console.warn(`Retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`);
await new Promise((r) => setTimeout(r, delay));
} else {
throw error;
}
}
}
throw new Error("Unreachable");
}
// Usage
const result = await withRetry(() => sandbox.exec("npm install"));
let sandbox: Sandbox | null = null;
try {
sandbox = await runra.sandboxes.create({
image: "node:22",
resources: { cpu: 2, memoryMb: 4096 },
});
await sandbox.exec("npm install && npm test");
console.log("Tests passed!");
} catch (error) {
console.error("Workflow failed:", error);
} finally {
if (sandbox) {
await sandbox.terminate();
console.log("Sandbox cleaned up");
}
}
const configs = [
{ image: "node:22", resources: { cpu: 2, memoryMb: 4096 } },
{ image: "python:3.12", resources: { cpu: 2, memoryMb: 4096 } },
{ image: "node:22", resources: { cpu: 4, memoryMb: 8192 } },
];
const sandboxes = await Promise.all(
configs.map((c) => runra.sandboxes.create(c))
);
// Run work in parallel
const results = await Promise.all(
sandboxes.map((sb, i) => sb.exec(`echo "Sandbox ${i} ready"`))
);
// Start multiple background processes
await sandbox.exec("node server.js &");
await sandbox.exec("node worker.js &");
await sandbox.exec("node scheduler.js &");
// Check all are running
const ps = await sandbox.exec("ps aux | grep node");
console.log(ps.stdout);
class SandboxPool {
private available: Sandbox[] = [];
private inUse = new Set<string>();
private waitQueue: Array<(sb: Sandbox) => void> = [];
constructor(
private runra: Runra,
private poolSize: number,
private config: SandboxConfig
) {}
async initialize() {
this.available = await Promise.all(
Array.from({ length: this.poolSize }, () =>
this.runra.sandboxes.create(this.config)
)
);
}
async acquire(): Promise<Sandbox> {
if (this.available.length > 0) {
const sb = this.available.pop()!;
this.inUse.add(sb.id);
return sb;
}
// Wait for a sandbox to become available
return new Promise((resolve) => {
this.waitQueue.push(resolve);
});
}
async release(sandbox: Sandbox) {
this.inUse.delete(sandbox.id);
await sandbox.exec("rm -rf /workspace/*");
if (this.waitQueue.length > 0) {
const resolve = this.waitQueue.shift()!;
resolve(sandbox);
} else {
this.available.push(sandbox);
}
}
async drain() {
await Promise.all(
[...this.available, ...Array.from(this.inUse).map((id) =>
this.runra.sandboxes.get(id)
)].map((sb) => sb.terminate?.())
);
}
}
// Usage
const pool = new SandboxPool(runra, 10, {
image: "node:22",
resources: { cpu: 2, memoryMb: 4096 },
});
await pool.initialize();
// In your request handler
const sandbox = await pool.acquire();
try {
const result = await sandbox.exec("npm test");
return result;
} finally {
await pool.release(sandbox);
}

Limit the number of concurrent sandbox operations:

async function withConcurrencyLimit<T>(
tasks: (() => Promise<T>)[],
limit: number
): Promise<T[]> {
const results: T[] = [];
const executing: Promise<void>[] = [];
for (const task of tasks) {
const p = task().then((result) => {
results.push(result);
});
executing.push(p);
if (executing.length >= limit) {
await Promise.race(executing);
executing.splice(
executing.findIndex((p) =>
p.then(() => true).catch(() => false)
),
1
);
}
}
await Promise.all(executing);
return results;
}
// Usage: Create 50 sandboxes, max 10 at a time
const tasks = Array.from({ length: 50 }, (_, i) => async () => {
const sb = await runra.sandboxes.create({
image: "node:22",
resources: { cpu: 2, memoryMb: 4096 },
metadata: { index: String(i) },
});
return sb;
});
const sandboxes = await withConcurrencyLimit(tasks, 10);