Docs / guides / sdk

SDK Usage

Complete SDK reference for Runra Sandbox — all methods with TypeScript examples, error handling patterns, and concurrent sandbox management

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

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

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"

get()

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()

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()

Pause a running sandbox, preserving its state:

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

resume()

Resume a paused sandbox:

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

terminate()

Permanently destroy a sandbox:

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

Code execution

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);  // 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),
});

ExecutionResult type

interface ExecutionResult {
  stdout: string;
  stderr: string;
  exitCode: number;
  durationMs: number;
  signal: string | null;
}

File operations

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()

Read a file's contents:

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

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()

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()

Delete a file or directory:

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

files.mkdir()

Create a directory:

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

Port management

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

getPorts()

List all exposed ports:

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

closePort()

Close an exposed port:

await sandbox.closePort(3000);

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

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

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

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

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

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"));

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

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 parallel
const results = await Promise.all(
  sandboxes.map((sb, i) => sb.exec(`echo "Sandbox ${i} ready"`))
);

Concurrent execution in one sandbox

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

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?.())
    );
  }
}

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

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

Next steps