---
name: oblien-terminal
description: >-
  Oblien interactive terminal sessions - create PTY sessions multiplexed over WebSocket via the Runtime API on port 9990
license: MIT
compatibility:
  - claude-code
  - cursor
  - openclaw
  - goose
  - jetbrains-ai
metadata:
  author: oblien
  version: 1.0.0
  source: https://oblien.com/docs
---

# Terminal

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

> **Note:** Requires the internal server to be [enabled](/docs/runtime-api/server-setup#enable-the-server). 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.


**SDK:**

```typescript
const rt = await client.workspaces.runtime('ws_a1b2c3d4');

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

console.log(term.id);      // "1"
console.log(term.cols);    // 120
console.log(term.rows);    // 40
```


**REST API:**

```http
POST https://workspace.oblien.com/terminals
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json

{
  "cmd": ["/bin/bash"],
  "cols": 120,
  "rows": 40
}
```


**cURL:**

```bash
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

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `cmd` | `string[]` | No | Command to run (e.g. `["/bin/bash"]`). Falls back to default shell |
| `command` | `string[]` | No | Alias for `cmd` |
| `cols` | `integer` | No | Terminal width in columns |
| `rows` | `integer` | No | Terminal height in rows |

### Response

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

HTTP status: `201 Created`

---

## List sessions

List all active terminal sessions.


**SDK:**

```typescript
const sessions = await rt.terminal.list();

for (const term of sessions) {
  console.log(`${term.id}: ${term.command.join(' ')} (alive: ${term.alive})`);
}
```


**REST API:**

```http
GET https://workspace.oblien.com/terminals
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```


**cURL:**

```bash
curl "https://workspace.oblien.com/terminals" \
  -H "Authorization: Bearer $GATEWAY_JWT"
```


### Response

```json
{
  "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.


**SDK:**

```typescript
await rt.terminal.close('1');
```


**REST API:**

```http
DELETE https://workspace.oblien.com/terminals/1
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```


**cURL:**

```bash
curl -X DELETE "https://workspace.oblien.com/terminals/1" \
  -H "Authorization: Bearer $GATEWAY_JWT"
```


### Response

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

---

## Get scrollback

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


**SDK:**

```typescript
const scrollback = await rt.terminal.scrollback('1');

console.log(scrollback.size);       // bytes in buffer
console.log(scrollback.alive);      // session still running
console.log(scrollback.scrollback); // base64-encoded data
```


**REST API:**

```http
GET https://workspace.oblien.com/terminals/1/scrollback
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```


**cURL:**

```bash
curl "https://workspace.oblien.com/terminals/1/scrollback" \
  -H "Authorization: Bearer $GATEWAY_JWT"
```


### Response

```json
{
  "success": true,
  "scrollback": "dXNlckBzYW5kYm94Oi9hcHAkIA==",
  "size": 2048,
  "alive": true,
  "exit_code": 0
}
```

| Field | Description |
|-------|-------------|
| `scrollback` | Base64-encoded terminal output (64 KiB ring buffer) |
| `size` | Size of the scrollback data in bytes |
| `alive` | Whether the session is still running |
| `exit_code` | Process 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


**SDK:**

```typescript
// Create a terminal session
const term = await rt.terminal.create({ shell: '/bin/bash' });

// Open a RuntimeWebSocket for bidirectional I/O
const ws = rt.ws();

ws.onTerminalOutput((id, data) => {
  process.stdout.write(data);
});

ws.onClose(() => console.log('WebSocket closed'));

await ws.connect();
ws.writeTerminalInput(term.id, 'ls -la\n');
ws.resizeTerminal(term.id, 160, 50);
```


**REST API:**

```
WebSocket: wss://workspace.oblien.com/ws
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```


**JavaScript:**

```javascript
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

| Direction | Format | Description |
|-----------|--------|-------------|
| 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:**

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

**Terminal exit notification (server → client):**

```json
{
  "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

| Status | Meaning |
|--------|---------|
| `400` | Missing terminal ID |
| `401` | Missing or invalid token |
| `404` | Terminal session not found |
| `405` | Method not allowed |
| `500` | Failed to create PTY session |
