Agent-readable docs index: /llms.txt. Full docs in one file: /llms-full.txt. Download /docs.zip to grep all markdown files locally.

Scroll with the mouse wheel

/v1/leases/{lease}/scrollPOST
Moves the pointer to at (default: screen center), then sends wheel ticks there. Scrolls lists that a drag cannot scroll (a drag on a UITableView row is a tap).
Authorizationoptional
bearerAuth?Bearer <token>
Required on the tunnel. An API token of the account, from ghosthand tunnel url --new-token. The unix socket needs no token
Query Parameters
scale?number
Coordinate space of images and points, as a fraction of the native screen pixels. Default: screen points (1/2 on @2x iPhones, 1/3 on @3x), e.g. 414x896 on an iPhone XR instead of 828x1792. Points keep UI text readable for vision models at about a third of the tokens, and stay under every model's resize limit, so coordinates the model reads from the image are the ones to send. Use 1 for native pixels.
Example: 1Min: > 0Max: 1
language?string
OCR language, a BCP 47 tag (e.g. it-IT). English en-US is always included.
Example: "it-IT"Min length: 2
waitText?string
After the input, read the screen with OCR every 150 ms until a line contains this text (case-insensitive). On timeout it fails with wait-text-timeout: the input was sent and is never repeated.
Example: "About"Min length: 1
timeoutMs?number
Deadline of waitText, counted from the end of the input. Needs waitText.
Default: 5000Min: 1Max: 60000
screenshot?"png" | "jpeg"
After the input (and waitText), wait until the screen is still, then return its image in screenshot of the result, in the scale space.
screentext?boolean
After the input (and waitText), wait until the screen is still, then return its OCR text in screen of the result. OCR runs on the native frame, boxes are in the scale space. With screenshot, both come from the same frame.
Default: false
Request Bodyapplication/jsonrequired
direction *"up" | "down" | "left" | "right"
down shows content further down the page (like swiping a finger up); right shows content further right
ticks *integer
Number of wheel ticks. See ghosthand scroll --help for the distance of one tick.
Example: 5Min: 1Max: 1000
at?object
Where the pointer scrolls. Default the screen center.
Show properties
x *number
Distance from the left edge, in the request's scale space (screen points by default)
Example: 207Min: 0
y *number
Distance from the top edge, in the request's scale space (screen points by default)
Example: 448Min: 0
Response
200 · Input sent. JSON by default, server-sent events with `Accept text/event-stream`.
message *string
What was sent, e.g. tapped 207,448
logs *string[]
Progress lines written while the operation ran
screenshot?object
The screen after the input, present when screenshot was asked
Show properties
format *"png" | "jpeg"
width *integer
Image width in the request's scale space
height *integer
Image height in the request's scale space
data *string
Base64 image bytes
screen?object
The screen text after the input, present when screentext was asked
Show properties
width *integer
Screenshot width in the request's scale space
height *integer
Screenshot height in the request's scale space
lines *object[]
Recognized lines, top to bottom
Show item properties
text *string
x *integer
Left edge, in the request's scale space
y *integer
Top edge, in the request's scale space
width *integer
height *integer
confidence *number
OCR confidence, 0...1
Min: 0Max: 1
Default · Error (RFC 9457 problem details). `code` is stable; switch on it, not on `title` or `detail`.
code *"invalid-request" | "unauthorized" | "not-found" | "device-not-found" | "no-device" | "multiple-devices" | "permission-denied" | "bluetooth-unavailable" | "bluetooth-disconnected" | "no-frame" | "device-locked" | "lock-failed" | "pairing-timeout" | "pointer-not-found" | "lock-state-unavailable" | "lockdown-unavailable" | "unlock-failed" | "mute-unconfirmed" | "text-not-found" | "wait-text-timeout" | "cancelled" | "lease-invalid" | "device-leased" | "recording-active" | "no-recording" | "recording-failed" | "daemon-busy" | "tunnel-offline" | "login-required" | "subscription-required" | "unsupported-in-mode" | "xctest-unavailable" | "task-failed" | "internal-error"
Stable error code. Switch on it; title and detail are for people.
title *string
Short summary of the code, the same for every error with this code
status *integer
HTTP status of the error. In stream mode the response status stays 200.
detail *string
What happened and what to do next. Agents should read it.
Request example
curl -X POST "https://ghosthand.dev/macs/studio/v1/leases/42/scroll" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "direction": "up", "ticks": 5, "at": { "x": 207, "y": 448 } }'
Response example
{ "message": "tapped 98,443", "logs": [ "found \"General\" at 98,443" ], "screen": { "width": 414, "height": 896, "lines": [ { "text": "General", "x": 158, "y": 56, "width": 98, "height": 22, "confidence": 1 }, { "text": "About", "x": 66, "y": 145, "width": 50, "height": 20, "confidence": 1 } ] } }