> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orgo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Terminal WebSocket

> Interactive PTY shell over WebSocket - real-time bidirectional terminal I/O.

Connect to an interactive terminal session on a computer via WebSocket. This provides a full PTY (pseudo-terminal) interface, enabling real-time bidirectional communication with the computer's shell.

## Connection URL

```
wss://www.orgo.ai/desktops/{computer_id}/ws/terminal?token={password}
```

### Authentication

The terminal WebSocket requires the computer's password as the `token` query parameter. This is the same password used for VNC connections. Retrieve it from the [Get VNC Password](/api-reference/computers/vnc-password) endpoint before connecting.

Connections without a valid token are rejected with close code `4401`.

### Query Parameters

<ParamField query="token" type="string" required>
  Computer password. Retrieve via the [Get VNC Password](/api-reference/computers/vnc-password) endpoint.
</ParamField>

<ParamField query="cols" type="number" default="80">
  Number of columns for the terminal.
</ParamField>

<ParamField query="rows" type="number" default="24">
  Number of rows for the terminal.
</ParamField>

## Message Protocol

All messages are JSON-encoded. The WebSocket uses a simple request/response protocol with the following message types.

### Client → Server Messages

<AccordionGroup>
  <Accordion title="input" icon="keyboard">
    Send keyboard input to the terminal.

    ```json theme={null}
    {
      "type": "input",
      "data": "ls -la\r"
    }
    ```

    <ParamField body="type" type="string" required>
      Must be `"input"`.
    </ParamField>

    <ParamField body="data" type="string" required>
      The input string to send. Use `\r` for Enter key.
    </ParamField>
  </Accordion>

  <Accordion title="resize" icon="arrows-maximize">
    Resize the terminal dimensions.

    ```json theme={null}
    {
      "type": "resize",
      "cols": 120,
      "rows": 40
    }
    ```

    <ParamField body="type" type="string" required>
      Must be `"resize"`.
    </ParamField>

    <ParamField body="cols" type="number" required>
      New number of columns.
    </ParamField>

    <ParamField body="rows" type="number" required>
      New number of rows.
    </ParamField>
  </Accordion>

  <Accordion title="ping" icon="heart-pulse">
    Send a heartbeat ping to keep the connection alive.

    ```json theme={null}
    {
      "type": "ping"
    }
    ```
  </Accordion>
</AccordionGroup>

### Server → Client Messages

<AccordionGroup>
  <Accordion title="output" icon="terminal">
    Terminal output data.

    ```json theme={null}
    {
      "type": "output",
      "data": "user@computer:~$ "
    }
    ```

    <ResponseField name="type" type="string">
      Always `"output"`.
    </ResponseField>

    <ResponseField name="data" type="string">
      The terminal output. May contain ANSI escape codes for colors and formatting.
    </ResponseField>
  </Accordion>

  <Accordion title="error" icon="circle-exclamation">
    Error message from the server.

    ```json theme={null}
    {
      "type": "error",
      "message": "Connection failed"
    }
    ```

    <ResponseField name="type" type="string">
      Always `"error"`.
    </ResponseField>

    <ResponseField name="message" type="string">
      Human-readable error description.
    </ResponseField>
  </Accordion>

  <Accordion title="exit" icon="right-from-bracket">
    The shell process has exited.

    ```json theme={null}
    {
      "type": "exit",
      "code": 0
    }
    ```

    <ResponseField name="type" type="string">
      Always `"exit"`.
    </ResponseField>

    <ResponseField name="code" type="number">
      Exit code of the shell process.
    </ResponseField>
  </Accordion>

  <Accordion title="pong" icon="heart-pulse">
    Response to a ping message.

    ```json theme={null}
    {
      "type": "pong"
    }
    ```
  </Accordion>
</AccordionGroup>

## Examples

