Banata

SDK & API

SDK Reference

Complete reference for the @banata-boxes/sdk TypeScript SDK — browser sessions, code sandboxes, webhooks, and billing.

The @banata-boxes/sdk package is the official TypeScript SDK for Banata Boxes. It provides two main classes: BrowserCloud for browser sessions and BanataSandbox for code sandboxes.


Installation

bash
npm install @banata-boxes/sdk
# or
bun add @banata-boxes/sdk
# or
yarn add @banata-boxes/sdk

BrowserCloud

Manages headless browser sessions with CDP access.

Constructor

typescript
import \{ BrowserCloud \} from "@banata-boxes/sdk";
 
const cloud = new BrowserCloud(\{
  apiKey: "br_live_...",       // Required
  baseUrl: "https://...",      // Default: "https://api.boxes.banata.dev"
  appUrl: "https://boxes.banata.dev", // Optional public app host for viewer URLs
  retry: \{
    maxRetries: 3,             // Default: 3
    baseDelayMs: 500,          // Default: 500
    maxDelayMs: 10000,         // Default: 10,000
  \},
\});

cloud.launch(config)

Creates a session, waits for it to become ready, and returns the CDP URL. This is the recommended way to use the SDK.

typescript
const \{ cdpUrl, sessionId, previewViewerUrl, close \} = await cloud.launch(\{
  weight: "light",
  stealth: true,
  recording: true,
  timeout: 30000,
\});
 
// Use cdpUrl with Puppeteer, Playwright, or any CDP client
const browser = await puppeteer.connect(\{ browserWSEndpoint: cdpUrl \});
 
// Or hand a human the standalone viewer URL
console.log(previewViewerUrl);
 
// Clean up when done
await browser.disconnect();
await close();

Config options:

FieldTypeDefaultDescription
weight"light" | "medium" | "heavy""light"Browser session tier
isolation"shared" | "dedicated""shared"Machine isolation (Pro+)
egressProfilestringAutoOutbound IP profile
stealthbooleanfalseAnti-detection (Builder+)
recordingbooleanfalseMP4 recording (Pro+)
captchabooleanfalseAuto CAPTCHA solving
maxDurationMsnumberPlan limitSession TTL in ms
proxyobject—BYO proxy {protocol, host, port, username?, password?}
profileKeystring—Saved browser profile key
regionstring—Preferred region (e.g. "iad", "lhr")
timeoutnumber30,000Max wait for ready (ms)

Returns: \{ cdpUrl: string, sessionId: string, previewViewerUrl: string \| null, getPreviewViewerUrl: () => Promise<string \| null>, close: () => Promise<void> \}

cloud.createBrowser(config)

Creates a session and returns immediately without waiting for ready.

typescript
const session = await cloud.createBrowser(\{ weight: "medium" \});
// session.id, session.status === "queued"

cloud.getBrowser(sessionId)

Gets the current status of a session.

typescript
const session = await cloud.getBrowser("session-id");
// session.status, session.cdpUrl, session.artifacts

cloud.closeBrowser(sessionId)

Ends a session and triggers cleanup.

typescript
await cloud.closeBrowser("session-id");

cloud.waitForReady(sessionId, timeoutMs)

Polls until the session is ready and returns the CDP URL.

typescript
const cdpUrl = await cloud.waitForReady("session-id", 30000);

BanataSandbox

Manages code sandbox sessions with execution, filesystem, AI agent, and human handoff capabilities.

Constructor

typescript
import \{ BanataSandbox \} from "@banata-boxes/sdk";
 
const sandbox = new BanataSandbox(\{
  apiKey: "br_live_...",       // Required
  baseUrl: "https://...",      // Default: "https://api.boxes.banata.dev"
  appUrl: "https://boxes.banata.dev", // Optional public app host for standalone viewers
  retry: \{
    maxRetries: 3,
    baseDelayMs: 500,
    maxDelayMs: 10000,
  \},
\});

sandbox.launch(config)

Creates a sandbox, waits for it to become ready, and returns a rich session object with bound methods. This is the recommended way to use sandboxes.

typescript
const session = await sandbox.launch(\{
  runtime: "bun",
  size: "standard",
  capabilities: \{
    opencode: \{ enabled: true, defaultAgent: "build", allowPromptApi: true \},
    browser: \{
      mode: "local-chromium",
      streamPreview: true,
      humanInLoop: true,
      viewport: \{ width: 1280, height: 800 \},
    \},
  \},
\});
 
