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

Set control mode from setup

/v1/phones/{phone}/controlPUT
For the setup app only, after a user click. Saves the mode and, for xctest, builds and starts WebDriverAgent in the background; GET /v1/phones shows its progress in xctest. Fails with device-leased while an agent holds the iPhone. Agents use PUT /v1/leases/{lease}/control instead.
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
202 · Saved; the runner starts in the background
message *string
What the daemon did, for people and logs
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/phones/<phone>/control" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "control": "bluetooth", "team": "string", "rebuild": false }'
Response example
{ "code": "device-leased", "title": "iPhone leased by another client", "status": 409, "detail": "Tommy's iPhone is in use by another lease:\n ses_456 (book a table on OpenTable), acquired 2 min ago, last used 10 s ago (tap)\nDecide first: if that task is still active, give up or tell the user the iPhone is busy." }