# VNC Access (/docs/vnc-access)

VNC (Virtual Network Computing) access provides a graphical desktop environment for your Daytona Sandbox directly in the browser. This allows you to interact with GUI applications, desktop tools, and visual interfaces running inside your sandbox.

VNC and [computer use](/docs/computer-use) work together for manual and automated desktop interactions. VNC is the visual interface for users, while computer use is the programmatic API for AI agents to automate mouse, keyboard, and screenshot operations. Through VNC, you can watch agents perform computer use tasks in real time.

The desktop stack (D-Bus, Xvfb, the XFCE window manager, and the x11vnc server) ships with the sandbox runtime as a self-contained add-on, so it works on any Linux sandbox image, including custom images that carry no desktop packages. The virtual display runs at 1280x720.

<VncAccessVisual />

<Callout type="note">
The desktop is available for Linux sandboxes.
</Callout>

## Use cases [#use-cases]

VNC access is used when a person needs to see or operate the desktop of a sandbox.

- **Computer-use agents.** A person follows what an agent does on the desktop as it happens, from the browser. The agent drives the mouse and keyboard through Computer Use, and VNC shows the same desktop. Desktop interaction calls, such as screenshots and mouse and keyboard input, fail with a desktop-not-ready error until the desktop is ready.

- **Takeover from an agent.** A person operates the desktop directly when an agent cannot continue, for example at a login screen. The **Desktop** tab in the Dashboard gives mouse and keyboard control over the same desktop. Only members whose project API key has the sandbox write permission see that tab.

- **GUI applications.** Desktop applications with no web or command-line interface run in the sandbox and are used from the browser. The desktop stack ships with the sandbox runtime, so it works on any Linux image, including custom images without desktop packages. The virtual display runs at 1280x720.

- **Desktop test debugging.** A person watches GUI tests run and inspects the desktop after a failure. A desktop restart clears all application windows, so inspect the desktop before you restart it. When the desktop itself fails, its status carries a cause with a code and a message.

- **Your own VNC client.** Any VNC client that supports WebSocket, such as noVNC, connects to the desktop through the Daytona proxy. An application embeds the desktop in its own interface. Browser clients that cannot set headers pass the API key as a query parameter.

## Access VNC from Dashboard [#access-vnc-from-dashboard]

