Skip to main content

Overview

Access running sandboxes through interactive terminal sessions with full PTY support. Use the CLI for command-line access or integrate terminals directly into your web application.

Prerequisites

Before using interactive terminals, ensure you have:
  • Installed the Sandbox SDK (see Quickstart)
  • Created and started a sandbox instance
  • Set your SANDBOX_API_KEY environment variable
  • Basic familiarity with shell commands and WebSockets

CLI usage

Connect to a sandbox

Connect to a running sandbox from your terminal:
export SANDBOX_API_KEY=sk_live_...
sandboxcli connect <sandbox-id>

Select a shell

Choose your preferred shell (bash, zsh, fish, or sh):
# Use zsh
sandboxcli connect sbx_abc123 --shell zsh

# Use fish
sandboxcli connect sbx_abc123 --shell fish

# Default is bash
sandboxcli connect sbx_abc123

Features

When connected via CLI:
  • Full PTY support: Colors, cursor control, vim, tmux, nano work perfectly
  • Terminal resize: Automatically detects and syncs terminal size changes
  • Interactive input: Real-time input forwarding with proper buffering
  • Graceful disconnect: Press Ctrl+C to disconnect cleanly

Browser integration

Creating a terminal via API

Create a new terminal session and get a WebSocket URL:
const response = await fetch(
  `/api/v1/sandboxes/${sandboxId}/terminals`,
  {
    method: "POST",
    credentials: "include",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      shell: "bash",  // Optional: bash, zsh, fish, sh
      cwd: "/app",    // Optional: working directory
      cols: 80,       // Optional: terminal width
      rows: 24        // Optional: terminal height
    })
  }
);

const { terminal, wsUrl } = await response.json();

// terminal = { id, sandboxId, shell, status, createdAt }
// wsUrl = "wss://host/api/v1/sandboxes/sbx_abc/terminals/123/ws"

Connecting via WebSocket

Connect to the terminal using the WebSocket URL:
const ws = new WebSocket(wsUrl);

ws.onopen = () => {
  console.log("Terminal connected");
};

// Receive terminal output
ws.onmessage = (event) => {
  const data = event.data;

  // Check if it's a control message (JSON)
  if (typeof data === "string" && data.startsWith("{")) {
    const message = JSON.parse(data);

    if (message.type === "ready") {
      console.log("Terminal ready:", message);
    } else if (message.type === "exit") {
      console.log("Process exited:", message.exitCode);
    }
  } else {
    // Raw terminal output - display in your terminal UI
    console.log(data);
  }
};

// Send input to terminal
ws.send("ls -la\n");

// Send resize event
ws.send(JSON.stringify({
  type: "resize",
  cols: 120,
  rows: 40
}));

Using xterm.js

Example React component using xterm.js:
import { useEffect, useRef } from "react";
import { Terminal } from "xterm";
import { FitAddon } from "xterm-addon-fit";
import "xterm/css/xterm.css";

export function SandboxTerminal({ sandboxId }: { sandboxId: string }) {
  const terminalRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!terminalRef.current) return;

    // 1. Initialize xterm.js
    const term = new Terminal({
      cursorBlink: true,
      fontSize: 14,
      fontFamily: 'Menlo, Monaco, "Courier New", monospace',
      theme: {
        background: "#1e1e1e",
        foreground: "#d4d4d4",
      },
    });

    const fitAddon = new FitAddon();
    term.loadAddon(fitAddon);
    term.open(terminalRef.current);
    fitAddon.fit();

    // 2. Create terminal via API
    fetch(`/api/v1/sandboxes/${sandboxId}/terminals`, {
      method: "POST",
      credentials: "include",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ shell: "bash" }),
    })
      .then(res => res.json())
      .then(({ wsUrl }) => {
        // 3. Connect WebSocket
        const ws = new WebSocket(wsUrl);

        ws.onmessage = (event) => {
          term.write(event.data);
        };

        // 4. Forward input to WebSocket
        term.onData((data) => {
          if (ws.readyState === WebSocket.OPEN) {
            ws.send(data);
          }
        });

        // 5. Handle resize
        term.onResize(({ cols, rows }) => {
          if (ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({ type: "resize", cols, rows }));
          }
        });

        // Cleanup
        return () => {
          ws.close();
          term.dispose();
        };
      });
  }, [sandboxId]);

  return <div ref={terminalRef} style={{ width: "100%", height: "100%" }} />;
}

