Internal API

Terminal

The terminal endpoints let you create interactive PTY sessions inside the workspace VM. Terminal I/O is multiplexed over a single WebSocket connection.

Requires the internal server to be enabled. Up to 10 concurrent terminal sessions per workspace.

Overview

1. Create a terminal session  →  get session ID
2. Open WebSocket at /ws      →  bidirectional I/O
3. Send stdin as binary       →  [id_byte][data]
4. Receive stdout as binary   →  [id_byte][data]
5. Resize / close via JSON messages or REST

Create session

Create a new terminal session with an interactive PTY.

const term = await ws.terminal.create('ws_a1b2c3d4', {
  shell: '/bin/bash',
  cols: 120,
  rows: 40,
});

console.log(term.id);      // "1"
console.log(term.cols);    // 120
console.log(term.rows);    // 40
POST https://workspace.oblien.com/terminals
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json

{
  "cmd": ["/bin/bash"],
  "cols": 120,
  "rows": 40
}
curl -X POST "https://workspace.oblien.com/terminals" \
  -H "Authorization: Bearer $GATEWAY_JWT" \
  -H "Content-Type: application/json" \
  -d '{"cmd":["/bin/bash"],"cols":120,"rows":40}'

Parameters

ParameterTypeRequiredDescription
cmdstring[]NoCommand to run (e.g. ["/bin/bash"]). Falls back to default shell
commandstring[]NoAlias for cmd
colsintegerNoTerminal width in columns
rowsintegerNoTerminal height in rows

Response

{
  "success": true,
  "id": "1",
  "cols": 120,
  "rows": 40,
  "command": ["/bin/bash"]
}

HTTP status: 201 Created


List sessions

List all active terminal sessions.

const sessions = await ws.terminal.list('ws_a1b2c3d4');

for (const term of sessions.terminals) {
  console.log(`${term.id}: ${term.command.join(' ')} (alive: ${term.alive})`);
}
GET https://workspace.oblien.com/terminals
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
curl "https://workspace.oblien.com/terminals" \
  -H "Authorization: Bearer $GATEWAY_JWT"

Response

{
  "success": true,
  "terminals": [
    {
      "id": "1",
      "command": ["/bin/bash"],
      "cols": 120,
      "rows": 40,
      "alive": true,
      "exit_code": 0,
      "created_at": "2025-01-15T10:30:00Z"
    }
  ]
}

Close session

Close a terminal session and kill its process.

await ws.terminal.close('ws_a1b2c3d4', '1');
DELETE https://workspace.oblien.com/terminals/1
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
curl -X DELETE "https://workspace.oblien.com/terminals/1" \
  -H "Authorization: Bearer $GATEWAY_JWT"

Response

{
  "success": true,
  "terminal_id": "1"
}

Get scrollback

Retrieve the scrollback buffer for a terminal session. Useful for restoring terminal state after reconnection.

const scrollback = await ws.terminal.scrollback('ws_a1b2c3d4', '1');

console.log(scrollback.size);       // bytes in buffer
console.log(scrollback.alive);      // session still running
console.log(scrollback.scrollback); // base64-encoded data
GET https://workspace.oblien.com/terminals/1/scrollback
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
curl "https://workspace.oblien.com/terminals/1/scrollback" \
  -H "Authorization: Bearer $GATEWAY_JWT"

Response

{
  "success": true,
  "scrollback": "dXNlckBzYW5kYm94Oi9hcHAkIA==",
  "size": 2048,
  "alive": true,
  "exit_code": 0
}
FieldDescription
scrollbackBase64-encoded terminal output (64 KiB ring buffer)
sizeSize of the scrollback data in bytes
aliveWhether the session is still running
exit_codeProcess exit code (0 if still alive)

WebSocket

Terminal I/O flows over a single multiplexed WebSocket connection at /ws. Multiple terminal sessions share the same connection.

Connect

// Auto-connect when using terminal manager
const term = await ws.terminal.create('ws_a1b2c3d4', { shell: '/bin/bash' });

term.on('data', (data) => process.stdout.write(data));
term.on('exit', (code) => console.log(`Exited: ${code}`));

term.write('ls -la\n');
term.resize(160, 50);
WebSocket: wss://workspace.oblien.com/ws
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
const ws = new WebSocket('wss://workspace.oblien.com/ws', {
  headers: { Authorization: `Bearer ${gatewayJwt}` },
});

ws.binaryType = 'arraybuffer';

ws.onmessage = (event) => {
  if (event.data instanceof ArrayBuffer) {
    // Binary: terminal output
    const bytes = new Uint8Array(event.data);
    const terminalId = bytes[0];
    const data = bytes.slice(1);
    console.log(`Terminal ${terminalId}:`, new TextDecoder().decode(data));
  } else {
    // Text: control messages (exit, etc.)
    const msg = JSON.parse(event.data);
    console.log('Control:', msg);
  }
};

Protocol

Binary frames

DirectionFormatDescription
Client → Server[id_byte][stdin_data]Send input to terminal
Server → Client[id_byte][stdout_data]Receive output from terminal

The first byte is the terminal ID byte (mapped from the session ID). The remaining bytes are raw terminal data.

Text frames

Resize a terminal:

{
  "channel": "terminal",
  "type": "resize",
  "id": "1",
  "cols": 160,
  "rows": 50
}

Terminal exit notification (server → client):

{
  "channel": "terminal",
  "type": "exit",
  "id": "1",
  "code": 0
}

On connect

When a WebSocket connection is established, the server automatically sends:

  1. Scrollback data - binary frames with buffered output for each active session
  2. Exit notifications - text frames for any sessions that have already exited

This allows clients to restore terminal state after reconnection without explicit scrollback requests.


Error responses

StatusMeaning
400Missing terminal ID
401Missing or invalid token
404Terminal session not found
405Method not allowed
500Failed to create PTY session