// Execute commands
const result = await session.exec("echo", ["Hello from sandbox!"]);
console.log(result.stdout); // "Hello from sandbox!"
 
// Run code
const code = await session.runCode("console.log(1 + 1)");
console.log(code.stdout); // "2"
 
// Read/write files
await session.fs.write("/workspace/hello.txt", "Hello!");
const content = await session.fs.read("/workspace/hello.txt");
const files = await session.fs.list("/workspace");
 
// AI agent prompt (requires opencode capability)
const response = await session.prompt("Create a simple web server");
 
// Start or resize the live browser preview if needed
await session.startPreview();
await session.resizePreview(\{ width: 1100, height: 720 \});
console.log(session.browserPreviewViewerUrl);
 
// Read OpenCode state or stream events
const state = await session.getOpencodeState(\{ ensureSession: true \});
 
// Create a checkpoint (snapshot)
const checkpoint = await session.checkpoint();
 
// Clean up
await session.kill();

Config options:

FieldTypeDefaultDescription
runtime"bun" | "python" | "base""bun"Runtime environment
size"standard""standard"Standard sandbox worker shape (2 shared vCPUs / 4 GB RAM).
envRecord<string, string>—Environment variables
ephemeralbooleantrueDestroy on kill
maxDurationMsnumberPlan limitSession TTL in ms
regionstring—Preferred region
capabilitiesobject—OpenCode, browser, docs, storage
timeoutnumber30,000Max wait for ready (ms)

Returns: LaunchedSandbox — an object with these bound methods:

MethodDescription
exec(command, args?)Run a shell command
runCode(code)Run code in the sandbox runtime
prompt(text, options?)Send an AI agent prompt (OpenCode)
promptAsync(text, options?)Queue an async OpenCode prompt
getOpencodeState(options?)Get current OpenCode session state
listOpencodeMessages(options?)Read OpenCode message history
streamOpencodeEvents(options?)Stream OpenCode SSE events
checkpoint()Snapshot the sandbox state
fs.read(path)Read a file
fs.write(path, content)Write a file
fs.list(path?)List directory contents
getRuntime()Get full runtime state
getPreview()Get browser preview state
getPreviewViewerUrl()Get a standalone public viewer URL for the browser
getPreviewConnection()Get preview WS/start/navigate/resize URLs
startPreview()Warm and attach the preview backend
navigatePreview(url)Navigate the preview/browser from the SDK
resizePreview(size)Resize the preview/browser viewport
getHandoff()Get human handoff state
setControl(mode, options?)Set browser control mode
takeControl(options?)Take human control of browser
returnControl(options?)Return control to AI
requestHumanHandoff(options)Request a human operator
acceptHumanHandoff(options)Accept a handoff request
completeHumanHandoff(options?)Complete handoff, return to AI
kill()Destroy the sandbox

sandbox.create(config)

Creates a sandbox and returns immediately without waiting for ready.

typescript
const session = await sandbox.create(\{ runtime: "python", size: "standard" \});
// session.id, session.status === "queued"

sandbox.get(id)

Gets the current status of a sandbox session.

typescript
const session = await sandbox.get("sandbox-id");
// session.status, session.terminalUrl, session.opencode, session.browserPreviewViewerUrl, etc.

sandbox.list()

Lists all sandbox sessions for your organization.

typescript
const sessions = await sandbox.list();

sandbox.kill(id)

Destroys a sandbox session.

typescript
await sandbox.kill("sandbox-id");

sandbox.exec(id, command, args?)

Executes a shell command in the sandbox.

typescript
const result = await sandbox.exec("sandbox-id", "ls", ["-la", "/workspace"]);
// result.stdout, result.stderr, result.exitCode, result.durationMs

sandbox.runCode(id, code)

Runs code using the sandbox's configured runtime (bun/python/base).

typescript
const result = await sandbox.runCode("sandbox-id", "print('Hello from Python!')");

sandbox.pause(id) / sandbox.resume(id)

Pause a sandbox to stop billing while preserving state. Resume when ready.

typescript
await sandbox.pause("sandbox-id");
// ... later ...
await sandbox.resume("sandbox-id");

sandbox.fs

File system operations.

typescript
// Read a file
const content = await sandbox.fs.read("sandbox-id", "/workspace/app.ts");
 
// Write a file
await sandbox.fs.write("sandbox-id", "/workspace/app.ts", "console.log('hello')");
 
