{"components":{"securitySchemes":{"bearerAuth":{"description":"Token obtained from POST /auth/login","scheme":"bearer","type":"http"}}},"info":{"description":"Desktop automation API for controlling Linux desktops.","title":"desktopkit API","version":"1.0.0"},"openapi":"3.0.3","paths":{"/actions":{"get":{"description":"Returns the most recent actions executed by the server.","parameters":[{"description":"Maximum number of entries to return","in":"query","name":"limit","required":false,"schema":{"default":100,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"example":[{"action":"mouse.click","duration_ms":42,"params":"...","result":"ok","timestamp":"..."}]}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Get recent action log","tags":["Actions"]}},"/application/close":{"post":{"description":"Closes the window with the given ID. Lock-pflichtig (globaler Input-Lock). Optionaler Query-Parameter `lock_timeout_ms` (default 10000). Bei Lock-Timeout: HTTP 503 mit {error: \"lock_timeout\", waited_ms, held_by}.","requestBody":{"content":{"application/json":{"example":{"window_id":12345}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"status":"ok"}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Close an application window","tags":["Application"]}},"/application/open":{"post":{"description":"Launches an application by its executable path, optionally with arguments. Lock-pflichtig (globaler Input-Lock). Optionaler Query-Parameter `lock_timeout_ms` (default 10000). Bei Lock-Timeout: HTTP 503 mit {error: \"lock_timeout\", waited_ms, held_by}.","requestBody":{"content":{"application/json":{"example":{"args":["file.txt"],"path":"/usr/bin/gedit"}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"pid":12345,"status":"ok"}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Open an application","tags":["Application"]}},"/application/running":{"get":{"description":"Checks whether a process with the given name is currently running.","parameters":[{"description":"Process name to search for","in":"query","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"pids":[12345],"running":true}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Check if application is running","tags":["Application"]}},"/auth/login":{"post":{"description":"Validates the Unix password of the server user and returns a bearer token.","requestBody":{"content":{"application/json":{"example":{"password":"..."}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"expires_in_s":28800,"token":"abc123..."}}},"description":"Success"}},"security":[],"summary":"Authenticate with Unix password","tags":["Auth"]}},"/auth/logout":{"post":{"description":"Removes the bearer token from the server, ending the session.","responses":{"200":{"content":{"application/json":{"example":{"status":"ok"}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Invalidate current token","tags":["Auth"]}},"/auth/token-info":{"get":{"description":"Returns whether the current token is valid and how many seconds remain.","responses":{"200":{"content":{"application/json":{"example":{"remaining_ttl_s":28750,"valid":true}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Check token validity and remaining TTL","tags":["Auth"]}},"/clipboard":{"get":{"description":"Returns the current text content of the system clipboard. Optional ?max_bytes (default 1048576, hard cap) truncates oversize content at a UTF-8 boundary; truncated:true is returned when truncation happened. Lock-free.","parameters":[{"description":"Maximum bytes to return (capped at 1 MiB).","in":"query","name":"max_bytes","required":false,"schema":{"default":1048576,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"example":{"length":17,"text":"clipboard content","truncated":false}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Read clipboard contents","tags":["Clipboard"]},"post":{"description":"Sets the system clipboard to the given text. Lock-pflichtig.","requestBody":{"content":{"application/json":{"example":{"text":"new content"}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"status":"ok"}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Write to clipboard","tags":["Clipboard"]}},"/clipboard/capture":{"post":{"description":"Runs a sequential batch of capture steps. Each step optionally focuses a window, clicks, sends Ctrl+A, sends Ctrl+C, sleeps settle_ms (default 150), reads the clipboard, and stores the text in the named slot. If the clipboard text is identical to the pre-copy state, ok=false with error \"clipboard unchanged after copy\" — but the following steps still run. Holds the global input lock for the whole batch. Lock-pflichtig.","requestBody":{"content":{"application/json":{"example":{"captures":[{"copy":true,"focus_window_id":"0x4400003","select_all":true,"settle_ms":150,"slot":"panel_1"}]}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"results":[{"length":482,"ok":true,"slot":"panel_1","text":"...","truncated":false}]}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Batch capture into clipboard slots","tags":["Clipboard"]}},"/clipboard/slots":{"delete":{"description":"Clears the entire in-memory slot map. Returns 204. Lock-pflichtig.","responses":{"200":{"content":{"application/json":{"example":""}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Delete all slots","tags":["Clipboard"]},"get":{"description":"Returns metadata for all in-memory clipboard slots (name, length, stored_at). Lock-free. Slots are session-scoped and lost on server restart.","responses":{"200":{"content":{"application/json":{"example":[{"length":482,"name":"panel_1","stored_at":"2026-05-19T10:15:00Z"}]}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"List clipboard slots","tags":["Clipboard"]}},"/clipboard/slots/{name}":{"delete":{"description":"Removes the named slot if present. Returns 204 either way. Lock-pflichtig.","responses":{"200":{"content":{"application/json":{"example":""}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Delete a named slot","tags":["Clipboard"]},"get":{"description":"Returns the full contents of the named slot. 404 if missing. Lock-free.","responses":{"200":{"content":{"application/json":{"example":{"length":482,"name":"panel_1","stored_at":"2026-05-19T10:15:00Z","text":"..."}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Read a clipboard slot","tags":["Clipboard"]},"put":{"description":"Creates or overwrites the named slot with the given text. Text is truncated to 1 MiB at a UTF-8 boundary. Lock-pflichtig.","requestBody":{"content":{"application/json":{"example":{"text":"collected text"}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"length":14,"name":"panel_1"}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Store text in a named slot","tags":["Clipboard"]}},"/clipboard/slots/{name}/restore":{"post":{"description":"Writes the slot's text back into the OS clipboard via desktopkit-clipboard. 404 if the slot is missing. Lock-pflichtig.","responses":{"200":{"content":{"application/json":{"example":{"length":482,"name":"panel_1"}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Restore a slot to the OS clipboard","tags":["Clipboard"]}},"/health":{"get":{"description":"Returns server health status. No authentication required.","responses":{"200":{"content":{"application/json":{"example":{"status":"ok"}}},"description":"Success"}},"security":[],"summary":"Health check","tags":["System"]}},"/keyboard/hotkey":{"post":{"description":"Presses a hotkey combination. Keys: ctrl, alt, shift, super, tab, enter, escape, backspace, delete, home, end, pageup, pagedown, arrowup/arrowdown/arrowleft/arrowright, f1-f12, {\"char\":\"a\"}. Optional `duration_ms` (1..=10000) holds the combination for that many milliseconds before releasing. Lock-pflichtig (globaler Input-Lock). Optionaler Query-Parameter `lock_timeout_ms` (default 10000). Bei Lock-Timeout: HTTP 503 mit {error: \"lock_timeout\", waited_ms, held_by}.","requestBody":{"content":{"application/json":{"example":{"duration_ms":1500,"keys":["ctrl",{"char":"s"}]}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"status":"ok"}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Press key combination","tags":["Keyboard"]}},"/keyboard/type":{"post":{"description":"Types the given text string using simulated keystrokes. Optional `duration_ms` (1..=10000) holds each character for that many milliseconds before releasing. Lock-pflichtig (globaler Input-Lock). Optionaler Query-Parameter `lock_timeout_ms` (default 10000). Bei Lock-Timeout: HTTP 503 mit {error: \"lock_timeout\", waited_ms, held_by}.","requestBody":{"content":{"application/json":{"example":{"duration_ms":500,"text":"Hello, World!"}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"status":"ok"}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Type text","tags":["Keyboard"]}},"/mouse/click":{"post":{"description":"Performs a single mouse click at the current cursor position. Lock-pflichtig (globaler Input-Lock). Optionaler Query-Parameter `lock_timeout_ms` (default 10000). Bei Lock-Timeout: HTTP 503 mit {error: \"lock_timeout\", waited_ms, held_by}.","requestBody":{"content":{"application/json":{"example":{"button":"left"}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"status":"ok"}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Click at current position","tags":["Mouse"]}},"/mouse/double-click":{"post":{"description":"Performs a double click at the current cursor position. Lock-pflichtig (globaler Input-Lock). Optionaler Query-Parameter `lock_timeout_ms` (default 10000). Bei Lock-Timeout: HTTP 503 mit {error: \"lock_timeout\", waited_ms, held_by}.","requestBody":{"content":{"application/json":{"example":{"button":"left"}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"status":"ok"}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Double-click at current position","tags":["Mouse"]}},"/mouse/down":{"post":{"description":"Presses the given mouse button at the current cursor position and holds it. Pair with `POST /mouse/up` to control press-and-drag manually (e.g. interactive resize, lasso-select). Each request acquires the global Input-Lock for the duration of the xdotool call only — between down and up the lock is NOT held, so other API consumers can interleave; the desktop simply stays in the pressed state. Optionaler Query-Parameter `lock_timeout_ms` (default 10000). Bei Lock-Timeout: HTTP 503 mit {error: \"lock_timeout\", waited_ms, held_by}.","requestBody":{"content":{"application/json":{"example":{"button":"left"}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"status":"ok"}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Press mouse button (no release)","tags":["Mouse"]}},"/mouse/drag":{"post":{"description":"Performs a mouse drag operation between two screen positions. Lock-pflichtig (globaler Input-Lock). Optionaler Query-Parameter `lock_timeout_ms` (default 10000). Bei Lock-Timeout: HTTP 503 mit {error: \"lock_timeout\", waited_ms, held_by}.","requestBody":{"content":{"application/json":{"example":{"button":"left","from_x":100,"from_y":200,"to_x":300,"to_y":400}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"status":"ok"}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Drag from one position to another","tags":["Mouse"]}},"/mouse/move":{"post":{"description":"Moves the mouse cursor to the given absolute screen position. Lock-pflichtig (globaler Input-Lock). Optionaler Query-Parameter `lock_timeout_ms` (default 10000). Bei Lock-Timeout: HTTP 503 mit {error: \"lock_timeout\", waited_ms, held_by}.","requestBody":{"content":{"application/json":{"example":{"x":100,"y":200}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"status":"ok"}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Move cursor to coordinates","tags":["Mouse"]}},"/mouse/scroll":{"post":{"description":"Moves cursor to (x, y) and scrolls by the given deltas. Lock-pflichtig (globaler Input-Lock). Optionaler Query-Parameter `lock_timeout_ms` (default 10000). Bei Lock-Timeout: HTTP 503 mit {error: \"lock_timeout\", waited_ms, held_by}.","requestBody":{"content":{"application/json":{"example":{"delta_x":0,"delta_y":-3,"x":500,"y":300}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"status":"ok"}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Scroll at position","tags":["Mouse"]}},"/mouse/up":{"post":{"description":"Releases a mouse button previously held via `POST /mouse/down` at the current cursor position. Calling `up` without a preceding `down` is a no-op (xdotool sends the release event regardless). Lock-pflichtig (globaler Input-Lock). Optionaler Query-Parameter `lock_timeout_ms` (default 10000). Bei Lock-Timeout: HTTP 503 mit {error: \"lock_timeout\", waited_ms, held_by}.","requestBody":{"content":{"application/json":{"example":{"button":"left"}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"status":"ok"}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Release a previously-pressed mouse button","tags":["Mouse"]}},"/screens":{"get":{"description":"Returns information about all connected screens/monitors.","responses":{"200":{"content":{"application/json":{"example":{"screens":[{"height":1080,"id":0,"width":1920}]}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"List available screens","tags":["Screenshot"]}},"/screenshot":{"get":{"description":"Takes a screenshot of the entire screen and returns it as base64-encoded PNG.","parameters":[{"description":"Screen index (0 = primary)","in":"query","name":"screen_id","required":false,"schema":{"default":0,"type":"integer"}},{"description":"Draw a red ring around the cursor position","in":"query","name":"cursor_ring","required":false,"schema":{"default":false,"type":"boolean"}},{"description":"Wait until the image has stabilised before returning","in":"query","name":"wait_stable","required":false,"schema":{"default":false,"type":"boolean"}},{"description":"Per-pixel |ΔR|+|ΔG|+|ΔB| threshold (requires wait_stable)","in":"query","name":"pixel_delta","required":false,"schema":{"default":30,"type":"integer"}},{"description":"Max percent of changed pixels to count as stable (requires wait_stable)","in":"query","name":"stable_ratio","required":false,"schema":{"default":0.2,"type":"number"}},{"description":"How long the frame must stay stable (requires wait_stable)","in":"query","name":"stable_window_ms","required":false,"schema":{"default":500,"type":"integer"}},{"description":"Max wait time in ms (requires wait_stable)","in":"query","name":"timeout_ms","required":false,"schema":{"default":5000,"type":"integer"}},{"description":"Gap between captures (requires wait_stable)","in":"query","name":"poll_interval_ms","required":false,"schema":{"default":200,"type":"integer"}},{"description":"Min percent change vs t=0 required; unset = no baseline check","in":"query","name":"min_change_ratio","required":false,"schema":{"type":"number"}},{"description":"Rectangle 'x,y,w,h' excluded from diffing. Repeatable. Composes with watch_region via AND-NOT.","explode":true,"in":"query","name":"ignore_region","required":false,"schema":{"items":{"type":"string"},"type":"array"},"style":"form"},{"description":"Rectangle 'x,y,w,h' that must contain the change. Repeatable. Empty (default) = whole frame. When set, only pixels inside any watch_region count toward stable_ratio / min_change_ratio / pixel_delta. Composes with ignore_region (a pixel counts iff inside any watch_region AND inside no ignore_region).","explode":true,"in":"query","name":"watch_region","required":false,"schema":{"items":{"type":"string"},"type":"array"},"style":"form"},{"description":"Upload destination. When set, the binary uploads the image after capture and returns an `upload` field in the response.","in":"query","name":"upload_url","required":false,"schema":{"type":"string"}},{"description":"HTTP method: POST | PUT | PATCH.","in":"query","name":"upload_method","required":false,"schema":{"default":"POST","type":"string"}},{"description":"Body format: raw (image bytes), multipart (form-data with file + metadata fields), base64-json (JSON body with base64 image).","in":"query","name":"upload_format","required":false,"schema":{"default":"raw","type":"string"}},{"description":"Repeatable `key:value` HTTP header. Caller headers override default `X-Desktopkit-*` headers.","explode":true,"in":"query","name":"upload_header","required":false,"schema":{"items":{"type":"string"},"type":"array"},"style":"form"},{"description":"When false and upload succeeds, the base64 `image` field is omitted from the response (only the upload outcome is returned). When upload fails, base64 image is always included as a fallback.","in":"query","name":"upload_include_inline","required":false,"schema":{"default":true,"type":"boolean"}},{"description":"Logical name for the screenshot. Appears in X-Desktopkit-Name (raw), multipart form-field `name`, and JSON key `name`.","in":"query","name":"upload_name","required":false,"schema":{"default":"screenshot","type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"change_ratio_last":0.0008,"height":1080,"image":"<base64>","polls":7,"stable":true,"timed_out":false,"waited_ms":1240,"width":1920}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Capture full screen","tags":["Screenshot"]}},"/screenshot/cursor-position":{"get":{"description":"Returns the current mouse cursor coordinates.","responses":{"200":{"content":{"application/json":{"example":{"x":512,"y":384}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Get current cursor position","tags":["Screenshot"]}},"/screenshot/region":{"get":{"description":"Takes a screenshot of a rectangular region.","parameters":[{"description":"Left edge X coordinate","in":"query","name":"x","required":true,"schema":{"type":"integer"}},{"description":"Top edge Y coordinate","in":"query","name":"y","required":true,"schema":{"type":"integer"}},{"description":"Region width in pixels","in":"query","name":"width","required":true,"schema":{"type":"integer"}},{"description":"Region height in pixels","in":"query","name":"height","required":true,"schema":{"type":"integer"}},{"description":"Wait until the image has stabilised before returning","in":"query","name":"wait_stable","required":false,"schema":{"default":false,"type":"boolean"}},{"description":"Per-pixel |ΔR|+|ΔG|+|ΔB| threshold (requires wait_stable)","in":"query","name":"pixel_delta","required":false,"schema":{"default":30,"type":"integer"}},{"description":"Max percent of changed pixels to count as stable (requires wait_stable)","in":"query","name":"stable_ratio","required":false,"schema":{"default":0.2,"type":"number"}},{"description":"How long the frame must stay stable (requires wait_stable)","in":"query","name":"stable_window_ms","required":false,"schema":{"default":500,"type":"integer"}},{"description":"Max wait time in ms (requires wait_stable)","in":"query","name":"timeout_ms","required":false,"schema":{"default":5000,"type":"integer"}},{"description":"Gap between captures (requires wait_stable)","in":"query","name":"poll_interval_ms","required":false,"schema":{"default":200,"type":"integer"}},{"description":"Min percent change vs t=0 required; unset = no baseline check","in":"query","name":"min_change_ratio","required":false,"schema":{"type":"number"}},{"description":"Rectangle 'x,y,w,h' excluded from diffing. Repeatable. Composes with watch_region via AND-NOT.","explode":true,"in":"query","name":"ignore_region","required":false,"schema":{"items":{"type":"string"},"type":"array"},"style":"form"},{"description":"Rectangle 'x,y,w,h' that must contain the change. Repeatable. Empty (default) = whole frame. When set, only pixels inside any watch_region count toward stable_ratio / min_change_ratio / pixel_delta. Composes with ignore_region (a pixel counts iff inside any watch_region AND inside no ignore_region).","explode":true,"in":"query","name":"watch_region","required":false,"schema":{"items":{"type":"string"},"type":"array"},"style":"form"},{"description":"Upload destination. When set, the binary uploads the image after capture and returns an `upload` field in the response.","in":"query","name":"upload_url","required":false,"schema":{"type":"string"}},{"description":"HTTP method: POST | PUT | PATCH.","in":"query","name":"upload_method","required":false,"schema":{"default":"POST","type":"string"}},{"description":"Body format: raw (image bytes), multipart (form-data with file + metadata fields), base64-json (JSON body with base64 image).","in":"query","name":"upload_format","required":false,"schema":{"default":"raw","type":"string"}},{"description":"Repeatable `key:value` HTTP header. Caller headers override default `X-Desktopkit-*` headers.","explode":true,"in":"query","name":"upload_header","required":false,"schema":{"items":{"type":"string"},"type":"array"},"style":"form"},{"description":"When false and upload succeeds, the base64 `image` field is omitted from the response (only the upload outcome is returned). When upload fails, base64 image is always included as a fallback.","in":"query","name":"upload_include_inline","required":false,"schema":{"default":true,"type":"boolean"}},{"description":"Logical name for the screenshot. Appears in X-Desktopkit-Name (raw), multipart form-field `name`, and JSON key `name`.","in":"query","name":"upload_name","required":false,"schema":{"default":"screenshot","type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"change_ratio_last":0.0008,"height":300,"image":"<base64>","polls":7,"stable":true,"timed_out":false,"waited_ms":1240,"width":400}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Capture screen region","tags":["Screenshot"]}},"/screenshots":{"post":{"description":"Captures one or more screenshot variants in a single call. Each variant may set `crop` (clip area), `scale_x`/`scale_y` (resize), `format` (`png`|`jpeg`|`webp`) with optional `jpeg_quality`, and `upload` to deliver the image to an external store. Without `upload.include_inline=true`, successful uploads omit the base64 `image`; on any upload failure the base64 is included as a fallback. The response includes an `upload` object per variant with the store's full HTTP response (status, headers, body). Lock-free; safe to poll concurrently with mutating endpoints. Each variant accepts an `upload` JSON object with fields `{ url, method, format, headers, include_inline, name }` — same semantics as the upload_* query params on GET /screenshot.","requestBody":{"content":{"application/json":{"example":{"variants":[{"name":"overview"},{"crop":{"height":600,"width":800,"x":400,"y":200},"format":"jpeg","jpeg_quality":70,"name":"calc_zoom","scale_x":2.0,"scale_y":2.0,"upload":{"headers":{"Authorization":"Bearer ..."},"include_inline":false,"url":"https://store.example.com/upload"}}]}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"images":[{"crop":null,"format":"png","height":1080,"image":"<base64>","name":"overview","ok":true,"scale_x":1.0,"scale_y":1.0,"url":null,"width":1920,"window_id":null},{"crop":{"height":600,"width":800,"x":400,"y":200},"format":"jpeg","height":1200,"image":null,"name":"calc_zoom","ok":true,"scale_x":2.0,"scale_y":2.0,"upload":{"ok":true,"response":{"body":"","headers":{"location":"https://store.example.com/files/abc123.jpg"},"status":200},"status":200},"url":null,"width":1600,"window_id":null}],"native_height":1080,"native_width":1920}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Capture multiple variants (crop, scale, format, upload)","tags":["Screenshot"]}},"/sequence":{"post":{"description":"Runs a client-ordered list of input steps (window.focus, mouse.move, mouse.click, mouse.down, mouse.up, mouse.scroll, mouse.drag, clipboard.write, clipboard.slot_write, clipboard.slot_restore, application.open, application.close, dialog.close, keyboard.type, keyboard.key, sleep, screenshot) while holding the global input lock for the entire batch. Validates the full step list before acquiring the lock; semantic errors return HTTP 400. Returns HTTP 200 with per-step `ok`/`duration_ms`/`error` and a top-level `aborted_at`. `on_error` is either `abort` (default) or `continue` and can be overridden per step. Query parameter `lock_timeout_ms` (default 10000) controls how long to wait for the lock; on timeout returns HTTP 503. The `screenshot` step accepts every option that GET /screenshot does (`screen_id`, `crop`, `window_id`, `cursor_ring`, `scale_factor`/`scale_x`/`scale_y`, `format`, `jpeg_quality`, plus the full wait-stable parameter set: `wait_stable`, `pixel_delta`, `stable_ratio`, `stable_window_ms`, `timeout_ms`, `poll_interval_ms`, `min_change_ratio`, `stable_against`, `ignore_region`, `watch_region`); the captured image and its metadata (`image`, `format`, `width`, `height`, `bytes`, `change_ratio_from_start`, `waited_ms`, `timed_out`, `stable`, …) are flattened into that step's result. The `clipboard.slot_*` steps share the same in-memory slot store as the REST `/clipboard/slots/*` endpoints. The `dialog.close` step runs 3 server-side escalation stages (Escape → Alt+F4 → focus+window-close) on a caller-supplied `window_id`, polling `desktopkit-window list` every 50 ms between stages for up to `wait_dialog_gone_ms` (default 500, max 5000) ms; on success the reached stage is reported as `closed_via` ∈ `\"already-gone\" | \"escape\" | \"alt-f4\" | \"force\"`. The `mouse.drag` step is a dedicated atomic `xdotool` drag (press-move-release with 50 ms internal wait) — preferred over the `mouse.down` + `mouse.move` + `mouse.up` triplet for apps that distinguish a held drag from three separate events. The `screenshot` step also accepts an `upload` object with fields `{ url, method, format, headers, include_inline, name }` to upload the captured image to an external store after capture.","parameters":[{"description":"How long to wait for the global input lock before returning 503.","in":"query","name":"lock_timeout_ms","required":false,"schema":{"default":10000,"type":"integer"}}],"requestBody":{"content":{"application/json":{"example":{"on_error":"abort","steps":[{"name":"focus-calc","type":"window.focus","window_id":"0x4400003"},{"name":"paste-order-id","slot_name":"current_order","type":"clipboard.slot_restore"},{"keys":"ctrl+v","name":"hotkey-paste","type":"keyboard.key"},{"delta_x":0,"delta_y":5,"name":"scroll-down","type":"mouse.scroll","x":600,"y":400},{"name":"submit","type":"mouse.click","x":500,"y":400},{"ms":150,"name":"settle","type":"sleep"},{"name":"close-confirm","type":"dialog.close","wait_dialog_gone_ms":800,"window_id":"0x4500001"},{"format":"jpeg","jpeg_quality":80,"name":"snap","type":"screenshot"}]}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"aborted_at":null,"results":[{"duration_ms":12,"name":"focus-calc","ok":true,"type":"window.focus"},{"duration_ms":18,"length":8,"name":"paste-order-id","ok":true,"type":"clipboard.slot_restore"},{"duration_ms":4,"name":"hotkey-paste","ok":true,"type":"keyboard.key"},{"duration_ms":21,"name":"scroll-down","ok":true,"type":"mouse.scroll"},{"duration_ms":7,"name":"submit","ok":true,"type":"mouse.click"},{"duration_ms":151,"name":"settle","ok":true,"type":"sleep"},{"closed_via":"escape","duration_ms":62,"name":"close-confirm","ok":true,"type":"dialog.close"},{"bytes":184523,"duration_ms":34,"format":"jpeg","height":1080,"image":"<base64>","name":"snap","ok":true,"type":"screenshot","width":1920}],"total_duration_ms":309}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Execute an ordered batch of input steps under the global lock","tags":["Sequence"]}},"/windows":{"get":{"description":"Returns a list of all open windows with their IDs, titles, and bounds. `bounds` is `{ x, y, width, height }` in native screen pixels. Lock-frei (Polling-fähig).","responses":{"200":{"content":{"application/json":{"example":[{"bounds":{"height":600,"width":800,"x":0,"y":0},"id":12345,"is_dialog":false,"is_focused":true,"pid":4321,"process_name":"gnome-terminal","title":"Terminal"}]}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"List all windows","tags":["Windows"]}},"/windows/dialog":{"get":{"description":"Checks if there is an active dialog or modal window.","responses":{"200":{"content":{"application/json":{"example":{"dialog":null}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Detect active dialog/modal","tags":["Windows"]}},"/windows/focused":{"get":{"description":"Returns information about the currently focused window, including its bounds. Lock-frei.","responses":{"200":{"content":{"application/json":{"example":{"bounds":{"height":600,"width":800,"x":0,"y":0},"id":12345,"is_dialog":false,"is_focused":true,"pid":4321,"process_name":"gnome-terminal","title":"Terminal"}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Get focused window","tags":["Windows"]}},"/windows/{id}/focus":{"post":{"description":"Brings the window with the given ID to the foreground and focuses it. Lock-pflichtig (globaler Input-Lock). Optionaler Query-Parameter `lock_timeout_ms` (default 10000). Bei Lock-Timeout: HTTP 503 mit {error: \"lock_timeout\", waited_ms, held_by}.","parameters":[{"description":"Window ID","in":"path","name":"id","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"example":{"status":"ok"}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Focus a window by ID","tags":["Windows"]}},"/windows/{id}/move":{"post":{"description":"Sets position and size of the given window in one call. Body: `{ \"bounds\": { x, y, width, height } }`. To only move, keep the existing width/height; to only resize, keep the existing x/y. Lock-pflichtig (siehe globalen Lock). Accepts optional `?lock_timeout_ms=<u64>` (default 10000).","parameters":[{"description":"Window ID (numeric, e.g. \"12345\")","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Max ms to wait for the global input lock before returning HTTP 503.","in":"query","name":"lock_timeout_ms","required":false,"schema":{"default":10000,"type":"integer"}}],"requestBody":{"content":{"application/json":{"example":{"bounds":{"height":720,"width":1280,"x":200,"y":100}}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"bounds":{"height":720,"width":1280,"x":200,"y":100},"id":"12345","ok":true}}},"description":"Success"}},"security":[{"bearerAuth":[]}],"summary":"Move and resize a window","tags":["Windows"]}}},"servers":[{"url":"http://0.0.0.0:8082"}],"tags":[{"name":"Auth"},{"name":"Screenshot"},{"name":"Mouse"},{"name":"Keyboard"},{"name":"Clipboard"},{"name":"Sequence"},{"name":"Windows"},{"name":"Application"},{"name":"Actions"},{"name":"System"}]}