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.
Client initialization
Section titled “Client initialization”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", },});Sandbox management
Section titled “Sandbox management”create()
Section titled “create()”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()
Section titled “list()”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()
Section titled “pause()”Pause a running sandbox, preserving its state:
await sandbox.pause();console.log(sandbox.state); // "paused"resume()
Section titled “resume()”Resume a paused sandbox:
const resumed = await runra.sandboxes.resume("sb_x7k2m9p4q1");console.log(resumed.state); // "running"terminate()
Section titled “terminate()”Permanently destroy a sandbox:
await sandbox.terminate();console.log(sandbox.state); // "terminated"Code execution
Section titled “Code execution”exec()
Section titled “exec()”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); // 0console.log(result.durationMs); // 45With 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),});ExecutionResult type
Section titled “ExecutionResult type”interface ExecutionResult { stdout: string; stderr: string; exitCode: number; durationMs: number; signal: string | null;}File operations
Section titled “File operations”files.write()
Section titled “files.write()”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);`);files.read()
Section titled “files.read()”Read a file’s contents:
const content = await sandbox.files.read("/workspace/app.ts");console.log(content);files.list()
Section titled “files.list()”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)`);}files.exists()
Section titled “files.exists()”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");}files.delete()
Section titled “files.delete()”Delete a file or directory:
await sandbox.files.delete("/workspace/temp.log");await sandbox.files.delete("/workspace/old-build", { recursive: true });files.mkdir()
Section titled “files.mkdir()”Create a directory:
await sandbox.files.mkdir("/workspace/src/components");await sandbox.files.mkdir("/workspace/src/utils", { recursive: true });Port management
Section titled “Port management”exposePort()
Section titled “exposePort()”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); // 3000getPorts()
Section titled “getPorts()”List all exposed ports:
const ports = await sandbox.getPorts();for (const port of ports) { console.log(`${port.port} -> ${port.url}`);}closePort()
Section titled “closePort()”Close an exposed port:
await sandbox.closePort(3000);Lifecycle events
Section titled “Lifecycle events”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}`);});Observability events
Section titled “Observability events”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);});TypeScript types
Section titled “TypeScript types”Sandbox
Section titled “Sandbox”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;}SandboxConfig
Section titled “SandboxConfig”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[]; }; };}Error handling patterns
Section titled “Error handling patterns”Error types
Section titled “Error types”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; } }}Retry logic
Section titled “Retry logic”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");}
// Usageconst result = await withRetry(() => sandbox.exec("npm install"));Graceful cleanup
Section titled “Graceful cleanup”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"); }}Concurrent sandbox management
Section titled “Concurrent sandbox management”Parallel sandbox creation
Section titled “Parallel sandbox creation”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 parallelconst results = await Promise.all( sandboxes.map((sb, i) => sb.exec(`echo "Sandbox ${i} ready"`)));Concurrent execution in one sandbox
Section titled “Concurrent execution in one sandbox”// Start multiple background processesawait sandbox.exec("node server.js &");await sandbox.exec("node worker.js &");await sandbox.exec("node scheduler.js &");
// Check all are runningconst ps = await sandbox.exec("ps aux | grep node");console.log(ps.stdout);Sandbox pool pattern
Section titled “Sandbox pool pattern”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?.()) ); }}
// Usageconst pool = new SandboxPool(runra, 10, { image: "node:22", resources: { cpu: 2, memoryMb: 4096 },});await pool.initialize();
// In your request handlerconst sandbox = await pool.acquire();try { const result = await sandbox.exec("npm test"); return result;} finally { await pool.release(sandbox);}Bounded concurrency
Section titled “Bounded concurrency”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 timeconst 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);Next steps
Section titled “Next steps”- Running Code — detailed exec() patterns
- Exposing Ports — port exposure API
- Observability — event handlers and exporters
- Setup Guide — production configuration