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

Swipe like a finger

/v1/leases/{lease}/swipePOST
A quick human-like finger swipe from from to to: fast start, lifted while still moving, so feeds like TikTok and lists keep going. Speed, easing and a slight sideways bow are random on every call; durationMs fixes the speed. Use it for Home Screen pages, feeds, carousels, pickers and sliders. For exact drag and drop use hold-drag.
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
from *object
Where the drag starts
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
to *object
Where the drag ends
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
durationMs?number
Default is random and human-like, 80-180 ms growing with the distance. It starts fast and lifts while still moving, so feeds keep going. Set it to fix the speed.
Min: 16Max: 10000
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/swipe" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "from": { "x": 207, "y": 300 }, "to": { "x": 207, "y": 600 }, "durationMs": 400 }'
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 } ] } }