docs/platforms/ios.md
Availability: internal preview. The iOS app is not publicly distributed yet.
node.invoke commands and reports node status events.openclaw.internal.), oropenclaw gateway --port 18789
In the iOS app, open Settings and pick a discovered gateway (or enable Manual Host and enter host/port).
Approve the pairing 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 the external push relay instead of publishing the raw APNs token to the gateway.
Gateway-side requirement:
{
gateway: {
push: {
apns: {
relay: {
baseUrl: "https://relay.example.com",
},
},
},
},
}
How the flow works:
push.apns.register.push.test, background wakes, and wake nudges.What the gateway does not need for this path:
Expected operator flow:
gateway.push.apns.relay.baseUrl on the gateway.push.apns.register automatically after 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.The relay exists to enforce two constraints that direct APNs-on-gateway cannot provide for official iOS builds:
Hop by hop:
iOS app -> gateway
gateway.identity.get.iOS app -> relay
gateway identity delegation
gateway.identity.get.gateway -> relay
push.apns.register.push.test, reconnect wakes, and wake nudges, the gateway signs the send request with its
own device identity.relay -> APNs
Why this design was created:
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 / TestFlight auth such as ASC_KEY_ID and ASC_ISSUER_ID; it does not configure
direct APNs delivery for local iOS builds.
Recommended gateway-host storage:
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 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/.gateway.port, default 18789).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"}'
NODE_BACKGROUND_UNAVAILABLE: bring the iOS app to the foreground (canvas/camera/screen commands require it).A2UI_HOST_NOT_CONFIGURED: the Gateway did not advertise a canvas host URL; check canvasHost in Gateway configuration.openclaw devices list and approve manually.