Connect your phone and browser
Built from docs/ONBOARDING.md at 0f4fe49.
For someone who already has a gateway running and wants to reach it from a phone and from a browser, on their own network, with no baked-in token. Two client surfaces, one pairing flow.
Not built yet. Every step below —
orchestra pair, the handshake, both client connect flows — describes what you will do once this lands Saturday morning; none of it is onmainright now, and none of the commands below will work if you try them tonight. Checkorchestra pair --helpfirst if you’re reading this after Saturday morning to confirm it has landed.
Before anything else: log in
Do this before you do anything below — install and log in to one agent CLI
(Claude Code, Gemini CLI, or Codex). docs/INSTALL.md §0 has the exact
commands. If you have no subscription to any of them, Gemini CLI’s free tier
needs no credit card — that’s the zero-cost path onto this whole doc.
Two things people get wrong here, stated up front so you don’t have to guess mid-flow:
- The gateway and the dashboard are different ports.
8890is the gateway (what you’re pairing to);8891is the web dashboard (what you open in a browser). Typing the dashboard’s port where the gateway’s goes is the single most common mistake on this page. - The pairing code is a credential. Anyone who has it before you use it can pair their own device to your server. Never screenshare a terminal while a live code is on screen.
1. Run your gateway
orchestra up --detach && orchestra status
Confirm the gateway row has a live pid. Note the host you’ll reach it at —
a Tailscale hostname, a LAN IP, or 127.0.0.1 if the phone and the gateway are
on the same machine (rare outside a demo). This doc uses
your-gateway.example.net as a placeholder everywhere; substitute your real
host, never share it outside people you’re actually pairing.
2. Run orchestra pair
orchestra pair
This prints a QR code as text in the terminal — no image viewer needed,
so it works over a bare SSH session on a headless VPS — plus the raw pairing
code underneath it, in case scanning isn’t convenient. The code is
short-lived and single-use: it expires the moment it’s exchanged, or after
about 10 minutes, whichever comes first. Running orchestra pair again always
mints a fresh one; an old code left on screen goes stale on its own.
Do not screenshare this terminal while the code is visible. The code is
effectively a password for your gateway for the few minutes it’s live — anyone
who has it before you use it can pair their own device to your server. Run
orchestra pair again to invalidate an old code if you think someone saw it.
3. Connect the web dashboard
The dashboard can’t scan a QR code, so type the two values instead:
- Open your own dashboard, at the address
orchestra upprinted when you started it. - It shows a connect screen asking for a gateway URL and a code — paste the URL you’re running the gateway at and the code from step 2.
- The dashboard exchanges the code for a token itself and stores it for that browser; you don’t see or copy the token directly.
Plain http:// is fine here if the gateway and your browser are on the same
LAN — the dashboard says so quietly, it isn’t a warning you need to click
through.
4. Connect the iOS app
First launch shows a pairing screen, not the approvals list:
- Scan — point the camera at the terminal QR from step 2.
- Type it in — enter the gateway URL and the code by hand (the same values the dashboard used), if scanning isn’t practical.
Either way, the app exchanges the code for its own token and stores both in Keychain. It does not ask again unless you revoke that device from Settings or its pairing genuinely expires.
The handshake, if you’re curious what “connected” actually checks
Neither client trusts a plain 200 OK. Two calls, in order:
GET /gateway/identity— unauthenticated, frozen forever:{"service":"orchestraos-gateway","protocol":1}. This just confirms you’re talking to an OrchestraOS gateway at all, before any credential is on the table.GET /gateway/capabilities— behind your paired token, tells the client what this gateway actually offers:{"providers":[...],"surfaces":[...], "pending":N}. The client renders whatever’s in the list — it never assumes a fixed set, so a gateway can add a provider or a surface later without an app update.
This is a deliberately different pair of endpoints from /health (that one’s
for orchestra doctor and the supervisor — don’t confuse the two if you’re
scripting against either).
What can go wrong, and what it means
Every client (web, iOS, this doc) shows the exact same words, verbatim, for each of these five states:
- Can’t resolve the address. Can’t find
<host>. Check the spelling — and if that’s a tailnet name, make sure this device is on the same tailnet. - Nothing listening. Found
<host>, but nothing is answering on port<port>. Isorchestra uprunning on that machine? - Answered, but not a gateway. Something is running at
<host>:<port>, but it isn’t an OrchestraOS gateway. Check the port — the gateway is usually 8890, and 8891 is the dashboard. - Refused. That is an OrchestraOS gateway, but it didn’t accept this
token. Run
orchestra pairon the server and use the new code. - Found a gateway, but it predates pairing. Found an OrchestraOS gateway
at
<host>:<port>. This version predates device pairing, so there’s nothing to connect to yet.
The first two are both “nothing valid answered” — a typo/DNS problem versus a “nothing’s listening on that port” problem, and they need opposite fixes — the third is “found something, but not a gateway,” the fourth is “found a gateway, but the token’s no good; re-pair,” and the fifth is “found a real gateway, but it’s from before pairing existed.”
What success looks like
Once both calls succeed, every client (web, iOS, this doc) shows the same line, word for word except the host and version:
Connected to your-gateway.example.net · gateway v1 · no cards yet — they appear here when an agent needs a decision.
That whole line is the success state on a fresh pairing with zero agents and
zero cards — it is not a placeholder or an error, even though nothing else on
the screen has happened yet. Fire one approval card (docs/GATE.md step 5) to
see the surface actually render something.
Notes for anyone building against this
- The pairing exchange (
orchestra pair→ scan/type →POST /pair/exchange {code}→{base_url, token}) is the primary path. If that route isn’t live yet on your checkout,orchestra pair’s own output will say so — don’t assume the shape above without checking. /gateway/identity’s fields are frozen:protocolis an integer, never a semver string, and no field is ever renamed or removed once shipped./gateway/capabilitiesis additive-only — treat any key your client doesn’t recognize as “ignore it,” never as an error, and treat an absent block (e.g. noproviders) as “unknown,” never as “none available.”- See
docs/tracks/01-device-pairing.mdfor the fuller device-pairing design this onboarding flow is built on; if the two documents disagree on a route name or a response shape, this page (written against the frozen contract) is the one to trust, and the track doc needs an update.
Next: The reference install