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

> ## Agent Instructions
> These docs cover learner integrations through Hosted Lab Pages, the SDK, and the REST API. Content management is outside this integration scope.
> Read the setup page for the chosen approach before implementing it. Keep organization API keys and hosted mint secrets on the server.
> Install the SDK with `npm install @cybr/labs-sdk`. It runs on the server; the `/terminal`, `/terminal/xterm`, `/video`, and `/hosted-browser` entry points run in the browser.
> Cybr provides completion tracking and CTF verification. The integrating platform decides whether to award points.

# Terminal

> Server session helpers, the browser terminal client, and the xterm.js adapter.

The [terminal guide](/guides/terminals) shows how these pieces fit together.

| Import | Runs in | Exports |
| - | - | - |
| `@cybr/labs-sdk` | Server | `handleTerminalSession`, `supportsWebTerminal`, `deployments.createTerminalSession` |
| `@cybr/labs-sdk/terminal` | Browser | `createTerminalClient`, `fetchTerminalSession`, `TerminalRefusedError`, `terminalRefusalMessage`, `supportsWebTerminal` |
| `@cybr/labs-sdk/terminal/xterm` | Browser | `xtermCallbacks`, `attachXterm` |

The browser entry points contain no server code and accept no API key.

## Server

### `handleTerminalSession(cybr, deploymentId, options?)`