Open the sandbox desktop directly from the [Daytona Dashboard ↗](https://app.daytona.com/dashboard/sandboxes).

1. Go to [Daytona Sandboxes ↗](https://app.daytona.com/dashboard/sandboxes)
2. Open the sandbox you want to access. The sandbox must be in the `running` state.
3. Select the **Desktop** tab (also reachable via `?tab=desktop` in the URL)
4. If the desktop is not started yet, click <Button>Start Desktop</Button>

The Dashboard polls the desktop status while it starts and then connects a VNC viewer in your browser, providing mouse and keyboard control over the sandbox's graphical interface. Click into the frame to interact, and use <Button>Fullscreen</Button> for a larger view or <Button>Reconnect</Button> if the connection drops. If the desktop fails to start, the tab shows the failure cause and offers <Button>Restart Desktop</Button>.

<Callout type="note">
The Desktop tab is shown only to members whose project API key has the sandbox write permission, alongside the Terminal, Files, and SSH tabs.
</Callout>

## Programmatic VNC management [#programmatic-vnc-management]

Daytona provides methods to [start](#start-vnc), [stop](#stop-vnc), [monitor](#get-vnc-status), and [restart](#restart-vnc) the desktop processes programmatically using the [computer use](/docs/computer-use) references as part of automated workflows.

### Start VNC [#start-vnc]

Start the desktop processes: D-Bus, Xvfb, the window manager, and the x11vnc server.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

result = sandbox.computer_use.start()
print("VNC processes started:", result.message)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk';

const daytona = new Daytona();
const sandbox = await daytona.v2.create();

const result = await sandbox.computerUse.start();
console.log('VNC processes started:', result.message);
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/start' \
  --request POST \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- `{toolboxProxyUrl}` is the `toolbox_proxy_url` field of the [sandbox](/docs/sandboxes#get-a-sandbox).
</Callout>

</TabItem>
</Tabs>

### Stop VNC [#stop-vnc]

Stop all desktop processes in the sandbox.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()
sandbox.computer_use.start()

result = sandbox.computer_use.stop()
print("VNC processes stopped:", result.message)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk';

const daytona = new Daytona();
const sandbox = await daytona.v2.create();
await sandbox.computerUse.start();

const result = await sandbox.computerUse.stop();
console.log('VNC processes stopped:', result.message);
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/stop' \
  --request POST \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

</TabItem>
</Tabs>

### Get VNC status [#get-vnc-status]

Check whether the desktop is running. The status field is active or inactive, and the desktop field reports the lifecycle stage: not started, starting, ready, or failed.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

status = sandbox.computer_use.status()
print("VNC status:", status.status, status.desktop)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk';

const daytona = new Daytona();
const sandbox = await daytona.v2.create();

const status = await sandbox.computerUse.status();
console.log('VNC status:', status.status, status.desktop);
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/status' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

</TabItem>
</Tabs>

### Restart VNC [#restart-vnc]

Tear down the desktop processes and start a fresh desktop. Restart is valid from every state and is the only way out of the failed state. Application windows live inside the X server, so a restart returns an empty desktop. The response reports an active status and a ready desktop.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()
sandbox.computer_use.start()

result = sandbox.computer_use.restart()
print("VNC restarted:", result.message, result.desktop)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk';

const daytona = new Daytona();
const sandbox = await daytona.v2.create();
await sandbox.computerUse.start();

const result = await sandbox.computerUse.restart();
console.log('VNC restarted:', result.message, result.desktop);
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/restart' \
  --request POST \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

</TabItem>
</Tabs>

### Desktop states [#desktop-states]

The desktop field of the [status](#get-vnc-status) response is the desktop lifecycle state. The status field is derived from it: active when the desktop is ready, inactive otherwise.

| **`desktop`**     | Meaning                                                              | Next step                                          |
| ------------- | -------------------------------------------------------------------- | -------------------------------------------------- |
| `not_started` | No desktop is running. Also the state after a sandbox stop or start. | Call [start](#start-vnc).                          |
| `starting`    | The desktop stack is coming up.                                      | Poll status until `ready`.                         |
| `ready`       | The desktop is running and the VNC server accepts connections.       | Connect a viewer or run Computer Use operations.   |
| `failed`      | The desktop stopped working. `cause` carries `code` and `message`.   | Call [restart](#restart-vnc).                      |

Only a failed desktop has a cause object, with the platform detail in its message.

Its code names what was lost:

| **`cause.code`**               | Meaning                                                                     |
| -------------------------- | --------------------------------------------------------------------------- |
| `desktop_exited`           | The X server (`xvfb`) exited. Windows live inside it, so the desktop is gone. |
| `addon_crashed`            | The desktop supervisor died while a desktop existed.                        |
| `start_failed`             | Starting the stack or probing its readiness failed.                         |
| `start_interrupted`        | The supervisor died while the desktop was starting.                         |
| `state_corrupt`            | The retained desktop state could not be read or validated.                  |
| `state_persistence_failed` | A desktop state transition could not be persisted.                          |

Treat unknown codes as failures as well; the list grows additively.

Lifecycle rules:

- Every Computer Use operation (mouse, keyboard, screenshot, display, accessibility, recording start) requires `ready`; otherwise it returns `409` `DESKTOP_NOT_READY`, raised by the SDKs as `DesktopNotReadyError`. Nothing retries it; a failed desktop cannot heal by retry.
- On a `ready` desktop, `start` returns the process status; on `starting` or `failed`, `409 DESKTOP_NOT_READY`. Retrying `start` cannot wipe a failed desktop.
- `stop` kills a `failed` desktop's processes, but only `restart` leaves `failed`.
- `dbus`, `wm`, and `x11vnc` restart automatically with backoff if they exit; the desktop stays `ready`. `xvfb` never does, and restarting it per process returns `400`; use [restart](#restart-vnc).
- Without the desktop add-on, `computeruse` endpoints return `503` `ADDON_UNAVAILABLE`.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona
from daytona.v2 import DesktopNotReadyError

daytona = Daytona()
sandbox = daytona.v2.create()

status = sandbox.computer_use.status()
if status.desktop == "failed":
    print("Desktop failed:", status.cause.code, status.cause.message)
    sandbox.computer_use.restart()

try:
    sandbox.computer_use.screen.screenshot()
except DesktopNotReadyError as e:
    print("Desktop not ready:", e)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk';
import { DesktopNotReadyError } from '@daytona/sdk/v2';

const daytona = new Daytona();
const sandbox = await daytona.v2.create();

const status = await sandbox.computerUse.status();
if (status.desktop === 'failed') {
  console.log('Desktop failed:', status.cause.code, status.cause.message);
  await sandbox.computerUse.restart();
}

try {
  await sandbox.computerUse.screen.screenshot();
} catch (e) {
  if (e instanceof DesktopNotReadyError) console.log('Desktop not ready:', e.message);
}
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# Failed desktop
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/status' \
  --header 'Authorization: Bearer YOUR_API_KEY'
# {"status":"inactive","desktop":"failed",
#  "cause":{"code":"desktop_exited","message":"Xvfb (:99) exited unexpectedly (signal 11)"}}

# Any action while not ready
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/screenshot' \
  --header 'Authorization: Bearer YOUR_API_KEY'
# HTTP 409 {"statusCode":409,"code":"DESKTOP_NOT_READY",
#  "message":"desktop failed: Xvfb (:99) exited unexpectedly (signal 11); call POST /computeruse/restart", ...}
```

</TabItem>
</Tabs>

For additional process management operations including restarting individual processes and viewing logs, see the [computer use](/docs/computer-use) reference.

## Connect with your own VNC client [#connect-with-your-own-vnc-client]

The sandbox's VNC server is bridged through the Daytona proxy as a WebSocket that speaks the standard RFB protocol. Any WebSocket-capable VNC client, such as noVNC, can connect to it and authenticate with your API key.

- The WebSocket address is `wss://toolbox.daytona.com/vnc/{sandboxId}`.
- The API key goes in an `Authorization: Bearer <API_KEY>` header, or, for browser clients that cannot set headers, in the `daytona_token` query parameter:

```text
wss://toolbox.daytona.com/vnc/{sandboxId}?daytona_token=<API_KEY>
```

The desktop processes must be [started](#start-vnc) and the sandbox running. Before the WebSocket handshake completes, the proxy returns 401 for a missing or invalid credential, 404 for an unknown sandbox, 409 if it is not running, and 502 if the VNC server is unreachable.

The VNC server itself runs without a VNC password and in shared mode: the API key is the only credential, a viewer that prompts for a VNC password is not talking to the sandbox desktop, and several viewers can be attached to the same desktop at once. The bridge carries raw RFB in binary WebSocket frames and echoes the binary subprotocol when the client requests it.

## VNC sessions and the idle timer [#vnc-sessions-and-the-idle-timer]

A connected VNC session counts as sandbox activity for the [idle timeout](/docs/linux-sandboxes#idle-timeout). The proxy records activity when the session connects and whenever the viewer sends bytes. VNC viewers send protocol traffic while the user is idle, so an open session keeps the sandbox alive until it closes. Close the viewer, or the Dashboard's Desktop tab, to let the idle timer run.

## Automating desktop interactions [#automating-desktop-interactions]

Once VNC is running, automate desktop interactions with Computer Use: AI agents can control the mouse and keyboard and capture screenshots within the VNC session.

**Available operations:**

- **Mouse**: click, move, drag, scroll, and get cursor position
- **Keyboard**: type text, press keys, and execute hotkey combinations
- **Screenshot**: capture full screen, regions, or compressed images
- **Display**: get display information and list open windows

For complete documentation on automating desktop interactions, see [computer use](/docs/computer-use).

> **Example**: Automated browser interaction

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

# Start VNC processes
sandbox.computer_use.start()

# Click to open browser
sandbox.computer_use.mouse.click(x=50, y=50, button="left")

# Type a URL
sandbox.computer_use.keyboard.type(text="https://www.daytona.io/docs/")
sandbox.computer_use.keyboard.key(key="enter")

# Take a screenshot
screenshot = sandbox.computer_use.screen.screenshot()
```

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- `start()` returns once the X server and the VNC server accept connections.
- Later calls do not need to poll the status.
- On a failed desktop, call `restart()` instead of `start()`.
</Callout>

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk';

const daytona = new Daytona();
const sandbox = await daytona.v2.create();

// Start VNC processes
await sandbox.computerUse.start();

// Click to open browser
await sandbox.computerUse.mouse.click({ x: 50, y: 50, button: 'left' });

// Type a URL
await sandbox.computerUse.keyboard.type({ text: 'https://www.daytona.io/docs/' });
await sandbox.computerUse.keyboard.key({ key: 'enter' });

// Take a screenshot
const screenshot = await sandbox.computerUse.screen.screenshot();
```

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- `start()` resolves once the X server and the VNC server accept connections.
- Later calls do not need to poll the status.
- On a failed desktop, call `restart()` instead of `start()`.
</Callout>

</TabItem>
</Tabs>

## Permissions [#permissions]

VNC access runs through the toolbox of a sandbox. Access is governed by one project permission, which you can grant to an [organization role](/docs/organizations#roles) or an [API key](/docs/api-keys).

A [sandbox access token](/docs/sandboxes#create-an-access-token) with the desktop operation grants the same access to a single sandbox, without an API key. The sandbox read permission alone does not grant toolbox access.

| **Permission**        | **Grants**                              |
| --------------------- | --------------------------------------- |
| **`write:sandboxes`** | Start, stop, and connect to the desktop |