Multiple terminals

Create multiple terminals per sandbox

Each sandbox can have multiple independent terminal sessions:
// Create terminal 1
const term1 = await fetch(`/api/v1/sandboxes/${sandboxId}/terminals`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ shell: "bash", cwd: "/app" })
}).then(r => r.json());

// Create terminal 2
const term2 = await fetch(`/api/v1/sandboxes/${sandboxId}/terminals`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ shell: "zsh", cwd: "/home" })
}).then(r => r.json());

// Each has its own terminal ID and WebSocket URL
console.log(term1.terminal.id);  // "123"
console.log(term2.terminal.id);  // "456"

List terminals

Get all active terminals for a sandbox:
const response = await fetch(
  `/api/v1/sandboxes/${sandboxId}/terminals`,
  {
    credentials: "include"
  }
);

const { terminals } = await response.json();

terminals.forEach(terminal => {
  console.log(`Terminal ${terminal.id}: ${terminal.shell} (${terminal.status})`);
});

Close a terminal

Delete a specific terminal session:
await fetch(
  `/api/v1/sandboxes/${sandboxId}/terminals/${terminalId}`,
  {
    method: "DELETE",
    credentials: "include"
  }
);

Multiple clients (screen sharing)

Multiple WebSocket clients can connect to the same terminal for collaborative sessions:
// Client 1 connects
const ws1 = new WebSocket(wsUrl);

// Client 2 connects to same terminal
const ws2 = new WebSocket(wsUrl);

// Both clients receive all output
// Input from either client goes to the terminal
// Perfect for pair programming or debugging together
All connected clients receive the same terminal output in real-time. Any client can send input to the terminal.

WebSocket protocol

Control messages

Control messages are JSON objects sent from client to server or server to client: Resize terminal (client → server):
{
  "type": "resize",
  "cols": 120,
  "rows": 40
}
Ready message (server → client):
{
  "type": "ready",
  "pid": 123,
  "shell": "bash"
}
Exit notification (server → client):
{
  "type": "exit",
  "exitCode": 0,
  "signal": null
}

Raw terminal data

All other messages are raw terminal input/output:
  • Client → Server: Raw bytes of keyboard input
  • Server → Client: Raw bytes of terminal output (including ANSI escape codes)

Terminal lifecycle

Auto-cleanup

Terminals are automatically cleaned up after 30 minutes of inactivity when no clients are connected:
// Create terminal
const { wsUrl } = await createTerminal(sandboxId);

// Connect client
const ws = new WebSocket(wsUrl);

// Terminal stays alive while client is connected

ws.close();
// Terminal enters idle state

// After 30 minutes with no clients, terminal is automatically deleted
Terminals with at least one connected client are never automatically cleaned up, regardless of idle time.

Manual cleanup

Close terminals explicitly when done:
// Close specific terminal
await fetch(`/api/v1/sandboxes/${sandboxId}/terminals/${terminalId}`, {
  method: "DELETE"
});

// Or just disconnect WebSocket and let auto-cleanup handle it
ws.close();

Sandbox termination

When a sandbox is deleted or killed, all its terminals are automatically closed:
await sandbox.kill();
// All terminals for this sandbox are immediately terminated

Common patterns

Interactive debugging session

Start a terminal for debugging a running application:
# Connect to sandbox
sandboxcli connect sbx_abc123

# Check running processes
ps aux

# Inspect logs
tail -f /tmp/app.log

# Test network connectivity
curl localhost:3000/health

# Debug environment
env | grep NODE

Remote shell for CI/CD

Use terminals in CI pipelines for interactive debugging:
// In your CI script
import { Sandbox } from "@simplesandbox/sdk";

const client = Sandbox({
  apiKey: process.env.SANDBOX_API_KEY
});

const sandbox = await client.sandboxes.create({
  image: "node:20"
});

// Run tests
const testResult = await sandbox.exec("npm test");

if (!testResult.success) {
  console.log("Tests failed. Connect for debugging:");
  console.log(`sandboxcli connect ${sandbox.id}`);

  // Keep sandbox alive for 10 minutes
  await sandbox.updateTimeout({ timeoutMs: 10 * 60 * 1000 });

  // Exit with failure
  process.exit(1);
}

Collaborative debugging

