docs/platforms/ios.md
Availability: iPhone app builds are distributed through Apple channels when enabled for a release. Local development builds can also run from source.
node.invoke commands and reports node status events.tts.speak clips with the configured TTS provider and falls back to on-device speech when gateway audio is unavailable or unplayable. Playback stops on session switch or backgrounding.openclaw.internal.), oropenclaw gateway --port 18789 --tailscale serve
For a trusted same-LAN setup, use an authenticated gateway.bind: "lan"
instead. The default loopback bind is not reachable from a phone. If the
Gateway has not been configured yet, run openclaw onboard first so setup-code
creation has a token or password auth path.
Open the Control UI, select Nodes, and click Pair mobile device in the Devices card.
In the iOS app, open Settings -> Gateway, scan the QR code (or paste the setup code), and connect.
If the setup code contains both LAN and Tailscale Serve routes, the app probes them in order and saves the first reachable endpoint.
The official app connects automatically. If Devices shows a pending request, review its role and scopes before approving it.
The Apple Watch companion does not have a separate OpenClaw pairing approval. Pair the Watch with the iPhone in Apple's Watch app, install OpenClaw from Watch app -> My Watch -> Available Apps, then open OpenClaw once on both devices. OpenClaw follows Apple Watch pairing and install changes immediately; the Gateway's device approval covers the iPhone node.
The Control UI button requires an already paired session with operator.admin.
As a terminal fallback, pick a discovered gateway in the iOS app (or enable
Manual Host and enter host/port), then approve the request on the Gateway host:
openclaw devices list
openclaw devices approve <requestId>
If the app retries pairing with changed auth details (role/scopes/public key), the previous pending request is superseded and a new requestId is created. Run openclaw devices list again before approval.
Optional: if the iOS node always connects from a tightly controlled subnet, you can opt in to first-time node auto-approval with explicit CIDRs or exact IPs:
{
gateway: {
nodes: {
pairing: {
autoApproveCidrs: ["192.168.1.0/24"],
},
},
},
}
This is disabled by default. It applies only to fresh role: node pairing with no requested scopes. Operator/browser pairing and any role, scope, metadata, or public-key change still require manual approval.
openclaw nodes status
openclaw gateway call node.list --params "{}"
Official distributed iOS builds use an external push relay instead of publishing the raw APNs token to the gateway. Official App Store builds from the public release lane use the hosted relay at https://ios-push-relay.openclaw.ai; this base URL is hardcoded for App Store distribution and does not read any override.
Custom relay deployments require a deliberately separate iOS build/deployment path whose relay URL matches the gateway relay URL. The App Store release lane never accepts a custom relay URL. If you're using a custom relay build, set the matching gateway relay URL:
{
gateway: {
push: {
apns: {
relay: {
baseUrl: "https://relay.example.com",
},
},
},
},
}
How the flow works:
gateway.identity.get) and includes it in relay registration, so the relay-backed registration is delegated to that specific gateway.push.apns.register.push.test, background wakes, and wake nudges.What the gateway does not need for this path: no deployment-wide relay token, no direct APNs key for official App Store relay-backed sends.
Expected operator flow:
gateway.push.apns.relay.baseUrl on the gateway only when using a deliberately separate custom relay build.push.apns.register once it has an APNs token, the operator session is connected, and relay registration succeeds.push.test, reconnect wakes, and wake nudges can use the stored relay-backed registration.When iOS wakes the app for a silent push, background refresh, or significant-location event, the app attempts a short node reconnect and then calls node.event with event: "node.presence.alive". The gateway records this as lastSeenAtMs/lastSeenReason on the paired node/device metadata only after the authenticated node device identity is known.
The app treats a background wake as successfully recorded only when the gateway response includes handled: true. Older gateways may acknowledge node.event with { "ok": true }; that response is compatible but does not count as a durable last-seen update.
Compatibility note:
OPENCLAW_APNS_RELAY_BASE_URL still works as a temporary env override for the gateway (gateway.push.apns.relay.baseUrl is the config-first path).OPENCLAW_PUSH_RELAY_BASE_URL build-time env var only affects local/sandbox iOS build modes.The relay exists to enforce two constraints direct APNs-on-gateway cannot provide for official iOS builds:
Hop by hop:
iOS app -> gateway: the app pairs with the gateway through the normal Gateway auth flow, giving it an authenticated node session plus an authenticated operator session. The operator session calls gateway.identity.get.iOS app -> relay: the app calls the relay registration endpoints over HTTPS with App Attest proof plus a StoreKit app transaction JWS. The relay validates the bundle ID, App Attest proof, and Apple distribution proof, and requires the official/production distribution path — this is what blocks local Xcode/dev builds from using the hosted relay, since a local build cannot satisfy the official Apple distribution proof.gateway identity delegation: before relay registration, the app fetches the paired gateway identity from gateway.identity.get and includes it in the relay registration payload. The relay returns a relay handle and a registration-scoped send grant delegated to that gateway identity.gateway -> relay: the gateway stores the relay handle and send grant from push.apns.register. On push.test, reconnect wakes, and wake nudges, the gateway signs the send request with its own device identity; the relay verifies both the stored send grant and the gateway signature against the delegated gateway identity from registration. Another gateway cannot reuse that stored registration, even if it somehow obtains the handle.relay -> APNs: the relay owns the production APNs credentials and the raw APNs token for the official build. The gateway never stores the raw APNs token for relay-backed official builds; the relay sends the final push to APNs on behalf of the paired gateway.Why this design was created: to keep production APNs credentials out of user gateways, avoid storing raw official-build APNs tokens on the gateway, allow hosted relay usage only for official OpenClaw iOS builds, and prevent one gateway from sending wake pushes to iOS devices owned by a different gateway.
Local/manual builds remain on direct APNs. If you are testing those builds without the relay, the gateway still needs direct APNs credentials:
export OPENCLAW_APNS_TEAM_ID="TEAMID"
export OPENCLAW_APNS_KEY_ID="KEYID"
export OPENCLAW_APNS_PRIVATE_KEY_P8="$(cat /path/to/AuthKey_KEYID.p8)"
These are gateway-host runtime env vars, not Fastlane settings. apps/ios/fastlane/.env only stores App Store Connect auth such as APP_STORE_CONNECT_KEY_ID and APP_STORE_CONNECT_ISSUER_ID; it does not configure direct APNs delivery for local iOS builds.
Recommended gateway-host storage, consistent with other provider credentials under ~/.openclaw/credentials/:
mkdir -p ~/.openclaw/credentials/apns
chmod 700 ~/.openclaw/credentials/apns
mv /path/to/AuthKey_KEYID.p8 ~/.openclaw/credentials/apns/AuthKey_KEYID.p8
chmod 600 ~/.openclaw/credentials/apns/AuthKey_KEYID.p8
export OPENCLAW_APNS_PRIVATE_KEY_PATH="$HOME/.openclaw/credentials/apns/AuthKey_KEYID.p8"
Do not commit the .p8 file or place it under the repo checkout.
The iOS app browses _openclaw-gw._tcp on local. and, when configured, the same wide-area DNS-SD discovery domain. Same-LAN gateways appear automatically from local.; cross-network discovery can use the configured wide-area domain without changing the beacon type.
If mDNS is blocked, use a unicast DNS-SD zone (choose a domain; example: openclaw.internal.) and Tailscale split DNS. See Bonjour for the CoreDNS example.
In Settings, enable Manual Host and enter the gateway host + port (default 18789).
The app keeps a registry of every gateway it has paired with, so you can switch between them without pairing again:
The iOS node renders a WKWebView canvas. Use node.invoke to drive it:
openclaw nodes invoke --node "iOS Node" --command canvas.navigate --params '{"url":"http://<gateway-host>:18789/__openclaw__/canvas/"}'
Notes:
/__openclaw__/canvas/ and /__openclaw__/a2ui/, from the Gateway HTTP server (same port as gateway.port, default 18789).canvas.a2ui.push and canvas.a2ui.reset use the bundled app-owned A2UI page.canvas.navigate and {"url":""}.The iOS app is a mobile node surface, not a Codex Computer Use backend. Codex Computer Use and cua-driver mcp control a local macOS desktop through MCP tools; the iOS app exposes iPhone capabilities through OpenClaw node commands such as canvas.*, camera.*, screen.*, location.*, and talk.*.
Agents can still operate the iOS app through OpenClaw by invoking node commands, but those calls go through the gateway node protocol and follow iOS foreground/background limits. Use Codex Computer Use for local desktop control and this page for iOS node capabilities.
openclaw nodes invoke --node "iOS Node" --command canvas.eval --params '{"javaScript":"(() => { const {ctx} = window.__openclaw; ctx.clearRect(0,0,innerWidth,innerHeight); ctx.lineWidth=6; ctx.strokeStyle=\"#ff2d55\"; ctx.beginPath(); ctx.moveTo(40,40); ctx.lineTo(innerWidth-40, innerHeight-40); ctx.stroke(); return \"ok\"; })()"}'
openclaw nodes invoke --node "iOS Node" --command canvas.snapshot --params '{"maxWidth":900,"format":"jpeg"}'
talk.realtime.transport is webrtc; an explicit gateway-relay configuration remains Gateway-owned. See Talk mode.talk capability and can declare talk.ptt.start, talk.ptt.stop, talk.ptt.cancel, and talk.ptt.once; the Gateway allows those push-to-talk commands by default for trusted Talk-capable nodes.NODE_BACKGROUND_UNAVAILABLE: bring the iOS app to the foreground (canvas/camera/screen commands require it).A2UI_HOST_UNAVAILABLE: the bundled A2UI page was not reachable in the app WebView; keep the app foregrounded on the Screen tab and retry.openclaw devices list and approve manually.watchPaired: true
and watchAppInstalled: true in watch.status. If pairing is false, pair the
Watch in Apple's Watch app. If installation is false, install the companion
from My Watch -> Available Apps. After either change, open OpenClaw on the
Watch once; immediate reachability still requires both apps to be running,
while queued updates can arrive later in the background.