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-i d >
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 SDK
const { terminal , wsUrl } = await sandbox . terminals . create ({
shell: "bash" , // Optional: bash, zsh, fish, sh
cwd: "/app" , // Optional: working directory
cols: 80 , // Optional: terminal width
rows: 24 // Optional: terminal height
});
// terminal = { id, sandboxId, shell, status, createdAt }
const response = await fetch (
`/api/v1/sandboxes/ ${ sandboxId } /terminals` ,
{
method: "POST" ,
credentials: "include" ,
headers: { "Content-Type" : "application/json" },
body: JSON . stringify ({
shell: "bash" ,
cwd: "/app" ,
cols: 80 ,
rows: 24
})
}
);
const { terminal , wsUrl } = await response . json ();
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 SDK
sandbox . terminals . create ({ shell: "bash" })
. 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 sandbox . terminals . create ({
shell: "bash" ,
cwd: "/app"
});
// Create terminal 2
const term2 = await sandbox . terminals . create ({
shell: "zsh" ,
cwd: "/home"
});
// 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.