// List directory
const entries = await sandbox.fs.list("sandbox-id", "/workspace");
// entries: [\{ name: "app.ts", type: "file", size: 22, mtime: ... \}]

sandbox.terminal(id)

Gets the WebSocket terminal URL for interactive access.

typescript
const \{ terminalUrl, token \} = await sandbox.terminal("sandbox-id");
// Connect via WebSocket: new WebSocket(terminalUrl)

sandbox.prompt(id, prompt, options?)

Sends a prompt to the OpenCode AI agent running in the sandbox.

typescript
const response = await sandbox.prompt("sandbox-id", "Create a REST API with Express", \{
  agent: "build",     // "build" or "plan"
  noReply: false,      // Set true for fire-and-forget
\});

sandbox.promptAsync(id, prompt, options?)

Queue a prompt without waiting on the synchronous response path. Useful when paired with streamOpencodeEvents().

typescript
await sandbox.promptAsync("sandbox-id", "Open example.com and summarize the page", \{
  agent: "build",
\});

sandbox.getOpencodeState / sandbox.listOpencodeMessages / sandbox.streamOpencodeEvents

Use these to build your own OpenCode UI or to monitor work from your own app:

typescript
const state = await sandbox.getOpencodeState("sandbox-id", \{
  ensureSession: true,
  agent: "build",
\});
 
const messages = await sandbox.listOpencodeMessages("sandbox-id");
 
for await (const event of sandbox.streamOpencodeEvents("sandbox-id")) \{
  console.log(event.type, event.data);
  if (event.type === "session.idle") break;
\}

sandbox.checkpoint(id)

Creates a checkpoint (snapshot) of the sandbox state.

typescript
const result = await sandbox.checkpoint("sandbox-id");
// result.ok, result.artifacts

sandbox.getArtifacts(id) / sandbox.getArtifactDownloadUrl(id, key)

Access sandbox artifacts (logs, outputs, checkpoints).

typescript
const \{ artifacts \} = await sandbox.getArtifacts("sandbox-id");
const download = await sandbox.getArtifactDownloadUrl("sandbox-id", artifacts.outputLogKey);
// download.url — presigned download URL

Human Handoff

For sandboxes with browser capabilities, you can request a human operator to take control when the AI encounters MFA, CAPTCHA, or ambiguous UI.

typescript
// Request human assistance
const handoff = await sandbox.requestHumanHandoff("sandbox-id", \{
  reason: "mfa",
  message: "MFA code required for login",
  resumePrompt: "Continue after MFA is completed",
  expiresInMs: 300000, // 5 minutes
\});
 
// Human accepts and takes control
const accepted = await sandbox.acceptHumanHandoff("sandbox-id", \{
  controller: "operator@example.com",
  leaseMs: 120000,
\});
 
// Human completes the task
const completed = await sandbox.completeHumanHandoff("sandbox-id", \{
  note: "MFA code entered successfully",
  returnControlTo: "ai",
  runResumePrompt: true, // Runs the resumePrompt from the request
\});

Handoff reasons: "mfa", "captcha", "approval", "login_failed", "ambiguous_ui", "file_download", "custom"


Browser Control

For sandboxes with browser capabilities, control who operates the browser.

typescript
// Set browser to human control
await sandbox.setControl("sandbox-id", "human", \{
  controller: "user@example.com",
  leaseMs: 60000,
\});
 
// Return to AI control
await sandbox.setControl("sandbox-id", "ai");
 
// Shared mode (both AI and human can interact)
await sandbox.setControl("sandbox-id", "shared");

Preview helpers

The SDK exposes two preview surfaces:

  • browserPreviewViewerUrl / getPreviewViewerUrl() for the standalone public viewer page
  • getPreviewConnection() for the raw preview transport if you are embedding your own viewer

If you are embedding the remote browser in your own UI, use the preview helpers to derive the noVNC/websocket endpoints and warm the preview backend:

typescript
const viewerUrl = await sandbox.getPreviewViewerUrl("sandbox-id");
const preview = await sandbox.getPreviewConnection("sandbox-id");
await sandbox.startPreview("sandbox-id");
await sandbox.navigatePreview("sandbox-id", "https://example.com");
await sandbox.resizePreview("sandbox-id", \{ width: 1280, height: 800 \});
 
console.log(viewerUrl);   // Public standalone viewer page
console.log(preview?.wsUrl);

Webhooks

Manage webhook endpoints for real-time event notifications.

typescript
const cloud = new BrowserCloud(\{ apiKey: "br_live_..." \});
 