<CodeGroup>
  ```javascript JavaScript theme={null}
  const computerId = 'orgo-a3bb189e-8bf9-3888-9912-ace4e6543002';
  const apiKey = process.env.ORGO_API_KEY;

  // Step 1: Get the computer password
  const res = await fetch(
    `https://www.orgo.ai/api/computers/${computerId}/vnc-password`,
    { headers: { 'Authorization': `Bearer ${apiKey}` } }
  );
  const { password } = await res.json();

  // Step 2: Connect with password as token
  const ws = new WebSocket(
    `wss://www.orgo.ai/desktops/${computerId}/ws/terminal?token=${password}&cols=80&rows=24`
  );

  ws.onopen = () => {
    console.log('Connected to terminal');
  };

  ws.onmessage = (event) => {
    const message = JSON.parse(event.data);

    switch (message.type) {
      case 'output':
        // Append to your terminal display
        terminal.write(message.data);
        break;
      case 'error':
        console.error('Terminal error:', message.message);
        break;
      case 'exit':
        console.log('Shell exited with code:', message.code);
        break;
    }
  };

  // Send a command
  function sendCommand(command) {
    ws.send(JSON.stringify({
      type: 'input',
      data: command + '\r'  // \r for Enter key
    }));
  }

  // Resize terminal
  function resizeTerminal(cols, rows) {
    ws.send(JSON.stringify({
      type: 'resize',
      cols,
      rows
    }));
  }

  // Example: Run a command
  sendCommand('echo "Hello, World!"');
  ```

  ```python Python theme={null}
  import asyncio
  import websockets
  import json
  import requests

  async def connect_terminal(computer_id: str, api_key: str, cols: int = 80, rows: int = 24):
      # Step 1: Get the computer password
      res = requests.get(
          f"https://www.orgo.ai/api/computers/{computer_id}/vnc-password",
          headers={"Authorization": f"Bearer {api_key}"}
      )
      password = res.json()["password"]

      # Step 2: Connect with password as token
      url = f"wss://www.orgo.ai/desktops/{computer_id}/ws/terminal?token={password}&cols={cols}&rows={rows}"

      async with websockets.connect(url) as ws:
          # Handle incoming messages
          async def receive_messages():
              async for message in ws:
                  data = json.loads(message)

                  if data["type"] == "output":
                      print(data["data"], end="", flush=True)
                  elif data["type"] == "error":
                      print(f"Error: {data['message']}")
                  elif data["type"] == "exit":
                      print(f"Shell exited with code: {data['code']}")
                      break

          # Send a command
          async def send_command(command: str):
              await ws.send(json.dumps({
                  "type": "input",
                  "data": command + "\r"
              }))

          # Start receiving messages in background
          receive_task = asyncio.create_task(receive_messages())

          # Send commands
          await send_command("echo 'Hello, World!'")
          await send_command("ls -la")

          # Wait for output
          await asyncio.sleep(2)
          receive_task.cancel()

  # Run
  computer_id = "orgo-a3bb189e-8bf9-3888-9912-ace4e6543002"
  api_key = "sk_live_..."
  asyncio.run(connect_terminal(computer_id, api_key))
  ```

  ```typescript TypeScript theme={null}
  interface TerminalMessage {
    type: 'output' | 'error' | 'exit' | 'pong';
    data?: string;
    message?: string;
    code?: number;
  }

  class TerminalConnection {
    private ws!: WebSocket;

    static async connect(computerId: string, apiKey: string, cols = 80, rows = 24) {
      const conn = new TerminalConnection();

      // Step 1: Get the computer password
      const res = await fetch(
        `https://www.orgo.ai/api/computers/${computerId}/vnc-password`,
        { headers: { 'Authorization': `Bearer ${apiKey}` } }
      );
      const { password } = await res.json();

      // Step 2: Connect with password as token
      const url = `wss://www.orgo.ai/desktops/${computerId}/ws/terminal?token=${password}&cols=${cols}&rows=${rows}`;
      conn.ws = new WebSocket(url);

      conn.ws.onmessage = (event) => {
        const message: TerminalMessage = JSON.parse(event.data);
        conn.handleMessage(message);
      };

      return conn;
    }

    private handleMessage(message: TerminalMessage) {
      switch (message.type) {
        case 'output':
          console.log(message.data);
          break;
        case 'error':
          console.error('Error:', message.message);
          break;
        case 'exit':
          console.log('Exited with code:', message.code);
          break;
      }
    }

    send(data: string) {
      this.ws.send(JSON.stringify({ type: 'input', data }));
    }

    resize(cols: number, rows: number) {
      this.ws.send(JSON.stringify({ type: 'resize', cols, rows }));
    }

    ping() {
      this.ws.send(JSON.stringify({ type: 'ping' }));
    }

    close() {
      this.ws.close();
    }
  }

  // Usage
  const terminal = await TerminalConnection.connect(
    'orgo-a3bb189e-8bf9-3888-9912-ace4e6543002',
    'sk_live_...'
  );
  terminal.send('echo "Hello!"\r');
  ```
</CodeGroup>

## Integration with xterm.js

For browser-based terminal UIs, we recommend using [xterm.js](https://xtermjs.org/):

```javascript theme={null}
import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import '@xterm/xterm/css/xterm.css';

