The paired app
Updated: 2026-07-25 09:40 CEST
Run ibkr app on the Mac to get ibkr on your phone. That single process serves
the progressive web app, handles pairing, streams live updates to paired devices
over /api/events, and sends opt-in canary push notifications.
Pair a phone
ibkr app
In another shell:
ibkr app pair
Scan the QR code or open the printed pairing URL. The URL contains a short-lived pairing id plus nonce, not a durable secret. After the first successful pair, the browser keeps a device credential and the app stores a matching device grant under the app state directory.
On iOS, pair in Safari first and then Add to Home Screen, so the install
snapshot carries the pairing cookie. The installed app's container inherits
Safari's cookies but not Safari's localStorage or IndexedDB, so a key-based
re-login can never run there.
The default app bind is LAN-capable (0.0.0.0:8765), so the same app process
can serve a local browser preview at http://127.0.0.1:8765 and a phone at the
Mac's LAN URL. Use plain ibkr app pair for the phone, so the running app
host's configured public URL is used. For a local preview, override only the
pairing URL:
ibkr app pair --public-url http://127.0.0.1:8765 --json
Do not run the shared app host with --addr 127.0.0.1:8765 when a phone needs
to pair too.
Staying paired
Restarting ibkr app clears only the in-memory session cookie. The paired
device mints a fresh session from its device grant silently, retries transient
failures at page load, and shows the "scan a fresh QR code" instruction only
when the app definitively rejected the device. A stale or already-consumed
pairing URL is recoverable too: the app strips the one-time pairing query and
falls through to the saved device credential before asking for a new QR code.
Session continuity does not depend on script-visible storage. Pairing sets a
long-lived HttpOnly ibkr_app_device cookie whose hash is stored on the device
grant, and the app mints fresh sessions from it. Key/secret logins re-provision
the cookie, and grants keep a capped list of valid cookie hashes so Safari and
the installed app (twin copies of the same cookie jar) never invalidate each
other.
Every auth outcome is logged as ibkr app auth: lines in
~/Library/Logs/ibkr/app.err.log.
Remote access
Remote mode keeps the normal app host local, then opens an outbound connector to
https://remote.osauer.dev:
ibkr app --remote
In another shell:
ibkr app pair
The printed pairing URL uses the relay origin and includes a remote= route id.
Remote mode needs the Mac, TWS/Gateway, ibkr app --remote, and the Cloudflare
Worker route to stay up. The app exposes relay connection state in
/api/bootstrap under relay.
What survives a restart
The route id and connector token are persisted locally and resumed across app
restarts, new builds, and relay-side route expiry. A token-matched resume
revives the route at the relay, so the route id, and with it every paired phone,
survives arbitrary Mac downtime as long as the app state directory keeps
state.json. Registration happens inside the connector loop with backoff, so a
relay or DNS outage at startup degrades the relay instead of killing ibkr app --remote.
A new route id is minted only when the relay definitively rejects the held credentials, and the app logs that paired devices must re-pair.
What the phone sees when something is wrong
Phone-side addressing is double-tracked. The relay sets the ibkr_remote_route
cookie with a 400-day Max-Age and refreshes it on every visit, and the app
mirrors the route id in localStorage. A navigation that arrives without the
cookie gets a recovery page that rebuilds it from the mirror. A navigation while
the Mac connector is down gets an auto-retrying wait page instead of raw JSON.
Running remote mode under launchd
For an unsupervised app host, an app-only restart can switch remote mode on:
ibkr restart --app --remote
Do not pass that override to a loaded LaunchAgent: supervised restart rejects runtime flag overrides because the loaded plist is authoritative. Install a new supervised host in remote mode with:
ibkr setup app --remote
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.osauer.ibkr-app.plist
To change an already-loaded LaunchAgent, run ibkr setup app --remote, unload
the existing gui/$(id -u)/com.osauer.ibkr-app job, and bootstrap the rewritten
plist again. Ordinary later restarts use ibkr restart --app with no --remote
override; launchd preserves the plist configuration.
Restart
ibkr restart
On the default daemon socket, this restarts a running app first and only then
restarts the shared daemon. If no app is running, plain ibkr restart leaves
the app stopped.
The two stages are ordered but not atomic. An app-stage failure leaves the
daemon untouched; a daemon-stage failure does not roll the already-restarted app
back. Fix the reported stage and rerun ibkr restart.
Updating has the
flags, JSON output, and socket-scope rules.
When the com.osauer.ibkr-app LaunchAgent is loaded, the app is restarted
through launchctl kickstart -k. Any unsupervised (orphaned) app process is
stopped first so launchd can own the app again, and app flag overrides are
rejected with a pointer to rewrite and reload ibkr setup app because the plist
arguments win. The loaded job's executable must resolve to the current installed
ibkr binary. launchd may throttle the respawn for about ten seconds; the
command waits for a stable supervised PID.
Without a loaded LaunchAgent, the unsupervised behavior applies: SIGTERM the
running app, preserve its flags such as --addr, --public-url, --remote,
and --state-dir, and start a detached replacement.
Use ibkr restart --app for app-only restart/start workflows, including cases
where no app is running yet. ibkr app restart is an alias for the same thing.
To switch an old local-only app host back to the shared local-preview plus phone mode:
ibkr restart --app --addr 0.0.0.0:8765
When --addr is overridden without --public-url, restart clears any preserved
app --public-url flag so the app can derive the Mac's LAN URL again.
Developing the app
Building or debugging the app itself is contributor work. Canary SPA
Development has the
development and debugging playbook, including P/L semantics and Browser plugin
caveats, and make help lists the app refresh and browser-smoke targets.