Multiple developers can connect to the same terminal:
// Developer 1 creates terminal
const { terminal, wsUrl } = await createTerminal(sandboxId);

// Share terminal ID with Developer 2
console.log("Connect via:", terminal.id);

// Developer 2 connects to same terminal
const reconnectUrl = `wss://host/api/v1/sandboxes/${sandboxId}/terminals/${terminal.id}/ws`;
const ws2 = new WebSocket(reconnectUrl);

// Both developers see the same output and can type commands

Running interactive tools

Use terminals for tools that require TTY:
# Connect to sandbox
sandboxcli connect sbx_abc123

# Use vim
vim /app/config.json

# Use tmux for multiple panes
tmux

# Use htop for process monitoring
htop

# Use interactive Python REPL
python3

Troubleshooting

WebSocket connection fails

If the WebSocket connection fails immediately:
  • Check authentication: Ensure your API key or session is valid
  • Check sandbox state: Sandbox must be in “started” state
  • Check terminal exists: Verify the terminal ID is valid
  • Check network: Ensure WebSocket traffic is not blocked by firewall

Terminal output appears garbled

If terminal output looks corrupted:
  • Check terminal size: Send a resize event with your terminal dimensions
  • Check xterm.js version: Ensure you’re using a compatible version
  • Clear and reset: Try sending Ctrl+L or running reset command

Input not working

If keyboard input is not reaching the terminal:
  • Check WebSocket state: Verify ws.readyState === WebSocket.OPEN
  • Check raw mode: For CLI, ensure terminal is in raw mode
  • Check focus: For browser, ensure terminal element has focus

Terminal exits immediately

If terminals close right after creation:
  • Check shell: Some shells exit without input (try bash instead of sh)
  • Check working directory: Ensure cwd parameter points to existing directory
  • Check logs: Look at terminal output before it closed

Performance issues

If terminal feels slow or laggy:
  • Check network latency: High latency affects responsiveness
  • Reduce output: Avoid commands that produce huge amounts of output
  • Use pagination: For large output, use less, more, or head/tail

Best practices

  • Choose the right shell: Use bash for compatibility, zsh/fish for features
  • Clean up terminals: Delete terminals when done to free resources
  • Handle disconnections: Implement reconnection logic in your application
  • Set proper dimensions: Send accurate terminal size for best display
  • Use for debugging: Keep terminals as a debugging tool, not primary interface
  • Secure access: Always authenticate WebSocket connections
  • Monitor activity: Track terminal usage for billing and security

Limitations

  • Max idle time: Terminals auto-cleanup after 30 minutes without clients
  • No session persistence: Terminals don’t survive sandbox restarts
  • No buffer history: Reconnecting clients don’t receive previous output
  • PTY only: Requires PTY library (@lydell/node-pty in production)
  • Shell availability: Only supports bash, zsh, fish, sh (must be installed in image)
  • Single PTY per terminal: Each terminal is one PTY process

API endpoints

Create terminal

POST /api/v1/sandboxes/{sandboxId}/terminals
Content-Type: application/json

{
  "shell": "bash",
  "cwd": "/app",
  "cols": 80,
  "rows": 24
}
Response:
{
  "terminal": {
    "id": "123",
    "sandboxId": "sbx_abc",
    "shell": "bash",
    "status": "active",
    "createdAt": "2025-11-07T12:00:00Z"
  },
  "wsUrl": "wss://host/api/v1/sandboxes/sbx_abc/terminals/123/ws"
}

List terminals

GET /api/v1/sandboxes/{sandboxId}/terminals
Response:
{
  "terminals": [
    {
      "id": "123",
      "shell": "bash",
      "status": "active",
      "clientCount": 2
    }
  ]
}

Get terminal info

GET /api/v1/sandboxes/{sandboxId}/terminals/{terminalId}
Response:
{
  "id": "123",
  "sandboxId": "sbx_abc",
  "shell": "bash",
  "status": "active",
  "clientCount": 1,
  "createdAt": "2025-11-07T12:00:00Z"
}

Delete terminal

DELETE /api/v1/sandboxes/{sandboxId}/terminals/{terminalId}
Response: 204 No Content

Connect WebSocket

GET /api/v1/sandboxes/{sandboxId}/terminals/{terminalId}/ws
Upgrade: websocket
Connection: Upgrade
Upgrades to WebSocket connection for bidirectional terminal communication.