// Initialize xterm.js
const terminal = new Terminal({
  cursorBlink: true,
  fontFamily: 'monospace',
  fontSize: 14,
});

const fitAddon = new FitAddon();
terminal.loadAddon(fitAddon);
terminal.open(document.getElementById('terminal'));
fitAddon.fit();

// Step 1: Get the computer password
const computerId = 'orgo-a3bb189e-8bf9-3888-9912-ace4e6543002';
const res = await fetch(
  `https://www.orgo.ai/api/computers/${computerId}/vnc-password`,
  { headers: { 'Authorization': `Bearer ${apiKey}` } }
);
const { password } = await res.json();

// Step 2: Connect with password as token
const ws = new WebSocket(
  `wss://www.orgo.ai/desktops/${computerId}/ws/terminal?token=${password}&cols=${terminal.cols}&rows=${terminal.rows}`
);

// Handle output from server
ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  if (message.type === 'output') {
    terminal.write(message.data);
  }
};

// Send user input to server
terminal.onData((data) => {
  ws.send(JSON.stringify({ type: 'input', data }));
});

// Handle terminal resize
window.addEventListener('resize', () => {
  fitAddon.fit();
  ws.send(JSON.stringify({
    type: 'resize',
    cols: terminal.cols,
    rows: terminal.rows
  }));
});
```

## Best Practices

<CardGroup cols={2}>
  <Card title="Heartbeat" icon="heart-pulse">
    Send periodic `ping` messages (every 30 seconds) to keep the connection alive and detect disconnections early.
  </Card>

  <Card title="Reconnection" icon="rotate">
    Implement automatic reconnection with exponential backoff. Start with 2 seconds and increase up to 30 seconds.
  </Card>

  <Card title="Resize Events" icon="maximize">
    Send `resize` messages whenever the terminal container size changes to ensure proper text wrapping.
  </Card>

  <Card title="ANSI Support" icon="palette">
    The terminal output may contain ANSI escape codes. Use a library like xterm.js that handles these automatically.
  </Card>
</CardGroup>

<Note>
  The terminal WebSocket provides direct shell access. For running individual commands programmatically, consider using the [Execute Bash](/api-reference/computers/bash) endpoint instead.
</Note>

## Close codes

| Code   | Meaning                                    |
| ------ | ------------------------------------------ |
| `1000` | Normal closure.                            |
| `1001` | Server shutting down or computer stopping. |
| `4401` | Missing or invalid `token`.                |
| `4404` | Computer not found or not running.         |
| `4500` | Failed to spawn the shell process.         |
