# Computer Use (/docs/computer-use)

Computer Use enables programmatic control of desktop environments within sandboxes. It provides mouse, keyboard, screenshot, screen recording, accessibility, and display operations for automating GUI interactions and testing desktop applications.

Computer Use and [VNC](/docs/vnc-access) work together to enable both manual and automated desktop interactions. VNC provides the visual interface for users to manually interact with the desktop, while Computer Use provides the programmatic API for AI agents to automate operations.

The desktop stack (D-Bus, Xvfb, the 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 images with no desktop packages. The virtual display runs at 1280x720. All Computer Use calls go through the toolbox API and require an API key with the sandbox write permission or a sandbox access token with the desktop or toolbox operation.

<ComputerUseVisual />

## Use cases [#use-cases]

Computer Use is used when a task needs a graphical desktop instead of an API or a shell.

- **Computer-use agents.** An agent reads screenshots and the accessibility tree, then moves the mouse and types to operate desktop applications. The desktop stack ships with the sandbox runtime, so it works on any Linux sandbox image. All Computer Use calls go through the toolbox API and require an API key with the sandbox write permission or a sandbox access token with the desktop or toolbox operation.

- **Desktop application tests.** GUI tests click, type, and take screenshots to check the state of an application. Full-screen and region screenshots return a PNG image as a base64 string. The virtual display has a fixed resolution of 1280x720, with no setting per sandbox.

- **Browser automation.** A browser on the sandbox desktop is driven through mouse and keyboard input, for sites that have no API. Typing sends any text, including symbols and non-ASCII characters. A delay between characters slows typing for applications that drop fast input.

- **Agents on Windows and macOS.** Windows and macOS sandboxes provide screenshots, mouse and keyboard input, the accessibility tree, and screen recordings through the same API. On Windows, a desktop session must be open. When that session ends or another session replaces it, the desktop fails with a cause that names the reason.

- **Human in the loop.** A person watches or takes over the same desktop through [VNC](/docs/vnc-access) while an agent works. Starting Computer Use also starts the x11vnc server. The VNC server runs in shared mode, so several viewers attach to the same desktop at once.

## Start Computer Use [#start-computer-use]

Start all computer use processes (D-Bus, Xvfb, the window manager, and the x11vnc server) in the sandbox. The call blocks until the X server and the VNC server accept connections and returns the status of each process. If either does not come up within 5 s, the desktop is marked failed and the call returns an error. Calling start on a desktop that is already ready returns the current status. A start made while another start or a restart is in progress waits for it to finish, then returns the resulting status, or an error if the desktop failed. Calling start on a failed desktop returns an error; call [restart](#restart-computer-use) instead.

<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("Computer use processes started:", result.message)
for name, process in result.status.items():
    print(f"{name}: running={process.running} pid={process.pid}")
```

</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('Computer use processes started:', result.message);
for (const [name, process] of Object.entries(result.status)) {
  console.log(`${name}: running=${process.running} pid=${process.pid}`);
}
```

</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 Computer Use [#stop-computer-use]

Stop all computer use processes in the sandbox. Stopping a ready desktop returns it to not started. Stopping a failed desktop kills any remaining processes but keeps the failed state and its cause; only [restart](#restart-computer-use) leaves the failed state.

<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("Computer use 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('Computer use 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>

## Restart Computer Use [#restart-computer-use]

Tear the desktop down and build a new one. Restart is valid from every state and is the only way out of the failed state. Concurrent restart calls join the same in-flight restart and receive the same result. Application windows do not survive a restart.

<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(result.message, result.desktop)  # computer use restarted ready
```

</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(result.message, result.desktop); // computer use restarted ready
```

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

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

</TabItem>
</Tabs>

## Get status [#get-status]

Get the desktop status. The status field is active only while the desktop field is ready.

| **`desktop`**     | **`status`** | **Meaning**                                                                                               |
| ----------------- | ------------ | --------------------------------------------------------------------------------------------------------- |
| `not_started` | `inactive`   | No desktop exists. Call [start](#start-computer-use).                                                     |
| `starting`    | `inactive`   | Start is in progress. Poll status.                                                                        |
| `ready`       | `active`     | Actions are accepted.                                                                                     |
| `failed`      | `inactive`   | The desktop is gone. `cause.code` and `cause.message` explain why. Call [restart](#restart-computer-use). |

The cause object is present only when the desktop is failed. Possible cause codes:

| **Code**                      | **Meaning**                                                                                   |
| ----------------------------- | --------------------------------------------------------------------------------------------- |
| **`desktop_exited`**          | The X server (`xvfb`) exited. Windows in it are lost.                                         |
| **`addon_crashed`**           | The add-on that supervises the desktop died while the desktop was ready.                      |
| **`start_failed`**            | Creating the desktop or probing its readiness failed, for example a missing binary.           |
| **`start_interrupted`**       | The add-on died while the desktop was being created.                                          |
| **`state_corrupt`**           | Retained desktop state could not be read or validated.                                        |
| **`state_persistence_failed`** | A state transition could not be persisted.                                                    |
| **`session_ended`**           | Windows and macOS only. The interactive desktop session ended or left the console.            |
| **`session_replaced`**        | Windows only. Another interactive desktop session replaced the one the desktop was bound to.  |

Failure evidence is boot-scoped: a sandbox reboot resets the desktop to not started. The SDKs report a state or cause code they do not recognize as `unknown` and keep the API value in `raw_status`, `raw_desktop`, and `cause.raw_code` (`rawStatus`, `rawDesktop`, and `cause.rawCode` in TypeScript).

<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("Computer use status:", status.status, status.desktop)
if status.desktop == "failed":
    print(f"Failure: {status.cause.code}: {status.cause.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 status = await sandbox.computerUse.status();
console.log('Computer use status:', status.status, status.desktop);
if (status.desktop === 'failed') {
  console.log(`Failure: ${status.cause.code}: ${status.cause.message}`);
}
```

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

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

</TabItem>
</Tabs>

## Get process status [#get-process-status]

Get the status of one desktop process: D-Bus, Xvfb, the window manager, or the x11vnc server. Without a name, it returns the [status of every process](#get-all-process-statuses).

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

```python
from daytona import Daytona

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

xvfb_status = sandbox.computer_use.service.status("xvfb")
print(xvfb_status.process_name, xvfb_status.running)
```

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- The process names are `dbus`, `xvfb`, `wm`, and `x11vnc`.
</Callout>

</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 xvfbStatus = await sandbox.computerUse.service.status('xvfb');
console.log(xvfbStatus.processName, xvfbStatus.running);
```

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- The process names are `dbus`, `xvfb`, `wm`, and `x11vnc`.
</Callout>

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

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

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- The process names are `dbus`, `xvfb`, `wm`, and `x11vnc`.
</Callout>

</TabItem>
</Tabs>

## Get all process statuses [#get-all-process-statuses]

Get the status of every desktop process. The response is a map keyed by process name, with one entry each for D-Bus, Xvfb, the window manager, and the x11vnc server while a desktop exists. The map is empty before the first start, after stop, after a failed start, and after the add-on restarts. Each entry reports whether the process is alive, its supervisor restart state, and its startup priority.

| **API field**          | **Description**                                                     |
| ---------------------- | ------------------------------------------------------------------- |
| **`Running`**          | Whether the process is alive.                                       |
| **`Pid`**              | Process ID while the process is running.                            |
| **`Status`**           | `running`, `stopped`, or `failed`.                      |
| **`Failed`**           | Whether the supervisor gave up on the process: `xvfb` after it exits, the other processes after their automatic restarts run out. A process restart or a desktop restart clears it. |
| **`AutoRestart`**      | Whether the supervisor restarts the process automatically.          |
| **`RestartAttempts`**  | Exits in a row within 30 s of starting. It reaches `6` when the supervisor marks the process failed. Resets after a run of 30 s or longer, a process restart, or a desktop start. |
| **`RestartBackoffMs`** | Current restart backoff in milliseconds. The SDKs return it in seconds, as `restart_backoff` in Python and `restartBackoffSeconds` in TypeScript. |
| **`Priority`**         | Relative startup priority within the desktop stack.                 |

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

```python
from daytona import Daytona

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

for name, process in sandbox.computer_use.service.status().items():
    print(f"{name}: {process.status} pid={process.pid} restarts={process.restart_attempts}")
```

</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 statuses = await sandbox.computerUse.service.status();
for (const [name, process] of Object.entries(statuses)) {
  console.log(`${name}: ${process.status} pid=${process.pid} restarts=${process.restartAttempts}`);
}
```

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

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

</TabItem>
</Tabs>

## Restart process [#restart-process]

Restart a specific desktop process. Per-process restart works only while the desktop is ready. The Xvfb process cannot be restarted on its own because application windows live inside it; use [restart](#restart-computer-use) instead.

The D-Bus, window manager, and x11vnc processes are restarted automatically when they exit. A process that exits within 30 s of starting is restarted at most 5 times in a row, after waits of 1, 2, 4, 8, and 16 s. The sixth such exit marks it failed in the status map, while the desktop stays ready. A run that lasts 30 s or longer resets the count. Restarting a failed process resets its count and starts it again.

<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.service.restart("wm")
print(result.message, result.process_name)
```

</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.service.restart('wm');
console.log(result.message, result.processName);
```

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

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

</TabItem>
</Tabs>

## Get process logs [#get-process-logs]

Get logs for a specific desktop process. Each log is checked every 5 s and whenever the process starts; once it exceeds 256 KiB, it is cut to its most recent 128 KiB. Logs accumulate across process and desktop restarts. Logs and errors are readable only while a desktop exists, including after Xvfb exits.

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

```python
from daytona import Daytona

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

logs = sandbox.computer_use.service.logs("x11vnc")
print("x11vnc logs:", logs.logs)
```

The response has the following fields:

| **Field** | **Description** |
| --------- | --------------- |
| `logs` | Holds the retained standard output of the process. |

</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 logs = await sandbox.computerUse.service.logs('x11vnc');
console.log('x11vnc logs:', logs.logs);
```

The response has the following fields:

| **Field** | **Description** |
| --------- | --------------- |
| `logs` | Holds the retained standard output of the process. |

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

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

</TabItem>
</Tabs>

## Get process errors [#get-process-errors]

Get error logs for a specific desktop process.

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

```python
from daytona import Daytona

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

errors = sandbox.computer_use.service.errors("x11vnc")
print("x11vnc errors:", errors.errors)
```

The response has the following fields:

| **Field** | **Description** |
| --------- | --------------- |
| `errors` | Holds the retained standard error output of the process, plus lines the supervisor adds when the process cannot be started or runs out of automatic restarts. Trimmed like the logs. The value is an empty string when nothing was written. |

</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 errors = await sandbox.computerUse.service.errors('x11vnc');
console.log('x11vnc errors:', errors.errors);
```

The response has the following fields:

| **Field** | **Description** |
| --------- | --------------- |
| `errors` | Holds the retained standard error output of the process, plus lines the supervisor adds when the process cannot be started or runs out of automatic restarts. Trimmed like the logs. The value is an empty string when nothing was written. |

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

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

</TabItem>
</Tabs>

## Mouse operations [#mouse-operations]

Every mouse, keyboard, screenshot, display, accessibility, and recording-start call requires the desktop to be ready; otherwise it returns 409 with a desktop not ready error. Coordinates are screen pixels in the range -32768 to 32767; values outside it return 400.

### Click [#click]

Click the mouse at the specified coordinates with the left, right, or middle button, as a single or a double click.

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

```python
from daytona import Daytona

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

sandbox.computer_use.mouse.click(x=100, y=200)
```

| **Parameter** | **Type**                 | **Description**      |
| ------------- | ------------------------ | -------------------- |
| `button` | ComputerUseMouseButton | Sets the mouse button. The default is `"left"`. |
| `double` | bool | Makes the click a double click. The default is `False`. |

</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();

await sandbox.computerUse.mouse.click({ x: 100, y: 200 });
```

| **Parameter** | **Type**                 | **Description**      |
| ------------- | ------------------------ | -------------------- |
| `button` | ComputerUseMouseButton | Sets the mouse button. The default is `'left'`. |
| `double` | boolean | Makes the click a double click. The default is `false`. |

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/mouse/click' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{
  "x": 100,
  "y": 200
}'
```

| **Field** | **Type**  | **Description**     |
| --------- | --------- | ------------------- |
| `button` | string | Sets the mouse button. The default is `left`. |
| `double` | boolean | Makes the click a double click. The default is `false`. |

The response has the following fields:

| **Field** | **Description** |
| --------- | --------------- |
| `x` | Holds the x coordinate of the pointer after the click. |
| `y` | Holds the y coordinate of the pointer after the click. |

</TabItem>
</Tabs>

### Move [#move]

Move the mouse cursor to the given coordinates. The response holds the final pointer position.

<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.mouse.move(x=100, y=200)
print(f"Mouse moved to: {result.x}, {result.y}")
```

</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.mouse.move({ x: 100, y: 200 });
console.log(`Mouse moved to: ${result.x}, ${result.y}`);
```

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/mouse/move' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{
  "x": 100,
  "y": 200
}'
```

</TabItem>
</Tabs>

### Drag [#drag]

Drag the mouse from start to end coordinates while holding the selected button. The pointer moves in 10 interpolated steps so applications that ignore a teleporting drag still register it.

<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.mouse.drag(
    start_x=50, start_y=50, end_x=150, end_y=150, button="left"
)
print(f"Drag ended at {result.x}, {result.y}")
```

| **Parameter** | **Type**                 | **Description**    |
| ------------- | ------------------------ | ------------------ |
| `button` | ComputerUseMouseButton | Sets the mouse button to hold. The default is `"left"`. |

</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.mouse.drag({
  startX: 50,
  startY: 50,
  endX: 150,
  endY: 150,
  button: 'left',
});
console.log(`Drag ended at ${result.x}, ${result.y}`);
```

| **Parameter** | **Type**                 | **Description** |
| ------------- | ------------------------ | --------------- |
| `button` | ComputerUseMouseButton | Sets the mouse button to hold. The default is `'left'`. |

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/mouse/drag' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{
  "button": "left",
  "endX": 150,
  "endY": 150,
  "startX": 50,
  "startY": 50
}'
```

| **Field** | **Type** | **Description**  |
| --------- | -------- | ---------------- |
| `button` | string | Sets the mouse button to hold. The default is `left`. |

</TabItem>
</Tabs>

### Scroll [#scroll]

Scroll the mouse wheel up or down at the specified coordinates by a number of wheel ticks. One tick is one notch of a physical mouse wheel.

<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.mouse.scroll(x=100, y=200, direction="down")
print(result.success)
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `amount` | int | Sets the number of wheel ticks. The value must not be negative, and a value above `100` is capped at `100`. The default is `1`. |

</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.mouse.scroll({ x: 100, y: 200, direction: 'down' });
console.log(result.success);
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `amount` | number | Sets the number of wheel ticks. The value must not be negative, and a value above `100` is capped at `100`. The default is `1`. |

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/mouse/scroll' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{
  "direction": "down",
  "x": 100,
  "y": 200
}'
```

| **Field** | **Type** | **Description** |
| --------- | -------- | --------------- |
| `amount` | integer | Sets the number of wheel ticks. The value must not be negative, and a value above `100` is capped at `100`. The default is `1`. |

</TabItem>
</Tabs>

### Get position [#get-position]

Get the current mouse cursor position.

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

```python
from daytona import Daytona

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

position = sandbox.computer_use.mouse.position()
print(f"Mouse is at: {position.x}, {position.y}")
```

</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 position = await sandbox.computerUse.mouse.position();
console.log(`Mouse is at: ${position.x}, ${position.y}`);
```

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

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

</TabItem>
</Tabs>

## Keyboard operations [#keyboard-operations]

Send keyboard input to the desktop. Type a text string, press one key with optional modifiers, or press a hotkey combination. Key names resolve against the keyboard map of the virtual display, and, like mouse calls, keyboard calls require the desktop to be ready.

### Type [#type]

Type arbitrary text, including uppercase letters, symbols, and non-ASCII characters. Newlines are sent as Enter and tabs as Tab. Characters with no key in the X11 keymap are typed through a temporary key mapping.

Set a delay between characters to slow typing down for applications that drop fast input.

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

```python
from daytona import Daytona

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

sandbox.computer_use.keyboard.type(text="Hello, World!")
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `delay` | float | Sets the pause between characters in seconds, rounded up to whole milliseconds. A value above `1` is capped at `1`. A negative value raises `BadRequestError`. The default is `0`. |

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- The call sends `\n` and `\r` as the Enter key, so a `\r\n` pair presses Enter twice.
- The text is limited to 4096 Unicode code points.
- The number of characters multiplied by the capped delay must not exceed 25 seconds; a longer request returns `400` before any key is typed.
</Callout>

</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();

await sandbox.computerUse.keyboard.type({ text: 'Hello, World!' });
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `delaySeconds` | number | Sets the pause between characters in seconds, rounded up to whole milliseconds. A value above `1` is capped at `1`. A negative value throws `BadRequestError`. The default is `0`. |

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- The call sends `\n` and `\r` as the Enter key, so a `\r\n` pair presses Enter twice.
- The text is limited to 4096 Unicode code points.
- The number of characters multiplied by the capped delay must not exceed 25 seconds; a longer request returns `400` before any key is typed.
</Callout>

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/keyboard/type' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{
  "text": "Hello, World!"
}'
```

| **Field** | **Type** | **Description** |
| --------- | -------- | --------------- |
| `delay` | integer | Sets the pause between characters in milliseconds. A value above `1000` is capped at `1000`. The default is `0`. |

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- The call sends `\n` and `\r` as the Enter key, so a `\r\n` pair presses Enter twice.
- The text is limited to 4096 Unicode code points.
- The number of characters multiplied by the capped delay must not exceed 25 seconds; a longer request returns `400` before any key is typed.
</Callout>

</TabItem>
</Tabs>

### Press [#press]

Press a key with optional modifiers. Modifiers are held in order, the key is pressed and released, and the modifiers are released in reverse, even if the press fails. See [supported keys](#supported-keys).

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

```python
from daytona import Daytona

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

sandbox.computer_use.keyboard.key(key="enter")
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `modifiers` | Sequence[ComputerUseKeyboardModifier] | Sets the modifier keys to hold during the press: `ctrl`, `alt`, `shift`, or `cmd`. |

</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();

await sandbox.computerUse.keyboard.key({ key: 'enter' });
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `modifiers` | ComputerUseKeyboardModifier[] | Sets the modifier keys to hold during the press: `ctrl`, `alt`, `shift`, or `cmd`. |

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/keyboard/key' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{
  "key": "enter"
}'
```

| **Field** | **Type** | **Description** |
| --------- | -------- | --------------- |
| `modifiers` | string[] | Sets the keys to hold during the press, in order. Accepts any name in [supported keys](#supported-keys); a name not in the keymap returns `400`. |

</TabItem>
</Tabs>

### Hotkey [#hotkey]

Press a hotkey combination. The combination is a plus-separated chord string: the last entry is the key, the others are modifiers.

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

```python
from daytona import Daytona

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

sandbox.computer_use.keyboard.hotkey(keys="ctrl+c")
```

</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();

await sandbox.computerUse.keyboard.hotkey({ keys: 'ctrl+c' });
```

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/keyboard/hotkey' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{
  "keys": "ctrl+c"
}'
```

</TabItem>
</Tabs>

### Supported keys [#supported-keys]

Named keys and aliases are case-insensitive. A single character keeps its case: `A` presses Shift and `a`. The following are supported for the key and modifiers parameters and for the parts of the keys parameter:

| Category      | Keys                                                                                                          |
| ------------- | ------------------------------------------------------------------------------------------------------------- |
| Modifiers     | `ctrl` (alias `control`), `alt`, `shift`, `cmd` (aliases `super`, `meta`, `win`)                             |
| Editing       | `enter` (alias `return`), `tab`, `escape` (alias `esc`), `backspace`, `delete` (alias `del`), `space`        |
| Navigation    | `home`, `end`, `pageup` (alias `page_up`), `pagedown` (alias `page_down`), `insert`, `up`, `down`, `left`, `right` |
| Function keys | `f1` through `f12`                                                                                            |
| Symbols       | `minus`, `plus`, `equal` (alias `equals`)                                                                     |
| Characters    | Any single character in the keymap of the display, for example `a`, `A`, `5`, `/`, `-`. In press and hotkey, a character outside the keymap returns `400`; use [type](#type) for it. In the keys parameter, write `plus` for `+`. |

The SDK modifiers type lists ctrl, alt, shift, and cmd; the API accepts those, their aliases, and any other supported key name.

## Accessibility operations [#accessibility-operations]

Use accessibility operations to inspect the AT-SPI tree and interact with UI elements by node ID. Start Computer Use before calling accessibility methods.

Each node carries an ID, role, name, bounds (x, y, width, height in screen pixels), and states. The description, actions, and children appear only when not empty, and find matches never include children. A node without the AT-SPI component interface reports zero bounds. The role is the AT-SPI role name, passed through unchanged. Node states outside the list in [find nodes](#find-nodes) appear as `state:<n>`, where `<n>` is the AT-SPI state number. On Linux, the value field is never set.

A node ID joins the bus name and the object path of the element with a colon, and is valid only while the owning application keeps the object alive. A malformed ID returns `400`. An unknown ID returns `404` from focus and invoke, and `400` from set value.

The scope parameter selects the root of the search:

| **Scope**     | **Root**                                                                                           |
| ------------- | -------------------------------------------------------------------------------------------------- |
| **`focused`** | The application whose state includes `active`, else the first registered application. Default.     |
| **`pid`**     | The application owned by process `pid`. Returns `404` if no accessible application has that PID.   |
| **`all`**     | The AT-SPI registry root, covering every application.                                              |

Traversal is bounded: 2000 nodes and 2 s for the tree, 5000 nodes and 2 s for find. When a bound is reached, the response is marked as truncated. Cutting the tree at the maximum depth does not mark it as truncated.

<Callout type="note" title="App accessibility support">
Accessibility operations read the semantic UI information that applications expose over AT-SPI. Apps or custom widgets that do not expose accessibility objects may return sparse nodes, generic roles, or no actionable nodes; mouse, keyboard, and screenshot operations remain available for those cases.
</Callout>

### Get tree [#get-tree]

Read an accessibility tree for the focused app, a specific process, or all apps. The maximum depth parameter limits the depth below the root, and 0 returns only the root. The SDKs return at most 256 levels. The API returns the whole subtree within the traversal budget when the parameter is omitted or -1.

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

```python
from daytona import Daytona

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

tree = sandbox.computer_use.a11y.tree(scope="focused")
print(tree.root.role, tree.root.name, tree.truncated)
```

| **Parameter** | **Type**               | **Description**                                     |
| ------------- | ---------------------- | --------------------------------------------------- |
| `scope` | ComputerUseA11yScope | Sets the root of the search: `"focused"`, `"pid"`, or `"all"`. This parameter is required. With `scope="pid"`, also pass `pid`. |
| `pid` | int | Selects the application when `scope` is `"pid"`. |
| `max_depth` | int | Limits the depth below the root, from `0` to `256`. A value outside the range raises `BadRequestError`. The default is `256`. |

</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 tree = await sandbox.computerUse.a11y.tree({ scope: 'focused' });
console.log(tree.root.role, tree.root.name, tree.truncated);
```

| **Parameter** | **Type**               | **Description**                                     |
| ------------- | ---------------------- | --------------------------------------------------- |
| `scope` | ComputerUseA11yScope | Sets the root of the search: `'focused'`, `'pid'`, or `'all'`. This parameter is required. With `scope: 'pid'`, also pass `pid`. |
| `pid` | number | Selects the application when `scope` is `'pid'`. |
| `maxDepth` | number | Limits the depth below the root, from `0` to `256`. A value outside the range throws `BadRequestError`. The default is `256`. |

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/a11y/tree?scope=focused' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

| **Field**  | **Type**  | **Description**                                         |
| ---------- | --------- | ------------------------------------------------------- |
| `scope` | string | Sets the root of the search. An unknown scope returns `400`. The default is `focused`. |
| `pid` | integer | Selects the application when `scope=pid`. |
| `maxDepth` | integer | Limits the depth below the root. Omit the parameter or pass `-1` to get the whole subtree. The query parameter name is camelCase. |

</TabItem>
</Tabs>

### Find nodes [#find-nodes]

Search the accessibility tree by role, accessible name, state, and scope. All filters must match. Name matching is case-sensitive. The regex mode uses the syntax of the Rust `regex-lite` crate and matches anywhere in the name unless the pattern is anchored with `^` and `$`; an invalid pattern returns `400`. Find visits nodes depth first, last child first, so the first match is not necessarily the first element on screen.

Node states: invalid, active, armed, busy, checked, collapsed, defunct, editable, enabled, expandable, expanded, focusable, focused, has-tooltip, horizontal, iconified, modal, multi-line, multiselectable, opaque, pressed, resizable, selectable, selected, sensitive, showing, single-line, stale, transient, vertical, visible, manages-descendants, indeterminate, required, truncated, and animated.

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

```python
from daytona import Daytona

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

buttons = sandbox.computer_use.a11y.find(scope="focused", role="button", name="Submit")
for node in buttons.matches:
    print(node.id, node.name, node.bounds.x, node.bounds.y)
```

| **Parameter** | **Type**                   | **Description**                                                              |
| ------------- | -------------------------- | ---------------------------------------------------------------------------- |
| `scope` | ComputerUseA11yScope | Sets the root of the search. This parameter is required. |
| `pid` | int | Selects the application when `scope` is `"pid"`. |
| `role` | str | Matches the node role exactly, for example `button` or `entry`. |
| `name` | str | Matches the accessible name according to `name_match`. |
| `name_match` | ComputerUseA11yNameMatch | Sets how `name` is compared: `exact`, `substring`, or `regex`. The default is `exact`. |
| `states` | Sequence\[str\] | Requires the node to have every listed state, for example `["enabled", "focusable"]`. |
| `limit` | int | Caps the number of matches. A value of `0` or less uses the default. With more matches, `truncated` is `True`. The default is `50`. |

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- Filters other than `scope` are optional, and an omitted filter matches every node.
</Callout>

</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 buttons = await sandbox.computerUse.a11y.find({ scope: 'focused', role: 'button', name: 'Submit' });
for (const node of buttons.matches) {
  console.log(node.id, node.name, node.bounds.x, node.bounds.y);
}
```

| **Parameter** | **Type**                   | **Description**                                                              |
| ------------- | -------------------------- | ---------------------------------------------------------------------------- |
| `scope` | ComputerUseA11yScope | Sets the root of the search. This parameter is required. |
| `pid` | number | Selects the application when `scope` is `'pid'`. |
| `role` | string | Matches the node role exactly, for example `'button'` or `'entry'`. |
| `name` | string | Matches the accessible name according to `nameMatch`. |
| `nameMatch` | ComputerUseA11yNameMatch | Sets how `name` is compared: `'exact'`, `'substring'`, or `'regex'`. The default is `'exact'`. |
| `states` | string\[\] | Requires the node to have every listed state, for example `['enabled', 'focusable']`. |
| `limit` | number | Caps the number of matches. A value of `0` or less uses the default. With more matches, `truncated` is `true`. The default is `50`. |

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- Filters other than `scope` are optional, and an omitted filter matches every node.
</Callout>

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/a11y/find' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{
  "scope": "focused",
  "role": "button",
  "name": "Submit"
}'
```

| **Field**   | **Type**   | **Description**                                                                              |
| ----------- | ---------- | -------------------------------------------------------------------------------------------- |
| `scope` | string | Sets the root of the search. The default is `focused`. |
| `pid` | integer | Selects the application when `scope` is `pid`. |
| `role` | string | Matches the node role exactly, for example `button` or `entry`. |
| `name` | string | Matches the accessible name according to `nameMatch`. |
| `nameMatch` | string | Sets how `name` is compared: `exact`, `substring`, or `regex`. The default is `exact`. |
| `states` | string\[\] | Requires the node to have every listed state, for example `["enabled", "focusable"]`. |
| `limit` | integer | Caps the number of matches. A value of `0` or less uses the default. With more matches, `truncated` is `true`. The default is `50`. |

</TabItem>
</Tabs>

### Focus node [#focus-node]

Focus a node from [get tree](#get-tree) or [find nodes](#find-nodes). The call asks the application to give the node focus; if the application declines, the call returns `400`.

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

```python
from daytona import Daytona

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

node = sandbox.computer_use.a11y.find(scope="focused", role="button", name="Submit").matches[0]
sandbox.computer_use.a11y.focus(id=node.id)
```

</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 node = (await sandbox.computerUse.a11y.find({ scope: 'focused', role: 'button', name: 'Submit' })).matches[0];
await sandbox.computerUse.a11y.focus({ id: node.id });
```

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/a11y/node/focus' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"id":"node-id"}'
```

</TabItem>
</Tabs>

### Invoke node [#invoke-node]

Run a node action, such as pressing a button. Without an action name, the node's click action is invoked, falling back to its first action. An action name must appear in the node's actions list and is case-sensitive. A node with an empty actions list, or an action the application rejects, returns `400`. A node without the AT-SPI action interface returns `404`.

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

```python
from daytona import Daytona

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

node = sandbox.computer_use.a11y.find(scope="focused", role="button", name="Submit").matches[0]
sandbox.computer_use.a11y.invoke(id=node.id)
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `action` | str | Sets the action to run. The name must appear in the node's `actions` list. The default is the node's click action, or its first action without one. |

</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 node = (await sandbox.computerUse.a11y.find({ scope: 'focused', role: 'button', name: 'Submit' })).matches[0];
await sandbox.computerUse.a11y.invoke({ id: node.id });
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `action` | string | Sets the action to run. The name must appear in the node's `actions` list. The default is the node's click action, or its first action without one. |

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/a11y/node/invoke' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"id":"node-id"}'
```

| **Field** | **Type** | **Description** |
| --------- | -------- | --------------- |
| `action` | string | Sets the action to run. The name must appear in the node's `actions` list. The default is the node's click action, or its first action without one. |

</TabItem>
</Tabs>

### Set node value [#set-node-value]

Write text content to nodes that implement editable text, such as entries and text areas. The value replaces the current content.

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

```python
from daytona import Daytona

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

node = sandbox.computer_use.a11y.find(scope="focused", role="entry").matches[0]
sandbox.computer_use.a11y.set_value(id=node.id, value="hello")
```

</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 node = (await sandbox.computerUse.a11y.find({ scope: 'focused', role: 'entry' })).matches[0];
await sandbox.computerUse.a11y.setValue({ id: node.id, value: 'hello' });
```

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/a11y/node/value' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"id":"node-id","value":"hello"}'
```

| **Field** | **Type** | **Description** |
| --------- | -------- | --------------- |
| `value` | string | Sets the new text, which replaces the current content. An omitted value clears the field. |

</TabItem>
</Tabs>

## Screenshot operations [#screenshot-operations]

Screenshots return the image as a base64 string, its encoded size in bytes, and, when the cursor is requested, the pointer coordinates. The cursor is never drawn. Full-screen and region screenshots are PNG; compressed variants accept format, quality, and scale parameters. The SDKs take every variant with one screenshot method: a region selects the region variant, and setting the format, quality, or scale selects the compressed variant. The show cursor parameter works with every variant and does not select compression.

### Take full screen [#take-full-screen]

Take a screenshot of the entire screen.

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

```python
import base64

from daytona import Daytona

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

screenshot = sandbox.computer_use.screen.screenshot()
print(f"Screenshot size: {screenshot.size_bytes} bytes")
with open("screen.png", "wb") as f:
    f.write(base64.b64decode(screenshot.screenshot))
```

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

```typescript
import { writeFile } from 'node:fs/promises';
import { Daytona } from '@daytona/sdk';

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

const screenshot = await sandbox.computerUse.screen.screenshot();
console.log(`Screenshot size: ${screenshot.sizeBytes} bytes`);
await writeFile('screen.png', Buffer.from(screenshot.screenshot, 'base64'));
```

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

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

</TabItem>
</Tabs>

### Take region [#take-region]

Take a screenshot of a specific region. The width and height must be positive, and the region must lie within the screen.

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

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

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

screenshot = sandbox.computer_use.screen.screenshot(
    region=ComputerUseRegion(x=100, y=100, width=300, height=200)
)
print(f"Captured region: {screenshot.size_bytes} bytes")
```

</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 screenshot = await sandbox.computerUse.screen.screenshot({
  region: { x: 100, y: 100, width: 300, height: 200 },
});
console.log(`Captured region: ${screenshot.sizeBytes} bytes`);
```

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/screenshot/region?x=100&y=100&width=300&height=200' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

</TabItem>
</Tabs>

### Take compressed [#take-compressed]

Take a compressed screenshot of the entire screen.

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

```python
from daytona import Daytona

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

screenshot = sandbox.computer_use.screen.screenshot(format="jpeg", quality=80)
print(f"Compressed size: {screenshot.size_bytes} bytes")
```

| **Parameter** | **Type** | **Description**                                                 |
| ------------- | -------- | --------------------------------------------------------------- |
| `format` | str | Sets the image format, `png` or `jpeg`. The default is `png`. |
| `quality` | int | Sets the image quality, from `1` to `100`. The default is `85`. The quality applies to `jpeg`. |
| `scale` | float | Resizes the captured image by a factor from `0.1` to `1.0`. The default is `1.0`. |
| `show_cursor` | bool | Returns the pointer coordinates with the screenshot. The cursor is never drawn. The default is `False`. |

</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 screenshot = await sandbox.computerUse.screen.screenshot({ format: 'jpeg', quality: 80 });
console.log(`Compressed size: ${screenshot.sizeBytes} bytes`);
```

| **Parameter** | **Type**  | **Description**                                             |
| ------------- | --------- | ----------------------------------------------------------- |
| `format` | string | Sets the image format, `'png'` or `'jpeg'`. The default is `'png'`. |
| `quality` | number | Sets the image quality, from `1` to `100`. The default is `85`. The quality applies to `'jpeg'`. |
| `scale` | number | Resizes the captured image by a factor from `0.1` to `1.0`. The default is `1.0`. |
| `showCursor` | boolean | Returns the pointer coordinates with the screenshot. The cursor is never drawn. The default is `false`. |

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/screenshot/compressed?format=jpeg&quality=80' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

| **Field**     | **Type**  | **Description**                                             |
| ------------- | --------- | ----------------------------------------------------------- |
| `format` | string | Sets the image format, `png` or `jpeg`. The default is `png`. |
| `quality` | integer | Sets the image quality, from `1` to `100`. The quality applies to `jpeg`. The default is `85`. |
| `scale` | number | Resizes the captured image by a factor from `0.1` to `1.0`. The default is `1.0`. |
| `show_cursor` | boolean | Returns the pointer coordinates with the screenshot. The cursor is never drawn. The default is `false`. |

</TabItem>
</Tabs>

### Take compressed region [#take-compressed-region]

Take a compressed screenshot of a specific region. Accepts the region parameters of [take region](#take-region) and the compression parameters of [take compressed](#take-compressed).

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

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

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

screenshot = sandbox.computer_use.screen.screenshot(
    region=ComputerUseRegion(x=0, y=0, width=800, height=600), format="jpeg", quality=80
)
print(f"Compressed size: {screenshot.size_bytes} bytes")
```

</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 screenshot = await sandbox.computerUse.screen.screenshot({
  region: { x: 0, y: 0, width: 800, height: 600 },
  format: 'jpeg',
  quality: 80,
});
console.log(`Compressed size: ${screenshot.sizeBytes} bytes`);
```

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/screenshot/region/compressed?x=0&y=0&width=800&height=600&format=jpeg&quality=80' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

</TabItem>
</Tabs>

## Screen Recording [#screen-recording]

Computer Use records desktop sessions for debugging, documentation, or automation workflows. Recordings are MP4 files captured at 10 frames per second and stored inside the sandbox; the file path field in every recording response reports the location. Video is encoded with MPEG-4 Part 2, not H.264, so some browsers cannot play it inline.

Each recording has the following fields. TypeScript and the API use the camelCase names; Python uses the snake_case names.

| **Field**              | **Description**                                                                                           |
| ---------------------- | --------------------------------------------------------------------------------------------------------- |
| `id`               | Recording ID, prefixed `rec_`. Used for stop, get, download, and delete.                                 |
| `fileName`, `file_name` | `<label>-<id>.mp4`. Label characters other than letters, digits, and `-` are replaced with `_`. The label defaults to `recording`. |
| `filePath`, `file_path` | Path of the file inside the sandbox.                                                                 |
| `startTime`, `start_time` | Start time: an RFC 3339 string in TypeScript and the API, a `datetime` in Python.                 |
| `endTime`, `end_time` | End time, in the same type as the start time. Absent until the recording is finalized.               |
| `status`           | See the status table below.                                                                               |
| `durationSeconds`, `duration_seconds` | Elapsed seconds while active, or the final duration.                                   |
| `sizeBytes`, `size_bytes` | Current file size while active, or the final size.                                                 |

| **Status**                   | **Meaning**                                                                          |
| ---------------------------- | ------------------------------------------------------------------------------------ |
| `recording`              | Capturing.                                                                           |
| `stopping`               | Stop was requested and the encoder is finalizing the file.                          |
| `duration_limit_reached` | The 15 minute cap was hit. The recording keeps this status once the file is finalized. |
| `size_limit_reached`     | The 512 MiB cap was hit. The recording keeps this status once the file is finalized.  |
| `completed`              | Terminal. The file is ready to download.                                             |
| `interrupted`            | Terminal. The encoder did not exit after a stop or a cap and was killed; the file may be incomplete. |
| `failed`                 | Terminal. The encoder exited on its own with an error, or the file is smaller than 1 KiB. The file is deleted, so download returns `404`. |

Limits:

- At most 2 recordings can be in progress at once, counting recordings that are recording, stopping, or finishing after the 15-minute or 512 MiB cap; a third start returns `400`.
- A recording stops on its own after 15 minutes or 512 MiB, whichever comes first.
- Up to 20 finished recordings are kept, including failed and interrupted ones; when a stop or list call finds more, the oldest are deleted with their files.
- Labels longer than 120 characters are truncated to 120.

### Start Recording [#start-recording]

Start a screen recording with an optional label. The desktop must be ready.

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

```python
from daytona import Daytona

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

recording = sandbox.computer_use.recording.start()
print(f"Recording started: {recording.id}")
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `label` | str | Sets the prefix of the file name. Characters other than letters, digits, and `-` are replaced with `_`, and the result is truncated to 120 characters. The default is `recording`. |

</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 recording = await sandbox.computerUse.recording.start();
console.log(`Recording started: ${recording.id}`);
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `label` | string | Sets the prefix of the file name. Characters other than letters, digits, and `-` are replaced with `_`, and the result is truncated to 120 characters. The default is `'recording'`. |

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/recordings/start' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{}'
```

| **Field** | **Type** | **Description** |
| --------- | -------- | --------------- |
| `label` | string | Sets the prefix of the file name. Characters other than letters, digits, and `-` are replaced with `_`, and the result is truncated to 120 characters. The default is `recording`. |

</TabItem>
</Tabs>

### Stop Recording [#stop-recording]

Stop an active recording session by providing the recording ID. The call returns once the file is finalized, with the terminal recording.

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

```python
from daytona import Daytona

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

stopped_recording = sandbox.computer_use.recording.stop(id=recording.id)
print(f"Recording stopped: {stopped_recording.duration_seconds} seconds")
print(f"Saved to: {stopped_recording.file_path}")
```

</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 recording = await sandbox.computerUse.recording.start();

const stoppedRecording = await sandbox.computerUse.recording.stop({ id: recording.id });
console.log(`Recording stopped: ${stoppedRecording.durationSeconds} seconds`);
console.log(`Saved to: ${stoppedRecording.filePath}`);
```

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/recordings/stop' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{
  "id": "recording-id"
}'
```

</TabItem>
</Tabs>

### List Recordings [#list-recordings]

Get a list of all recordings in the sandbox, active and finished:

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

```python
from daytona import Daytona

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

recordings_list = sandbox.computer_use.recording.list()
print(f"Total recordings: {len(recordings_list.recordings)}")
for rec in recordings_list.recordings:
    print(f"- {rec.file_name}: {rec.status} {rec.duration_seconds}s ({rec.size_bytes} bytes)")
```

</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 recordingsList = await sandbox.computerUse.recording.list();
console.log(`Total recordings: ${recordingsList.recordings.length}`);
for (const rec of recordingsList.recordings) {
  console.log(`- ${rec.fileName}: ${rec.status} ${rec.durationSeconds}s (${rec.sizeBytes} bytes)`);
}
```

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

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

</TabItem>
</Tabs>

### Get Recording [#get-recording]

Get details about a specific recording.

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

```python
from daytona import Daytona

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

recording_detail = sandbox.computer_use.recording.get(recording.id)
print(f"Recording: {recording_detail.file_name}")
print(f"Status: {recording_detail.status}")
print(f"Duration: {recording_detail.duration_seconds}s")
```

</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 recording = await sandbox.computerUse.recording.start();

const recordingDetail = await sandbox.computerUse.recording.get(recording.id);
console.log(`Recording: ${recordingDetail.fileName}`);
console.log(`Status: ${recordingDetail.status}`);
console.log(`Duration: ${recordingDetail.durationSeconds}s`);
```

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

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

</TabItem>
</Tabs>

### Delete Recording [#delete-recording]

Delete a recording by ID. Deleting an active recording stops it first and then removes the file.

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

```python
from daytona import Daytona

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

sandbox.computer_use.recording.delete(recording.id)
print("Recording deleted successfully")
```

</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 recording = await sandbox.computerUse.recording.start();

await sandbox.computerUse.recording.delete(recording.id);
console.log('Recording deleted successfully');
```

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/recordings/{id}' \
  --request DELETE \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

</TabItem>
</Tabs>

### Download Recording [#download-recording]

Download a recording file from the sandbox. The daemon streams the file. The SDKs hold the whole file in memory and return it as bytes; write it to disk yourself. An active recording can be downloaded, and the response holds the file written so far.

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

```python
from pathlib import Path

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()
sandbox.computer_use.start()
recording = sandbox.computer_use.recording.start()
sandbox.computer_use.recording.stop(id=recording.id)

data = sandbox.computer_use.recording.download(recording.id)
Path("local_recording.mp4").write_bytes(data)
print(f"Downloaded {len(data)} bytes")
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `max_response_bytes` | int | Replaces the client's response size cap for this call. A larger recording raises `PayloadTooLargeError`. The default is the client's cap, 1 GiB unless configured. |

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

```typescript
import { writeFile } from 'node:fs/promises';

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

const daytona = new Daytona();
const sandbox = await daytona.v2.create();
await sandbox.computerUse.start();
const recording = await sandbox.computerUse.recording.start();
await sandbox.computerUse.recording.stop({ id: recording.id });

const data = await sandbox.computerUse.recording.download(recording.id);
await writeFile('local_recording.mp4', data);
console.log(`Downloaded ${data.byteLength} bytes`);
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `maxResponseBytes` | number | Replaces the client's response size cap for this call. A larger recording throws `PayloadTooLargeError`. The default is the client's cap, 1 GiB unless configured. |

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/computeruse/recordings/{id}/download' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --output local_recording.mp4
```

The response is an MP4 video with the following headers:

| **Header**                       | **Description**                          |
| -------------------------------- | ---------------------------------------- |
| `Content-Type` | Reports `video/mp4`. |
| `Content-Disposition` | Marks the response as an attachment with the file name. |
| `x-daytona-recording-status` | Reports the recording status. |
| `x-daytona-recording-incomplete` | Reports `true` when the status is anything other than `completed`, including an active recording and the cap statuses. Absent for completed recordings. |

</TabItem>
</Tabs>

## Display operations [#display-operations]

The virtual display is created when Computer Use starts and runs at 1280x720 with 24-bit color. The resolution is fixed, with no per-sandbox setting. Verify the live geometry with [get info](#get-info).

### Get info [#get-info]

Get information about the displays. The response lists every X screen with its ID, position, size, and active flag. The virtual desktop has one screen.

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

```python
from daytona import Daytona

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

info = sandbox.computer_use.display.info()
print(f"Total displays: {len(info.displays)}")
for display in info.displays:
    print(f"Display {display.id}: {display.width}x{display.height} at {display.x},{display.y}")
```

</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 info = await sandbox.computerUse.display.info();
console.log(`Total displays: ${info.displays.length}`);
for (const display of info.displays) {
  console.log(`Display ${display.id}: ${display.width}x${display.height} at ${display.x},${display.y}`);
}
```

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

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

</TabItem>
</Tabs>

### Get windows [#get-windows]

Get the list of open windows. Each window carries its ID, title, position, size, and active flag. The list is read from the client list property that the window manager maintains, which is why start refuses to run without a window manager.

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

```python
from daytona import Daytona

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

windows = sandbox.computer_use.display.windows()
print(f"Found {len(windows.windows)} open windows:")
for window in windows.windows:
    print(f"- {window.title} (ID: {window.id}, active: {window.is_active})")
```

</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 windows = await sandbox.computerUse.display.windows();
console.log(`Found ${windows.windows.length} open windows:`);
for (const window of windows.windows) {
  console.log(`- ${window.title} (ID: ${window.id}, active: ${window.isActive})`);
}
```

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

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

</TabItem>
</Tabs>

## Errors [#errors]

The SDKs raise these errors with the codes in the following table.

| **Code**                | **Meaning**                                                                                                 | **What to do**                         |
| ----------------------- | ----------------------------------------------------------------------------------------------------------- | -------------------------------------- |
| **`ADDON_UNAVAILABLE`** | Returned with `503`. The desktop add-on is not in the sandbox, a start or restart did not bring the desktop up, a process restart did not bring the process back within 5 s, or the X server or the accessibility bus cannot be reached. The SDKs raise `AddonUnavailableError`. | Read the [status](#get-status). If the desktop is failed, call [restart](#restart-computer-use). If the add-on is missing, contact us with the sandbox ID. |
| **`DESKTOP_NOT_READY`** | Returned with `409`. The desktop is not ready: not started, starting, restarting, or failed. The SDKs raise `DesktopNotReadyError`. | Call [start](#start-computer-use) on a desktop that is not started, or [restart](#restart-computer-use) on a failed one. |

## Permissions [#permissions]

Computer Use 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 or toolbox 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`** | Control the desktop, mouse, keyboard, screenshots, and recordings |