Returns a `Promise<Response>`: the session as uncached JSON, or `{ message: <reason> }` with a status the browser client understands. The [guide](/guides/terminals#add-the-session-route) lists the statuses.

| Option | Default | Purpose |
| - | - | - |
| `timeoutMs` | `10000` | How long to wait for a starting terminal |
| `pollIntervalMs` | `2000` | Time between startup checks |
| `signal` | None | Cancel the request, such as `request.signal` |
| `onError` | None | Receives the original error before a refusal is returned |

Pass a `deploymentId` only after your [ownership check](/guides/tracking-deployments#check-ownership-on-every-request).

### `deployments.createTerminalSession(deploymentId, options?)`

The lower-level call behind `handleTerminalSession`. Use it to build the response yourself.

```ts theme={null}
const session = await cybr.deployments.createTerminalSession(deploymentId, {
  pollIntervalMs: 2000,
  timeoutMs: 30000,
  signal: abortController.signal
})
```

It retries `CONTAINER_STARTING` until `timeoutMs`, which defaults to 30 seconds. The deadline throws `timeout`, and cancellation throws `aborted`. Other failures throw a `CybrLabsError` whose `backendCode` holds the reason.

```ts theme={null}
interface TerminalSession {
  wsUrl: string
  protocols: string[]
  expiresAt: string
}
```

### `supportsWebTerminal(lab)`

Returns `true` for an AWS lab with `webTerminal: true`. Accepts a `Lab` or `LabDetails`.

## Browser client

### `createTerminalClient(options)`

| Option | Type | Purpose |
| - | - | - |
| `getSession` | `(signal: AbortSignal) => Promise<TerminalSession>` | Required. Fetch a new session from your route. Called on every connect and reconnect. |
| `onOutput` | `(data: Uint8Array) => void` | Terminal output |
| `onReset` | `() => void` | Called once per connection before its first output. Clear your renderer here, because the server replays recent output. |
| `onStateChange` | `(state: TerminalState) => void` | Connection state |
| `onExit` | `(code: number \| undefined) => void` | The shell exited, just before `onClose` |
| `onError` | `(error: Error) => void` | A connection failed before the shell was ready |
| `onClose` | `(info: TerminalCloseInfo) => void` | A connection ended. With `autoReconnect`, called once when the client stops trying. |
| `onReconnect` | `(info: TerminalReconnectInfo) => void` | `autoReconnect` scheduled another attempt |
| `autoReconnect` | `boolean \| { startupBudgetMs?, maxAttempts? }` | Retry with a new session after drops and while starting. Off by default; recommended. |
| `connectionTimeoutMs` | `number` | Deadline for one attempt. `120000` by default, or `30000` per attempt with `autoReconnect`. |

`startupBudgetMs` defaults to `120000`: how long to keep trying before the shell is first ready. `maxAttempts` defaults to `10`: attempts after each drop. The count starts again once a connection stays up for a minute.

`TerminalState` is `idle`, `connecting`, `ready`, `reconnecting`, `closed`, or `disposed`.

Without `autoReconnect`, each `connect()` is one attempt. Within it, retryable refusals such as `CONTAINER_STARTING` are retried until `connectionTimeoutMs`. A dropped connection is not reconnected.

### Client methods

| Method | Result | Behavior |
| - | - | - |
| `connect()` | `Promise<void>` | Fetch a session and connect. Resolves when the shell is ready. Rejects on failure, replacement, or disposal. |
| `reconnect()` | `Promise<void>` | Replace the connection with a new session |
| `write(text)` | `boolean` | Send input. Returns `false` when the shell is not ready. |
| `resize(cols, rows)` | `void` | Set the size, clamped to 1–500 columns and 1–300 rows |
| `dispose()` | `void` | Close the connection and release listeners. Does not end the lab. |

### `TerminalCloseInfo`

| Field | Meaning |
| - | - |
| `code` | WebSocket close code, or `1000` after a plain shell exit |
| `reason` | Close reason |
| `wasReady` | The shell was ready before the connection ended |
| `retryable` | `reconnect()` may work |
| `labEnded` | The lab ended. Launch it again for a new terminal. |

| Close code | Meaning | `retryable` | Retried by `autoReconnect` |
| - | - | - | - |
| `4408` | The lab ended | No | No |
| `4403` | The session belongs to a different lab | No | No |
| `4500` | Terminal service failure | Yes | No |
| `4401` | The session expired or was rejected | Yes | Yes |
| `4409` | The shell is being set up again | Yes | Yes |

A plain shell exit, such as the learner typing `exit`, is retryable and not retried automatically. `reconnect()` opens a fresh shell.

### `TerminalReconnectInfo`

| Field | Meaning |
| - | - |
| `cause` | `starting`, `session_expired`, `connection_lost`, or `refused` |
| `attempt` | Which retry this is, starting at 1 |
| `maxAttempts` | The attempt limit. Before the shell is first ready, `startupBudgetMs` applies instead, so `attempt` can exceed it. |
| `delayMs` | Milliseconds until the next attempt |
| `code` | WebSocket close code, when a closed connection triggered the retry |
| `reason` | The close reason, or the refusal code for `starting` and `refused` |

### `fetchTerminalSession(url, init?)`

POSTs to your session route without caching and resolves with the session. A non-2xx response throws `TerminalRefusedError`. Pass `init` for headers or a `signal`.

### `TerminalRefusedError`

| Field | Meaning |
| - | - |
| `reason` | The response body's `message`, such as `LAB_ENDED`, or `HTTP_<status>` |
| `message` | Learner-facing copy from `terminalRefusalMessage(reason)` |
| `status` | HTTP status |
| `retryAfter` | Seconds to wait, when the response said |

If you write your own `getSession`, throw a `TerminalRefusedError` so the client can tell a final refusal from one worth retrying.

### `terminalRefusalMessage(reason)`

Returns learner-facing copy for a refusal reason. Unknown reasons get a generic message.

## xterm.js adapter

The adapter does not depend on xterm.js. It works with any object of the same shape.

### `xtermCallbacks(term)`

Returns `{ onOutput, onReset }` to spread into `createTerminalClient`. Output goes to `term.write`, and each new connection calls `term.reset()`.

### `attachXterm(term, client, options?)`

Call after `term.open(element)`. Returns a function that detaches everything; call it before disposing either side.

| Option | Default | Purpose |
| - | - | - |
| `fit` | None | A fit addon. The terminal refits when its element resizes or the page becomes visible, and sends the new size. |
| `copyOnCtrlC` | `true` | Copy the selection on Ctrl+C or Cmd+C. With nothing selected, the keys still interrupt the shell. |

### Other renderers

Without xterm.js, wire the callbacks yourself:

```ts theme={null}
const terminal = createTerminalClient({
  getSession,
  onOutput: (data) => screen.write(data),
  onReset: () => screen.clear(),
  autoReconnect: true
})
terminal.resize(screen.cols, screen.rows)
screen.onData((text) => terminal.write(text))
```

Output arrives as UTF-8 bytes. The [WebSocket protocol](/api-reference/terminal-protocol) documents the wire format for clients that do not use the SDK.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.