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

Sign this Mac in to ghosthand.dev

/v1/account/loginPOST
Starts a browser sign-in (OAuth device flow) and returns verificationUrl to open. The daemon polls ghosthand.dev by itself until the user approves, denies, or the link expires; read the result from GET /v1/phones or GET /v1/account. Calling it again while a sign-in runs returns the same link. Unix socket only: the setup app and ghosthand account login call it, agents do not.
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
Response
200 ยท Sign-in started, or already running, or already signed in
state *"signed-out" | "signing-in" | "signed-in"
signed-out: no account on this Mac; every lease fails with login-required. signing-in: a browser sign-in runs; open verificationUrl. signed-in: each acquire checks the iPhone and this Mac against the plan.
server *string
The ghosthand.dev server the daemon uses (env GHOSTHAND_SERVER at daemon start)
email?string
Signed-in user
userCode?string
While signing in, the code the browser page shows, to compare
verificationUrl?string
While signing in, the page to open in a browser
lastError?string
Why the last sign-in did not finish (denied or expired), until the next one starts
plan?"free" | "pro"
Only from GET /v1/account. free: one iPhone, on any Mac. pro: every iPhone on each paid Mac, plus the free iPhone anywhere. Acquire on another iPhone fails with subscription-required.
paidMacs?integer
Only from GET /v1/account. Macs the Pro subscription pays for; 0 on free
sourceAccess?boolean
Only from GET /v1/account. True when the account can read the daemon source on GitHub
notice?string
Note about the plan to show the user
subscribeUrl?string
Checkout, or the billing portal when subscribed
dashboardUrl?string
serverError?string
Only from GET /v1/account, when ghosthand.dev could not be reached
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/account/login" \ -H "Authorization: Bearer <token>"
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." }