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
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
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
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
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.