# Open a terminal

Upgrade to a WebSocket for a running sandbox's interactive terminal.

GET `/v1/customer/tenants/{tenantId}/sandboxes/{sandboxId}/terminal`

Base URL: `https://api.runzivo.dev`

## Authentication

Send `Authorization: Bearer <token>` with a customer access token or workspace API key.

API key permission: `sandboxes:write`. These are permissions, not legacy scopes. A key with the `all:all` scope also works, limited by its creator's current workspace permissions.

A browser can instead use a [single-use terminal ticket](https://runzivo.dev/docs/reference/sandboxes/terminal-ticket/), without an Authorization header.

## Parameters

| Field | Description |
| --- | --- |
| `tenantId` string · path Required | Your workspace's tenant ID. Replace the placeholder with an ID your token can access. |
| `sandboxId` string · path Required | The id returned when the sandbox was created: sbx\_ followed by 24 lowercase hexadecimal characters. |
| `ticket` string · query Optional | Single-use browser terminal ticket. Supply this without Authorization; API and CLI clients send a bearer token instead. |

## Request body

No request body is required.

## Behavior and limits

- Requires a WebSocket client. The cURL example shows the HTTP upgrade request only; it is not an interactive terminal client.
- Terminal bytes travel as binary frames in both directions, never base64. The only text frame is a resize object: {"type":"resize","cols":80,"rows":24}; each dimension must be 1–1000.
- Answer server pings (every 30 seconds) with pong. Up to two terminals per sandbox. A new connection creates a new shell; a disconnected session does not resume.
- Close codes: 1000 for normal/lifecycle closure or 30-minute idle timeout; 1003 invalid text frame; 1008 input_rate; 1009 frame over 64 KiB; 1011 server fault or output_overflow.

## Responses

| Status | Meaning |
| --- | --- |
| 101 Switching Protocols | WebSocket |
| 400 Invalid request | Malformed JSON, unknown fields or invalid values. Terminal upgrades also reject malformed handshakes. |
| 401 Unauthorized | Missing or invalid bearer token. A ticketed terminal request can instead return ticket_invalid. |
| 403 Forbidden | The caller lacks workspace membership or permission, or the API key is not allowed. Codes include tenant_membership_required, permission_required and api_key_scope_required. |
| 404 Not found | No sandbox with this ID exists in the authorized workspace. |
| 409 Conflict | The operation is not allowed in the current sandbox state. See the operation notes for other possible conflict codes. |
| 426 Upgrade required | The request did not contain a WebSocket upgrade. |
| 500 Internal error | The server could not complete the request. |
| 503 Unavailable | Sandbox service is unavailable. Exec and terminal operations can also return sandbox_exec_unavailable. |

Examples use synthetic IDs, dates and values. Read the actual response and status in your workspace.

[Sandbox limits and billing](https://runzivo.dev/docs/sandboxes-limits/)

Page tools

[View as Markdown](https://runzivo.dev/docs/reference/sandboxes/terminal.md)

Ask AI about this page

[Open in ChatGPT](https://chatgpt.com/?q=Read%20this%20Runzivo%20documentation%20page%2C%20then%20help%20me%20with%20it%3A%0A%0Ahttps%3A%2F%2Frunzivo.dev%2Fdocs%2Freference%2Fsandboxes%2Fterminal.md) [Open in Claude](https://claude.ai/new?q=Read%20this%20Runzivo%20documentation%20page%2C%20then%20help%20me%20with%20it%3A%0A%0Ahttps%3A%2F%2Frunzivo.dev%2Fdocs%2Freference%2Fsandboxes%2Fterminal.md) [Open in Cursor](https://cursor.com/link/prompt?text=Read%20this%20Runzivo%20documentation%20page%2C%20then%20help%20me%20with%20it%3A%0A%0Ahttps%3A%2F%2Frunzivo.dev%2Fdocs%2Freference%2Fsandboxes%2Fterminal.md)

Claude shows a caution banner on any prefilled prompt. That is expected: review the prompt, then send it.

## Request example

Illustrative request. Set RUNZIVO_TOKEN and TENANT_ID, and SANDBOX_ID in your shell. Replace placeholders with your own values. This page does not send requests.

Set WEBSOCKET_KEY to a fresh base64-encoded 16-byte nonce. This cURL request shows the HTTP upgrade only; use a WebSocket client for terminal frames.

cURL

```
curl --http1.1 --request GET \
  "https://api.runzivo.dev/v1/customer/tenants/${TENANT_ID}/sandboxes/${SANDBOX_ID}/terminal" \
  --header "Authorization: Bearer ${RUNZIVO_TOKEN}" \
  --header "Connection: Upgrade" \
  --header "Upgrade: websocket" \
  --header "Sec-WebSocket-Version: 13" \
  --header "Sec-WebSocket-Key: ${WEBSOCKET_KEY}"
```

## Response example

WebSocket · An upgraded socket has no JSON response body. Illustrative protocol exchange:

101 · WebSocket

```
Client → server: binary terminal input bytes
Client → server: text {"type":"resize","cols":80,"rows":24}
Server → client: binary terminal output bytes
Server → client: ping (client replies pong)
```

### Browser connection with a ticket

Illustrative JavaScript after obtaining a ticket. Use its returned URL, do not log it, and send terminal input as binary frames.

JavaScript

```
// ticketResponse is the terminal-ticket response.
const url = new URL(ticketResponse.url);
url.searchParams.set("ticket", ticketResponse.ticket);
const socket = new WebSocket(url);
socket.binaryType = "arraybuffer";
socket.addEventListener("open", () => {
  socket.send(JSON.stringify({ type: "resize", cols: 80, rows: 24 }));
});
```

All response examples (also available without JavaScript)

### 101 Switching Protocols

An upgraded socket has no JSON response body. Illustrative protocol exchange:

WebSocket

```
Client → server: binary terminal input bytes
Client → server: text {"type":"resize","cols":80,"rows":24}
Server → client: binary terminal output bytes
Server → client: ping (client replies pong)
```

### 400 Invalid request

Malformed JSON, unknown fields or invalid values. Terminal upgrades also reject malformed handshakes.

application/json

```
{
  "error": {
    "code": "invalid_request"
  }
}
```

### 401 Unauthorized

Missing or invalid bearer token. A ticketed terminal request can instead return ticket_invalid.

application/json

```
{
  "error": {
    "code": "unauthorized"
  }
}
```

### 403 Forbidden

The caller lacks workspace membership or permission, or the API key is not allowed. Codes include tenant_membership_required, permission_required and api_key_scope_required.

application/json

```
{
  "error": {
    "code": "permission_required"
  }
}
```

### 404 Not found

No sandbox with this ID exists in the authorized workspace.

application/json

```
{
  "error": {
    "code": "sandbox_not_found"
  }
}
```

### 409 Conflict

The operation is not allowed in the current sandbox state. See the operation notes for other possible conflict codes.

application/json

```
{
  "error": {
    "code": "sandbox_wrong_state"
  }
}
```

### 426 Upgrade required

The request did not contain a WebSocket upgrade.

application/json

```
{
  "error": {
    "code": "websocket_required"
  }
}
```

### 500 Internal error

The server could not complete the request.

application/json

```
{
  "error": {
    "code": "internal_error"
  }
}
```

### 503 Unavailable

Sandbox service is unavailable. Exec and terminal operations can also return sandbox_exec_unavailable.

application/json

```
{
  "error": {
    "code": "sandboxes_unavailable"
  }
}
```
