computer-use is a worker process built to drive a real desktop:
It runs as a process separate from aiod, on any host with a desktop. Examples:
aiod proxies its API. It does not touch the desktop itself. That process boundary keeps desktop control optional: aiod runs the same without a desktop present, and reaches one wherever computer-use is running.
The desktop plane is v2-only, under /v2/computer/*.
v1 keeps two aliases for 1.x clients:
POST /v1/browser/actionsPOST /v1/display/recordThe routes side by side are in Migration from 1.x.
The first part of this page is the action reference an agent's tools are built on. The second covers the viewers a person uses to watch or take over the same desktop. Browser API covers CDP-based page automation instead, and Computer Use examples walks one desktop task end to end.
The worker binds AIO_COMPUTER_USE_LISTEN (default 0.0.0.0:18100). aiod reaches it at AIO_COMPUTER_USE_URL (default http://127.0.0.1:18100) and proxies the desktop routes opaquely. When the worker is down, every one of them answers 503:
Both v1 aliases answer the same way, since they reach the same worker.
Linux: a reachable X11 display. Set DISPLAY, and XAUTHORITY if the display needs cookie authentication. GET /v2/computer/info reports back what it found in display and xauthority.
Windows: an interactive logon session. aiod itself can run as a Session 0 SCM service.
computer-use must run inside the interactive session to see the desktop and inject input.
GET /v2/computer/info reports the display the worker found and what it can do on it:
data is:
available — whether the worker reached a displaydisplay — the display the worker foundxauthority — the XAUTHORITY it foundscreen_resolution — {width, height}capabilities — {screenshot, actions, clipboard, recording}warnings — problems the probe foundv1 has no desktop info route. The sandbox capability probe reports the plane instead:
data.computer is:
status — ready, degraded, or absentdisplay, resolution — what the worker foundscreenshot, actions, clipboard, recording, accessibility — one flag per operationaccessibility_backend — atspi, uia, or null where none is compiled inprovider, missing, warnings — how it was found, and what is not thereWhen no worker is running, the response is:
When you need the availability and the XAUTHORITY the worker itself reports, switch to v2:
The response body is the image itself:
The capture is a PNG of the entire desktop: all windows, the taskbar, dialogs. The response headers carry the geometry — x-image-width, x-image-height, x-screen-width, x-screen-height.
v1 has no desktop screenshot route. The post-action frame is the way in, and WAIT is the action that changes nothing:
screenshot in the response is the same desktop capture, base64 PNG. The Python SDK sends no query string on this route, so a frame taken this way needs the HTTP call. GET /v1/browser/screenshot is a different thing: it captures the browser page over CDP, not the desktop.
When you want the capture as a PNG body, with no action attached, switch to v2:
One action per request, addressed in screen coordinates. ?include_screenshot=true merges the observe step into the act step:
The alias and the v2 route are one handler: same body, same response.
The Python SDK sends one typed action per call:
Its action union covers the mouse, the keyboard, and WAIT. The clipboard, window, and node actions below go over HTTP on the same route, as does a post-action screenshot.
The action body is the same in v1 and v2. For example, click screen coordinates (640, 400):
The body describes the action directly, and action_type selects which action to run:
action_type | Required | Optional |
|---|---|---|
MOVE_TO | x, y | — |
MOVE_REL | x_offset, y_offset | — |
CLICK | — | x, y, button, num_clicks |
RIGHT_CLICK | — | x, y |
DOUBLE_CLICK | — | x, y |
MOUSE_DOWN | — | button |
MOUSE_UP | — | button |
DRAG_TO | x, y | — |
DRAG_REL | x_offset, y_offset | — |
SCROLL | — | dx, dy |
TYPING | text | use_clipboard |
PRESS | key | — |
KEY_DOWN | key | — |
KEY_UP | key | — |
HOTKEY | keys | — |
WAIT | duration | — |
SET_CLIPBOARD | text | — |
WINDOW_ACTIVATE | window_id | — |
WINDOW_MINIMIZE | window_id | — |
NODE_FOCUS | node_id | — |
NODE_INVOKE | node_id | action |
NODE_SET_VALUE | node_id, value | — |
duration is in seconds; keys is a list, ["ctrl", "c"] for copy.window_id comes from the window list, a node_id from the accessibility tree; both are below.WAIT asks for at most 10 s, and a SCROLL for at most 100 notches per axis.TYPING types ASCII text as keystrokes. Text with other characters goes through the clipboard (use_clipboard, default true), which replaces whatever the clipboard held; with use_clipboard: false such text is refused with 400, since keystrokes would drop the characters. Clipboard text is pasted with Shift+Insert, which terminals and GTK or Chromium windows both accept.PRESS, HOTKEY, or TYPING means the input reached the focused window, not that the application acted on it. Take a screenshot to confirm.The response carries status and action_performed, plus the post-action frame as base64 PNG when one was asked for. If capturing it fails, that shows up as screenshot_error next to the action result, not as a failed request: the action already happened.
A batch runs its actions in order in one request, and nothing else touches the display until it finishes:
include_screenshot is a body field here, not a query parameter. At most 50 actions per batch, and their WAIT actions total at most 20 s.
data is:
performed — the actions that ran, in orderfailed_index, error — which action stopped the batch, and why; everything before it ranstatus, reason — denied and its machine-readable cause, when the host refused the inputscreenshot, screenshot_error — the frame after the last action that ranThe alias takes one action per request. A sequence is that many round trips, and another caller can move the pointer between them.
When a sequence has to run without anything else touching the display, switch to v2:
The desktop is an ordinary X display and every command runs with DISPLAY set, so a script that drives the screen itself runs through POST /v2/commands (/v1/bash/exec on v1) against the same desktop the actions above use. The Computer image ships xdotool; pyautogui is one pip install pyautogui away:
| Endpoint | Purpose | Notes |
|---|---|---|
GET /v2/computer/cursor | Current pointer position | Screen coordinates |
GET /v2/computer/clipboard | Read the clipboard text | 5 s read timeout, 1 MiB size cap |
GET /v2/computer/windows | List top-level windows | Up to 200 on the xdotool code path |
The window list is {snapshot_id, windows: [{window_id, title, process_id, bounds, minimized}]}. A window_id is the native window handle rather than an index into the listing, so it stays valid while the window exists; acting on a closed window fails instead of hitting whatever took its place.
None of the three has a v1 route. Writing the clipboard is an action, so SET_CLIPBOARD goes through the alias; reading it back does not.
When you need the window list, the pointer position, or the clipboard text, switch to v2:
Two routes read the desktop's accessibility tree:
GET /v2/computer/accessibility returns the full tree.GET /v2/computer/accessibility/nodes searches that tree as a flat list.Both accept queries such as ?role=button&name=Sign+in and take the same fields:
| Field | Values | Meaning |
|---|---|---|
scope | foreground (default), desktop | The active window, or every top-level window |
role, name | string | Keep only what matches |
match | substring (default), exact, regex | How role and name are compared |
states | comma-separated | States a node must all carry (enabled,showing) |
include_offscreen | boolean, off by default | Include nodes the backend marks as offscreen |
max_depth | 1–64, default 32 | Depth budget |
max_nodes | 1–20000, default 5000 | Node budget |
timeout_ms | 100–60000, default 5000 | Wall-clock budget for the walk |
limit | 1–1000, default 50 | nodes only: most nodes returned |
Out-of-range values are clamped, not rejected, and role and name are compared case-insensitively in every mode. A truncated flag marks an answer that hit one of those budgets, so a missing node is not evidence the node is absent. On nodes, ?node_id= resolves one handle and ignores the search parameters.
A returned node_id stays valid as long as the element exists, across snapshots. It turns 404 once the element is gone — its window closed, or its application restarted. Feed it into NODE_FOCUS, NODE_INVOKE, or NODE_SET_VALUE actions to interact without pixel coordinates.
The NODE_* actions are in the set the alias accepts, but the handles they take come from the two v2 tree routes. v1 has neither, so there is nothing to feed them.
When you want to act on an element by role and name instead of pixel coordinates, switch to v2:
Backends: AT-SPI2 (Assistive Technology Service Provider Interface) on Linux, UI Automation (UIA) on Windows. 501 means there is no backend to run here: a platform with no implementation, or a Linux image without the AT-SPI runtime. Retrying will not change that.
503 means a backend is there but could not read right now — no interactive desktop, or no focused window. Chromium and Electron apps expose their tree only when started with --force-renderer-accessibility.
POST /v2/computer/record starts, inspects, and stops a recording: action is start, status, or stop.
POST /v1/display/record is the same endpoint: action is start, status, or stop. The Python SDK covers the route and every parameter of it:
Recording is ffmpeg-based; only one runs at a time. start takes the capture settings:
| Parameter | Range | Default |
|---|---|---|
fps | 1–60 | 30 |
crf | 0–51 (lower is higher quality) | 30 |
max_duration | up to 600 s | 60 |
width, height | pixels | The detected screen resolution |
save_path is optional; without one the file lands in the temp directory as recording_<timestamp>.mp4. A missing parent directory is created, and one the worker cannot write to is a 400.
data carries:
status — recording, stopped, or idleduration — seconds recorded so farsave_path — where the file is being writtenfile_size_bytes — the size after a stopStore recordings under a path the File API can reach. Stop recording before tearing down the host. That keeps the file playable.
Input on a secure desktop is refused with a 403 whose data is {"status": "denied", "reason": "secure-desktop-or-uipi"}. Examples: a UAC prompt, the lock screen. This is a Windows session boundary, not a bug to work around. In a batch the same refusal arrives as status and reason inside a 200, because the actions before it really ran.
computer-use also answers on its own port, without going through aiod:
| Route | Purpose | Notes |
|---|---|---|
GET /healthz | Liveness | Public |
GET /capabilities | Desktop probe | schema_version 1; needs the API key if set |
GET /openapi.json | The worker's own contract | Public |
GET /metrics | Prometheus text | Needs the API key if set |
Useful when deploying or health-checking the worker on its own.
A person steps into the same terminal, browser or desktop the agent is driving: to watch a run, to take over a login or a captcha, to hand the desktop back. Nothing in the daemon changes for that. The viewers are pages the prebuilt images serve through their gateway, and each one attaches to the object the agent already uses:
| Viewer | Path on the image | Shows |
|---|---|---|
| noVNC | /vnc/index.html | The X display the worker drives; watch or take over |
| DevTools | /browser-ui, /cdp/devtools/* | Chromium's tabs, over the same CDP the agent uses |
| WebShell | /terminal?session_id=… | One PTY session; GET /v1/shell/terminal-url builds the link |
| code-server | /code-server/ | An IDE on the same filesystem; off unless DISABLE_CODE_SERVER=false |
| JupyterLab | /jupyter | Notebooks on the same filesystem; off unless DISABLE_JUPYTER=false |
Xvnc is both the X server and the VNC server, so a person's clicks land on the display directly and the worker sees the result in its next screenshot. The daemon serves the API only; aiod alone answers 404 on every path above.
The prebuilt images wire this up. The AIO image runs Xvnc on DISPLAY=:99.0 with openbox and Chromium, and serves noVNC, DevTools and the WebShell. The Computer image sets AIO_DESKTOP=xfce on top of it: an XFCE session takes the display, the computer-use worker starts next to aiod, a session D-Bus serves AT-SPI, and Chromium runs with --force-renderer-accessibility so tab contents appear in the accessibility tree.