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

Switch input between Bluetooth and XCTest

/v1/leases/{lease}/controlPUT
xctest builds WebDriverAgent for this iPhone when needed (1-3 min the first time), starts it and waits until it answers; calling it again restarts the runner. Needs Xcode signed in to an Apple account, Developer Mode, the developer trusted on the iPhone and UI Automation on; errors say which step is missing. bluetooth stops the runner. Stream with Accept: text/event-stream to see the build steps.
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
Request Bodyapplication/jsonrequired
control *"bluetooth" | "xctest"
How input reaches the iPhone. bluetooth (default): the Mac is a Bluetooth mouse and keyboard; screenshots come from USB screen capture. xctest: WebDriverAgent, an XCUITest runner, taps, types and takes screenshots (they show dialogs that USB capture hides). Needs Xcode, Developer Mode and UI Automation. No passcode unlock, mute or keyboard shortcuts.
team?string
Apple team id that signs WebDriverAgent. Default the only Apple Development certificate in the keychain.
Pattern: ^[A-Z0-9]{10}$
rebuild?boolean
Build WebDriverAgent again (e.g. after the free team's 7-day signature expired)
Default: false
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 PUT "https://ghosthand.dev/macs/studio/v1/leases/42/control" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "control": "bluetooth", "team": "string", "rebuild": false }'
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 } ] } }