Displays: Input actions
Section titled “Displays: Input actions”Display input actions drive a desktop the same way a user does: clicking pixels, pressing keys, dragging windows, and waiting for the UI to settle. Every route is bound to a specific display by the -display-N segment of the hostname; the displayId query parameter overrides the host when needed. State-changing commands enter a 20-deep per-display queue, so input is delivered in order even when many calls arrive at once, while the wait-until admission cap sits outside that queue so waits never block timed key or button releases. Use these routes when an automation needs more than what the higher-level helpers cover.
Read state: display, mouse, and window
Section titled “Read state: display, mouse, and window”GET /api/v1/display/input/display-geometry
Section titled “GET /api/v1/display/input/display-geometry”Return the current display’s pixel dimensions.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Responses
Section titled “Responses”{ "success": true, "width": 1920, "height": 1080, "screen": 0}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Queue full | — | — |
Examples
Section titled “Examples”curl -X GET "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/input/display-geometry" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.display.getGeometry();GET /api/v1/display/mouse/location
Section titled “GET /api/v1/display/mouse/location”Return the current cursor position and the window it is over.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Responses
Section titled “Responses”{ "success": true, "x": 640, "y": 360, "screen": 0, "window": 6291468}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Queue full | — | — |
Examples
Section titled “Examples”curl -X GET "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/mouse/location" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.display.mouse.getPosition();GET /api/v1/display/window/{windowId}/geometry
Section titled “GET /api/v1/display/window/{windowId}/geometry”Return the position and size of a window.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
windowId | path | string | Yes | Window ID (decimal or hex). |
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Responses
Section titled “Responses”{ "success": true, "windowId": "6291468", "x": 100, "y": 100, "width": 800, "height": 600}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
{ "error": "Window not found or no longer exists", "code": "WINDOW_NOT_FOUND", "details": { "message": "Window not found or no longer exists", "help": "Check if the window still exists using /window/search" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | The window ID does not refer to any current window. | Confirm the window still exists via /window/search. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Queue full | — | — |
Examples
Section titled “Examples”curl -X GET "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/window/6291468/geometry" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.display.windows.getGeometry('6291468');GET /api/v1/display/window/{windowId}/name
Section titled “GET /api/v1/display/window/{windowId}/name”Return the title of a window.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
windowId | path | string | Yes | Window ID (decimal or hex). |
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Responses
Section titled “Responses”{ "success": true, "windowId": "6291468", "name": "Untitled Document — gedit"}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
{ "error": "Window not found or no longer exists", "code": "WINDOW_NOT_FOUND", "details": { "message": "Window not found or no longer exists", "help": "Check if the window still exists using /window/search" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | The window ID does not refer to any current window. | Confirm the window still exists via /window/search. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Queue full | — | — |
Examples
Section titled “Examples”curl -X GET "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/window/6291468/name" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.display.windows.getTitle('6291468');GET /api/v1/display/window/active
Section titled “GET /api/v1/display/window/active”Return the window that currently has focus.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Responses
Section titled “Responses”{ "success": true, "windowId": 6291468}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Queue full | — | — |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_WINDOW_MANAGER | No window manager | — | — |
Examples
Section titled “Examples”curl -X GET "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/window/active" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.display.windows.getActive();Combined input actions
Section titled “Combined input actions”These endpoints compose several primitives into a single call. Each one enters the same per-display input queue as the underlying keyboard and mouse routes, so a click, drag, or batch is delivered in the order it was sent.
POST /api/v1/display/input/act
Section titled “POST /api/v1/display/input/act”Execute a single named action, optionally with a trailing screenshot. The action path picks which primitive runs; params are validated against the same schema the standalone route uses, so a params rejection here IS a 400 VALIDATION_ERROR, unlike in /input/batch.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | One of mouse/click, mouse/double-click, mouse/move, mouse/move-relative, mouse/down, mouse/up, mouse/scroll, keyboard/type, keyboard/key, keyboard/key-down, keyboard/key-up, input/click-at, input/type-at, input/drag, input/select, input/wait, clipboard/read, clipboard/write, window/focus, window/raise, window/minimize, window/restore, window/move, window/resize, window/close, window/search. |
params | object | No | Action-specific parameters. Validated against the standalone route’s schema. Default: {}. |
screenshot | boolean | No | Capture a screenshot after the action. Default: true. |
screenshotDelay | integer | No | Delay before the screenshot in milliseconds. Default: 100. |
screenshotRegion | string | No | Crop region in x1,y1,x2,y2 form. |
{ "action": "input/click-at", "params": { "x": 320, "y": 240 }, "screenshot": true, "screenshotDelay": 150}Responses
Section titled “Responses”{ "success": true, "action": { "success": true, "action": "input/click-at", "details": {} }, "screenshot": { "timestamp": "2024-01-15T10:30:00.000Z", "image": { "data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "mimeType": "image/png", "dataUrl": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=" } }}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Window not found or no longer exists", "code": "WINDOW_NOT_FOUND", "details": { "message": "Window not found or no longer exists", "help": "Check if the window still exists using /window/search" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | The window referred to by params.windowId does not exist. | Confirm the window still exists via /window/search. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_WINDOW_MANAGER | No window manager | — | — |
WINDOW_NOT_VIEWABLE | No viewable window has keyboard focus | The keys could reach no viewable window, so none were sent. While no viewer is attached to the display its windows are not viewable. | Attach a viewer, focus the window with window/focus (its inputFocus must be true) and retry. |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/input/act" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "action": "input/click-at", "params": { "x": 320, "y": 240 }, "screenshot": true }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.input.act({ action: 'input/click-at', params: { x: 320, y: 240 }, screenshot: true,});POST /api/v1/display/input/batch
Section titled “POST /api/v1/display/input/batch”Execute a sequence of actions in order. See the Aside above for how failure is signalled.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
actions | array | Yes | Actions to run, between 1 and 50 entries. Each entry has action (the path) and an optional params object. |
{ "actions": [ { "action": "mouse/move", "params": { "x": 320, "y": 240 } }, { "action": "mouse/click", "params": { "button": 1 } }, { "action": "keyboard/type", "params": { "text": "hello" } } ]}Responses
Section titled “Responses”The envelope is HTTP 200 whether or not an action failed. Inspect success, failed, and skipped.
{ "success": false, "completed": [ { "index": 0, "action": "mouse/move", "success": true }, { "index": 1, "action": "mouse/click", "success": true } ], "failed": { "index": 2, "action": "keyboard/type", "error": "Window not found" }, "skipped": [3, 4]}Returned when the envelope itself is invalid: actions is missing, empty, or contains more than 50 entries, or there is no display context. A per-action validation failure is not a 400; it comes back inside the 200 body as failed.error.
{ "error": "Validation failed", "code": "VALIDATION_ERROR", "details": { "message": "\"actions\" must contain at least 1 items", "help": "Check the request body parameters and try again" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Validation error | The body failed schema validation, for example a missing or oversized actions array. | Send an actions array with at least 1 entry and at most 50. |
NO_DISPLAY_CONTEXT | No display context | The request names no display: it has neither a displayId query parameter nor a display-specific hostname. | Pass ?displayId=N, or call the display through its display-specific hostname. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
Returned when the JSON body is over 2 MiB.
{ "error": "Request body too large", "code": "PAYLOAD_TOO_LARGE", "details": { "message": "Maximum body size is 2MB", "help": "Reduce the size of your request body" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | The JSON body is over 2 MiB. | Reduce the body size below 2 MiB. |
Returned only for an error outside the actions themselves. An action that fails, an xdotool failure included, is reported in the 200 body as failed.error, with success: false.
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "Unexpected error before the first action ran", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | Input action failed | The batch failed outside its actions, for example before the first one ran. A failing action is reported in the 200 body instead. | Retry; if it persists, check that the display is active. |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/input/batch" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "actions": [ { "action": "mouse/move", "params": { "x": 320, "y": 240 } }, { "action": "mouse/click", "params": { "button": 1 } }, { "action": "keyboard/type", "params": { "text": "hello" } } ] }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.display.input.actMany({ actions: [ { action: 'mouse/move', params: { x: 320, y: 240 } }, { action: 'mouse/click', params: { button: 1 } }, { action: 'keyboard/type', params: { text: 'hello' } }, ],});POST /api/v1/display/input/click-at
Section titled “POST /api/v1/display/input/click-at”Move the cursor to a point and click it. Multiple clicks at the same point happen in one chained invocation, so the pointer cannot be moved between them.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
x | integer | Yes | Target X coordinate (0-65535). |
y | integer | Yes | Target Y coordinate (0-65535). |
button | integer | No | Mouse button (1=left, 2=middle, 3=right, 4-7=extra). Default: 1. |
repeat | integer | No | Number of clicks at that point. Default: 1. Must satisfy repeat x delay <= 4000 ms. |
delay | integer | No | Milliseconds after every click. Defaults to xdotool’s 100 ms. A double-click needs repeat: 2 with a delay under the desktop’s double-click time. |
{ "x": 320, "y": 240, "button": 1, "repeat": 2, "delay": 50}Responses
Section titled “Responses”{ "success": true, "action": "click-at", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_VIEWABLE | No viewable window at the point | The click could reach no viewable window, so nothing was clicked or typed. While no viewer is attached to the display its windows are not viewable. | Attach a viewer, take a fresh screenshot, and click a point on a window in it (screenshot pixels are screen coordinates). |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/input/click-at" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "x": 320, "y": 240, "button": 1, "repeat": 2, "delay": 50 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.input.click({ x: 320, y: 240, button: 1, repeat: 2, delay: 50 });POST /api/v1/display/input/drag
Section titled “POST /api/v1/display/input/drag”Press a mouse button at one point, drag through several intermediate positions, and release at the end point.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
startX | integer | Yes | Starting X coordinate. |
startY | integer | Yes | Starting Y coordinate. |
endX | integer | Yes | Ending X coordinate. |
endY | integer | Yes | Ending Y coordinate. |
button | integer | No | Mouse button (1=left, 2=middle, 3=right, 4-7=extra). Default: 1. |
steps | integer | No | Number of intermediate mouse positions for a smooth drag (1-1000). |
{ "startX": 100, "startY": 100, "endX": 500, "endY": 400, "button": 1, "steps": 20}Responses
Section titled “Responses”{ "success": true, "action": "drag", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/input/drag" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "startX": 100, "startY": 100, "endX": 500, "endY": 400, "steps": 20 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.input.drag({ startX: 100, startY: 100, endX: 500, endY: 400, steps: 20 });POST /api/v1/display/input/reset
Section titled “POST /api/v1/display/input/reset”Emergency release every held key and mouse button. Use this to recover from a stuck modifier or button.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Responses
Section titled “Responses”{ "success": true, "action": "reset", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Queue full | — | — |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/input/reset" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.input.reset();POST /api/v1/display/input/select
Section titled “POST /api/v1/display/input/select”Click at one point and shift-click at another to select a range. Useful for text-range selection in fields that respond to shift-click.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
x | integer | Yes | Anchor X coordinate. |
y | integer | Yes | Anchor Y coordinate. |
endX | integer | Yes | End X coordinate. |
endY | integer | Yes | End Y coordinate. |
{ "x": 100, "y": 100, "endX": 500, "endY": 400}Responses
Section titled “Responses”{ "success": true, "action": "select", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/input/select" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "x": 100, "y": 100, "endX": 500, "endY": 400 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.input.select({ x: 100, y: 100, endX: 500, endY: 400 });POST /api/v1/display/input/type-at
Section titled “POST /api/v1/display/input/type-at”Move the cursor to a point, click, and type a string. Useful for filling in a single field.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
x | integer | Yes | Target X coordinate. |
y | integer | Yes | Target Y coordinate. |
text | string | Yes | Text to type (max 10000 characters). |
delay | integer | No | Inter-keystroke delay in milliseconds (0-1000). |
{ "x": 320, "y": 240, "text": "hello world", "delay": 12}Responses
Section titled “Responses”{ "success": true, "action": "type-at", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_VIEWABLE | No viewable window at the point | The click could reach no viewable window, so nothing was clicked or typed. While no viewer is attached to the display its windows are not viewable. | Attach a viewer, take a fresh screenshot, and click a point on a window in it (screenshot pixels are screen coordinates). |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/input/type-at" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "x": 320, "y": 240, "text": "hello world", "delay": 12 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.input.type({ x: 320, y: 240, text: 'hello world', delay: 12 });POST /api/v1/display/input/wait
Section titled “POST /api/v1/display/input/wait”Sleep for a duration. Useful between actions to give the UI time to settle.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
ms | integer | Yes | Wait duration in milliseconds (50-30000). |
screenshot | boolean | No | Capture a screenshot after the wait. Default: false. |
{ "ms": 500, "screenshot": false}Responses
Section titled “Responses”{ "success": true, "action": "wait", "details": { "ms": 500 }}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/input/wait" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "ms": 500 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.input.wait({ ms: 500 });POST /api/v1/display/input/wait-until
Section titled “POST /api/v1/display/input/wait-until”Block until a window condition holds or the timeout expires, so a caller does not have to poll window/search over HTTP while an application starts up or a dialog closes.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
condition | string | Yes | One of window-present (wait for a window matching pattern to appear) or window-absent (wait for the last match to go away, or a specific windowId to stop existing). |
pattern | string | No | Extended POSIX regex as xdotool search takes it. Required for window-present. For window-absent give either this or windowId, not both. A pattern xdotool cannot compile is a 400, as is one containing a NUL. |
match | string | No | Which window property pattern is matched against: name (title), class (stable across a window’s life), or classname. Default: name. |
onlyVisible | boolean | No | Restrict the search to viewable windows. Sending it with windowId is rejected. Default: false. |
windowId | integer | string | No | Wait for this exact window to stop existing. window-absent only, and not together with pattern, match, or onlyVisible. |
timeoutMs | integer | No | Maximum wait in milliseconds (100-25000). Default: 10000. |
pollMs | integer | No | Gap between observations (100-5000). Default: 250. An explicit value above timeoutMs is rejected; the default is not. |
{ "condition": "window-present", "pattern": "^Settings$", "match": "name", "timeoutMs": 5000}Responses
Section titled “Responses”Returned with success: true and the matched window ids when the condition held, or with success: false and timedOut: true when the timeout expired with the condition unmet. A short timeoutMs that expires inside a single observation comes back as 200 with timedOut: true and polls: 0, which says plainly that nothing was ever observed.
{ "success": true, "condition": "window-present", "waitedMs": 1250, "polls": 5, "windows": [6291468]}VALIDATION_ERROR when the body is inconsistent (a window-present without pattern, both pattern and windowId, onlyVisible with windowId, an explicit pollMs above timeoutMs) or when pattern is a regex xdotool cannot compile. NO_DISPLAY_CONTEXT when the request carries neither a display-N hostname nor displayId.
{ "error": "Validation failed", "code": "VALIDATION_ERROR", "details": { "message": "\"pattern\" is required when condition is window-present", "help": "Check the request body parameters and try again" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Validation error | The body is internally inconsistent, or pattern is a regex xdotool cannot compile. | Provide pattern for window-present; pick one of pattern or windowId for window-absent; keep pollMs at or below timeoutMs. |
NO_DISPLAY_CONTEXT | No display context | The request names no display: it has neither a displayId query parameter nor a display-specific hostname. | Pass ?displayId=N, or call the display through its display-specific hostname. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
The JSON request body is larger than 2 MB.
{ "error": "Request body too large", "code": "PAYLOAD_TOO_LARGE", "details": { "message": "Maximum body size is 2MB", "help": "Reduce the size of your request body" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | The JSON request body is larger than 2 MB. | Shrink the body. |
Too many waits at once: 4 per display, 16 per service. This is a separate limit from the 20-command input queue, which a wait never enters.
{ "error": "Too many waits for display 10 (max 4)", "code": "QUEUE_FULL", "details": { "message": "Too many waits for display 10 (max 4)", "help": "Too many concurrent wait-until requests. Wait for one to finish and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The wait admission cap (4 per display, 16 per service) is exceeded. | Wait for an in-flight wait to finish and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
The display is not answering. That means either two consecutive observations were killed by their own slice (one is retried, since an unfiltered xdotool search walks the whole window tree), or one observation had a full 5 s slice and still did not finish, the same bar every other route’s command timeout uses. A short timeoutMs that expires inside a single observation is NOT this: on a loaded host an ordinary search can outlast 300 ms, and a display that is fine must not be reported as unavailable. That comes back as a 200 with timedOut: true and polls: 0.
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "The display did not answer two observations in a row", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | Two consecutive observations timed out, or one had a full 5 s slice and still did not finish. | Confirm the display is running and that the displayId matches a live display. |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/input/wait-until" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "condition": "window-present", "pattern": "^Settings$", "match": "name", "timeoutMs": 5000 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.display.windows.wait({ condition: 'window-present', pattern: '^Settings$', match: 'name', timeoutMs: 5000,});Keyboard
Section titled “Keyboard”POST /api/v1/display/keyboard/key
Section titled “POST /api/v1/display/keyboard/key”Press one or more key combinations in order. Each entry is sent to the windowed xdotool call together, so ctrl+c lands as a single keystroke.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
keys | array | Yes | Key combinations, for example ["ctrl+c", "Return"]. 1-20 entries, each up to 100 characters. |
window | integer | string | No | Target window ID (decimal or hex 0x...). |
delay | integer | No | Inter-keystroke delay in milliseconds (0-5000). |
clearModifiers | boolean | No | Clear modifier keys before pressing. |
{ "keys": ["ctrl+a", "Delete"]}Responses
Section titled “Responses”{ "success": true, "action": "key", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | — | — |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_VIEWABLE | No viewable window has keyboard focus | The keys could reach no viewable window, so none were sent. While no viewer is attached to the display its windows are not viewable. | Attach a viewer, focus the window with window/focus (its inputFocus must be true) and retry. |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/keyboard/key" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "keys": ["ctrl+a", "Delete"] }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.keyboard.press({ keys: ['ctrl+a', 'Delete'] });POST /api/v1/display/keyboard/key-down
Section titled “POST /api/v1/display/keyboard/key-down”Press and hold a single key. Pair with /keyboard/key-up to release.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | X11 keysym (for example Shift_L, ctrl). |
window | integer | string | No | Target window ID (decimal or hex 0x...). |
holdMs | integer | No | Auto-release after this many milliseconds (100-60000). |
{ "key": "Shift_L", "holdMs": 1000}Responses
Section titled “Responses”{ "success": true, "action": "key-down", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/keyboard/key-down" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "key": "Shift_L", "holdMs": 1000 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.keyboard.down({ key: 'Shift_L', holdMs: 1000 });POST /api/v1/display/keyboard/key-up
Section titled “POST /api/v1/display/keyboard/key-up”Release a key previously held by /keyboard/key-down.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | X11 keysym (max 100 characters). |
window | integer | string | No | Target window ID (decimal or hex 0x...). |
{ "key": "Shift_L"}Responses
Section titled “Responses”{ "success": true, "action": "key-up", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/keyboard/key-up" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "key": "Shift_L" }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.keyboard.up({ key: 'Shift_L' });POST /api/v1/display/keyboard/type
Section titled “POST /api/v1/display/keyboard/type”Type a string of literal text. Use /keyboard/key for named keys or combinations.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
text | string | Yes | Text to type (max 10000 characters). |
window | integer | string | No | Target window ID (decimal or hex 0x...). |
delay | integer | No | Inter-keystroke delay in milliseconds (0-1000). |
clearModifiers | boolean | No | Clear modifier keys before typing. |
{ "text": "hello world", "delay": 12}Responses
Section titled “Responses”{ "success": true, "action": "type", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | — | — |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_VIEWABLE | No viewable window has keyboard focus | The keys could reach no viewable window, so none were sent. While no viewer is attached to the display its windows are not viewable. | Attach a viewer, focus the window with window/focus (its inputFocus must be true) and retry. |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/keyboard/type" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "text": "hello world", "delay": 12 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.keyboard.type({ text: 'hello world', delay: 12 });POST /api/v1/display/mouse/click
Section titled “POST /api/v1/display/mouse/click”Click a mouse button at the current cursor position.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
button | integer | No | Mouse button: 1=left, 2=middle, 3=right, 4-7=extra. Default: 1. |
repeat | integer | No | Number of clicks (1-100). Default: 1. Must satisfy repeat x delay <= 4000 ms. |
delay | integer | No | Milliseconds to wait after every click. Defaults to xdotool’s 100 ms. |
window | integer | string | No | Target window ID (decimal or hex 0x...). |
{ "button": 1, "repeat": 1, "delay": 100}Responses
Section titled “Responses”{ "success": true, "action": "click", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/mouse/click" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "button": 1 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.mouse.click({ button: 1 });POST /api/v1/display/mouse/double-click
Section titled “POST /api/v1/display/mouse/double-click”Double-click a mouse button at the current cursor position.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
button | integer | No | Mouse button: 1=left, 2=middle, 3=right, 4-7=extra. Default: 1. |
window | integer | string | No | Target window ID (decimal or hex 0x...). |
{ "button": 1}Responses
Section titled “Responses”{ "success": true, "action": "double-click", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/mouse/double-click" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "button": 1 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.mouse.doubleClick({ button: 1 });POST /api/v1/display/mouse/down
Section titled “POST /api/v1/display/mouse/down”Press and hold a mouse button. Pair with /mouse/up to release.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
button | integer | No | Mouse button: 1=left, 2=middle, 3=right, 4-7=extra. Default: 1. |
window | integer | string | No | Target window ID (decimal or hex 0x...). |
holdMs | integer | No | Auto-release after this many milliseconds (100-60000). |
{ "button": 1, "holdMs": 1000}Responses
Section titled “Responses”{ "success": true, "action": "mouse-down", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/mouse/down" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "button": 1, "holdMs": 1000 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.mouse.down({ button: 1, holdMs: 1000 });POST /api/v1/display/mouse/move
Section titled “POST /api/v1/display/mouse/move”Move the cursor to an absolute screen position. Coordinates can be negative to address off-screen positions used by multi-monitor setups.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
x | integer | Yes | Target X coordinate (-65535 to 65535). |
y | integer | Yes | Target Y coordinate (-65535 to 65535). |
window | integer | string | No | Target window ID (decimal or hex 0x...). |
screen | integer | No | Screen number (0-15). |
sync | boolean | No | Wait for the move to be observed before answering. |
{ "x": 320, "y": 240, "screen": 0, "sync": true}Responses
Section titled “Responses”{ "success": true, "action": "mouse-move", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/mouse/move" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "x": 320, "y": 240, "screen": 0, "sync": true }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.mouse.move({ x: 320, y: 240, screen: 0, sync: true });POST /api/v1/display/mouse/move-relative
Section titled “POST /api/v1/display/mouse/move-relative”Move the cursor by an offset from its current position.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
x | integer | Yes | Horizontal offset to apply. |
y | integer | Yes | Vertical offset to apply. |
sync | boolean | No | Wait for the move to be observed before answering. |
{ "x": 50, "y": -20, "sync": true}Responses
Section titled “Responses”{ "success": true, "action": "mouse-move-relative", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/mouse/move-relative" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "x": 50, "y": -20, "sync": true }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.mouse.moveBy({ x: 50, y: -20, sync: true });POST /api/v1/display/mouse/scroll
Section titled “POST /api/v1/display/mouse/scroll”Scroll in a direction. Optionally move the pointer to a specific point first; the move and the wheel events are one chained xdotool invocation, so nothing can move the pointer in between.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
direction | string | Yes | One of up, down, left, right. |
clicks | integer | No | Wheel clicks (1-100). Default: 5. Must satisfy clicks x delay <= 4000 ms. |
delay | integer | No | Milliseconds between wheel clicks. Defaults to xdotool’s 100 ms. |
x | integer | No | Scroll at this point (0-65535) instead of wherever the pointer happens to be. Must be given together with y. |
y | integer | No | Must be given together with x (0-65535). |
{ "direction": "down", "clicks": 5, "x": 320, "y": 240}Responses
Section titled “Responses”{ "success": true, "action": "scroll", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/mouse/scroll" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "direction": "down", "clicks": 5, "x": 320, "y": 240 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.mouse.scroll({ direction: 'down', clicks: 5, x: 320, y: 240 });POST /api/v1/display/mouse/up
Section titled “POST /api/v1/display/mouse/up”Release a mouse button previously held by /mouse/down.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
button | integer | No | Mouse button: 1=left, 2=middle, 3=right, 4-7=extra. Default: 1. |
window | integer | string | No | Target window ID (decimal or hex 0x...). |
{ "button": 1}Responses
Section titled “Responses”{ "success": true, "action": "mouse-up", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/mouse/up" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "button": 1 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.mouse.up({ button: 1 });Window management
Section titled “Window management”POST /api/v1/display/window/close
Section titled “POST /api/v1/display/window/close”Ask the X server to close a window.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
windowId | integer | string | Yes | Window ID (decimal or hex 0x...). |
{ "windowId": 6291468}Responses
Section titled “Responses”{ "success": true, "action": "window-close", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Window not found or no longer exists", "code": "WINDOW_NOT_FOUND", "details": { "message": "Window not found or no longer exists", "help": "Check if the window still exists using /window/search" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | The window ID does not refer to any current window. | Confirm the window still exists via /window/search. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/window/close" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "windowId": 6291468 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.windows.close({ windowId: 6291468 });POST /api/v1/display/window/focus
Section titled “POST /api/v1/display/window/focus”Activate a window and give it X input focus, so that untargeted /keyboard/* input reaches it.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
windowId | integer | string | Yes | Target window ID (decimal or hex 0x...). |
sync | boolean | No | Wait for the X server to apply the change before answering. On this endpoint this is what makes a following keyboard/* action land in the window that was just focused. |
{ "windowId": 6291468, "sync": true}Responses
Section titled “Responses”{ "success": true, "action": "window-focus", "details": { "windowId": 6291468, "inputFocus": true }}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
VALIDATION_ERROR | Validation failed | A body field, path parameter or query parameter is missing, has the wrong type or is out of range. details.message names each failing check. | Fix the fields named in details.message and send the request again. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Window not found or no longer exists", "code": "WINDOW_NOT_FOUND", "details": { "message": "Window not found or no longer exists", "help": "Check if the window still exists using /window/search" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | The window ID does not refer to any current window. | Confirm the window still exists via /window/search. |
The display runs no window manager: a desktop started without a desktop environment, or one whose window manager has exited. Only a window manager keeps the window list and the active window, so this is a state of the display rather than a failure, and retrying does not change it. window/search, window geometry and name, and the mouse and keyboard actions work without a window manager.
{ "error": "The display has no window manager, so it keeps no window list", "code": "NO_WINDOW_MANAGER", "details": { "message": "Window query failed: Cannot get client list properties. (_NET_CLIENT_LIST or _WIN_CLIENT_LIST)", "help": "The display has no window manager. Start one (for example a desktop session with a desktop environment); window/search, window geometry and name, and the mouse and keyboard actions work without one." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_WINDOW_MANAGER | No window manager | The display runs no window manager, so it keeps no window list and no active window. This is a state of the display; retrying does not change it. | Start a window manager (for example a desktop session) on the display, or use the actions that work without one. |
The JSON request body is larger than 2 MB.
{ "error": "Request body too large", "code": "PAYLOAD_TOO_LARGE", "details": { "message": "Maximum body size is 2MB", "help": "Reduce the size of your request body" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Request body too large | The JSON request body is larger than 2 MB. | Reduce the size of the request body. |
The per-display command queue holds the maximum of 20 pending commands, so the request was refused before it ran.
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "The input command exited with an error", "help": "Check if the display is active" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | Input action failed | The input command ran and failed. | Check that the display is active, then retry. |
INTERNAL_ERROR | Unexpected server error | The request failed in an unexpected way before it could be handled, for example a request body that could not be read. | Retry the request. If it keeps failing, check the request body and headers. |
The target display is not running. A container has no display until a terminal session starts one, so this is the usual answer on a fresh container. Every display route answers this state with this status and code. When the container image lacks a tool the display needs, details.reason names it (dependency_missing: xpra).
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display is not running or does not answer. A container has no display until a terminal session starts one. When the container image lacks a tool the display needs, details.reason names it. | Start a display (open a terminal session), check the displayId, and retry. A dependency_missing reason does not clear on retry. |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/window/focus" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "windowId": 6291468, "sync": true }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.windows.focus({ windowId: 6291468, sync: true });POST /api/v1/display/window/minimize
Section titled “POST /api/v1/display/window/minimize”Minimize a window. To bring it back, call /window/restore.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
windowId | integer | string | Yes | Target window ID (decimal or hex 0x...). |
sync | boolean | No | Wait for the X server to apply the change before answering. |
{ "windowId": 6291468, "sync": true}Responses
Section titled “Responses”{ "success": true, "action": "window-minimize", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Window not found or no longer exists", "code": "WINDOW_NOT_FOUND", "details": { "message": "Window not found or no longer exists", "help": "Check if the window still exists using /window/search" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | The window ID does not refer to any current window. | Confirm the window still exists via /window/search. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/window/minimize" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "windowId": 6291468, "sync": true }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.windows.minimize({ windowId: 6291468, sync: true });POST /api/v1/display/window/move
Section titled “POST /api/v1/display/window/move”Move a window to a new position. With relative: true, the x and y are interpreted as offsets from the current position.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
windowId | integer | string | Yes | Target window ID (decimal or hex 0x...). |
x | integer | Yes | Target X coordinate (or offset when relative is true). |
y | integer | Yes | Target Y coordinate (or offset when relative is true). |
sync | boolean | No | Wait for the X server to apply the change before answering. |
relative | boolean | No | Treat x and y as offsets from the current position. |
{ "windowId": 6291468, "x": 100, "y": 100, "sync": true}Responses
Section titled “Responses”{ "success": true, "action": "window-move", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Window not found or no longer exists", "code": "WINDOW_NOT_FOUND", "details": { "message": "Window not found or no longer exists", "help": "Check if the window still exists using /window/search" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | The window ID does not refer to any current window. | Confirm the window still exists via /window/search. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/window/move" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "windowId": 6291468, "x": 100, "y": 100, "sync": true }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.windows.move({ windowId: 6291468, x: 100, y: 100, sync: true });POST /api/v1/display/window/raise
Section titled “POST /api/v1/display/window/raise”Ask the X server to raise the window.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
windowId | integer | string | Yes | Window ID (decimal or hex 0x...). |
{ "windowId": 6291468}Responses
Section titled “Responses”{ "success": true, "action": "window-raise", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Window not found or no longer exists", "code": "WINDOW_NOT_FOUND", "details": { "message": "Window not found or no longer exists", "help": "Check if the window still exists using /window/search" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | The window ID does not refer to any current window. | Confirm the window still exists via /window/search. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/window/raise" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "windowId": 6291468 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.windows.raise({ windowId: 6291468 });POST /api/v1/display/window/resize
Section titled “POST /api/v1/display/window/resize”Resize a window. The window manager may ignore the request, so use sync: true when the caller needs to know the new size took effect.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
windowId | integer | string | Yes | Target window ID (decimal or hex 0x...). |
width | integer | Yes | New width in pixels. |
height | integer | Yes | New height in pixels. |
sync | boolean | No | Wait for the X server to apply the change before answering. |
useHints | boolean | No | Apply the size as a normal hint rather than a forced change. |
{ "windowId": 6291468, "width": 1024, "height": 768, "sync": true}Responses
Section titled “Responses”{ "success": true, "action": "window-resize", "details": {}}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Window not found or no longer exists", "code": "WINDOW_NOT_FOUND", "details": { "message": "Window not found or no longer exists", "help": "Check if the window still exists using /window/search" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | The window ID does not refer to any current window. | Confirm the window still exists via /window/search. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/window/resize" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "windowId": 6291468, "width": 1024, "height": 768, "sync": true }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.windows.resize({ windowId: 6291468, width: 1024, height: 768, sync: true });POST /api/v1/display/window/restore
Section titled “POST /api/v1/display/window/restore”Un-minimize a window minimized by /window/minimize, by the application, or by a user in the display viewer.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
windowId | integer | string | Yes | X window ID (decimal, or hex like 0x60000c; 32-bit). |
sync | boolean | No | Wait (up to 2 s) until the window manager reports the window as no longer minimized. Defaults to true, unlike the other window actions: the restore request is a message the window manager may ignore, so without waiting the response cannot say whether it worked. |
{ "windowId": 6291468, "sync": true}Responses
Section titled “Responses”{ "success": true, "action": "window-restore", "details": { "windowId": 6291468, "previousState": "iconic", "state": "normal", "synced": true }}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
Returned when the window does not exist or is not managed by the window manager (override-redirect, withdrawn, never mapped).
{ "error": "Window not found or no longer exists", "code": "WINDOW_NOT_FOUND", "details": { "message": "Window not found or no longer exists", "help": "Check if the window still exists using /window/search" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WINDOW_NOT_FOUND | Window not found | The window ID does not refer to any current window, or the window is not managed by the window manager (override-redirect, withdrawn, never mapped). | Confirm the window still exists via /window/search. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
Returned when, with sync, the window is still minimized after 2 s.
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_WINDOW_MANAGER | No window manager | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/window/restore" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "windowId": 6291468, "sync": true }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.display.windows.restore({ windowId: 6291468, sync: true });POST /api/v1/display/window/search
Section titled “POST /api/v1/display/window/search”Return the window IDs that match a regex against the window name, class, and/or classname.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
displayId | query | integer | No | Display ID to use (overrides the *-display-N.* hostname pattern). Valid range: 1 to 999999. |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
pattern | string | Yes | Search pattern (regex, max 200 characters). |
name | boolean | No | Match against window name/title. |
class | boolean | No | Match against window class. |
classname | boolean | No | Match against window classname. |
onlyVisible | boolean | No | Restrict the search to viewable windows. This is not a way to skip minimized windows: measured on xpra 6.3.6 and 6.4.4, a window stays viewable after window/minimize, even once xpra reports it iconic. What it does filter is unmapped windows, which on a seamless session means every window while no viewer is attached. Note that GET /windows has a parameter of the same name with a different test: there it drops windows carrying _NET_WM_STATE_HIDDEN, which a minimized window does carry, so that one does exclude minimized windows. |
{ "pattern": "Settings$", "name": true, "onlyVisible": true}Responses
Section titled “Responses”{ "success": true, "windows": [6291468, 6291470]}{ "error": "No display context found", "code": "NO_DISPLAY_CONTEXT", "details": { "message": "A display ID is required for this endpoint. It can be provided via a \"displayId\" query parameter or a domain in the format \"-display-X\".", "help": "Use a display-specific domain or pass ?displayId=N." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_DISPLAY_CONTEXT | No display context found | The request reached a display service without a -display-N segment in the hostname and without a displayId query parameter. | Use a display-specific domain or pass ?displayId=N. |
INVALID_JSON | Invalid JSON body | The request body could not be parsed as JSON, or a body was sent without Content-Type: application/json. | Send a valid JSON body with Content-Type: application/json. |
{ "error": "Command queue full for display 10 (max 20)", "code": "QUEUE_FULL", "details": { "message": "Command queue full for display 10 (max 20)", "help": "Too many pending commands for this display. Wait and retry." }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
QUEUE_FULL | Command queue full | The per-display input queue already holds the maximum of 20 pending commands. | Wait for the queue to drain and retry. |
{ "error": "Input action failed", "code": "INPUT_ACTION_FAILED", "details": { "message": "xdotool command returned an error", "help": "Check if the display is active and xdotool is available" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INPUT_ACTION_FAILED | xdotool command failed | The underlying xdotool invocation exited with an error. | Verify the display is active and xdotool is installed and reachable. |
{ "error": "Display is not available or not running", "code": "DISPLAY_NOT_AVAILABLE", "details": { "message": "Display is not available or not running", "help": "Check if the display is running and the displayId is correct" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
DISPLAY_NOT_AVAILABLE | Display not available | The target display service is not running or has stopped responding. | Confirm the display is running and that the displayId matches a live display. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | — | — |
Examples
Section titled “Examples”curl -X POST "https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com/api/v1/display/window/search" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "pattern": "Settings$", "name": true, "onlyVisible": true }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-display-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.display.windows.search({ pattern: 'Settings$', name: true, onlyVisible: true,});