// Create a webhook
const webhook = await cloud.createWebhook(\{
  url: "https://your-app.com/webhooks/banata",
  eventTypes: ["session.ready", "session.ended", "sandbox.failed"],
\});
// webhook.signingSecret — save this to verify signatures
 
// List webhooks
const webhooks = await cloud.listWebhooks();
 
// Send a test delivery
await cloud.testWebhook(webhook.id);
 
// Delete a webhook
await cloud.deleteWebhook(webhook.id);

Billing

typescript
const cloud = new BrowserCloud(\{ apiKey: "br_live_..." \});
 
// Get current usage
const usage = await cloud.getUsage();
// usage.totalSessions, usage.totalBrowserHours
 
// Get billing info
const billing = await cloud.getBilling();
// billing.plan, billing.currentPeriod
 
// Create a checkout session for plan upgrade
const \{ checkoutUrl \} = await cloud.createCheckout(\{
  plan: "pro",
  successUrl: "https://your-app.com/billing/success",
\});

Error Handling

The SDK throws BanataError for all API errors.

typescript
import \{ BrowserCloud, BanataSandbox, BanataError \} from "@banata-boxes/sdk";
 
try \{
  const session = await sandbox.launch(\{ runtime: "bun" \});
\} catch (error) \{
  if (error instanceof BanataError) \{
    console.log(error.status);        // HTTP status (e.g. 403)
    console.log(error.code);          // "PLAN_FEATURE_UNAVAILABLE"
    console.log(error.requiredPlan);  // "builder"
    console.log(error.currentPlan);   // "free"
    console.log(error.message);       // Human-readable message
  \}
\}
CodeMeaning
PLAN_FEATURE_UNAVAILABLEFeature requires a higher plan
PLAN_LIMIT_EXCEEDEDExceeds your plan's limit
PERMISSION_DENIEDAPI key lacks the required permission
SCHEDULING_FAILEDNo machine could be assigned

Retry behavior

Both BrowserCloud and BanataSandbox automatically retry on:

  • 5xx server errors
  • 429 rate limit errors (respects Retry-After header)
  • Network errors (connection refused, timeouts)

Retries use exponential backoff with jitter.


Full Examples

Browser session with Puppeteer

typescript
import puppeteer from "puppeteer-core";
import \{ BrowserCloud \} from "@banata-boxes/sdk";
 
const cloud = new BrowserCloud(\{ apiKey: process.env.BANATA_API_KEY! \});
 
const \{ cdpUrl, close \} = await cloud.launch(\{
  weight: "medium",
  stealth: true,
\});
 
const browser = await puppeteer.connect(\{ browserWSEndpoint: cdpUrl \});
const page = (await browser.pages())[0];
await page.goto("https://example.com");
console.log(await page.title());
 
await browser.disconnect();
await close();

Code sandbox with file I/O

typescript
import \{ BanataSandbox \} from "@banata-boxes/sdk";
 
const sandbox = new BanataSandbox(\{ apiKey: process.env.BANATA_API_KEY! \});
 
const session = await sandbox.launch(\{
  runtime: "python",
  size: "standard",
\});
 
// Write a Python script
await session.fs.write("/workspace/analyze.py", `
import json
data = \{"result": [i**2 for i in range(10)]\}
print(json.dumps(data))
`);
 
// Run it
const result = await session.exec("python3", ["/workspace/analyze.py"]);
console.log(JSON.parse(result.stdout));
 
await session.kill();

AI agent with human handoff

typescript
import \{ BanataSandbox \} from "@banata-boxes/sdk";
 
const sandbox = new BanataSandbox(\{ apiKey: process.env.BANATA_API_KEY! \});
 
const session = await sandbox.launch(\{
  runtime: "bun",
  capabilities: \{
    opencode: \{ enabled: true, defaultAgent: "build" \},
    browser: \{ mode: "paired-banata-browser", humanInLoop: true \},
  \},
\});
 
// Send AI agent a task
await session.prompt("Log into dashboard.example.com and export the monthly report");
 
// If the AI encounters MFA, request human help
await session.requestHumanHandoff(\{
  reason: "mfa",
  message: "MFA code needed for dashboard.example.com",
  resumePrompt: "Continue exporting the report after login",
\});
 
// ... human completes MFA via browser preview ...
 
await session.completeHumanHandoff(\{
  returnControlTo: "ai",
  runResumePrompt: true,
\});
 
await session.kill();

Next Steps