apus qr

Generate a mobile pairing QR and setup code from your current Gateway configuration.

The legacy apus clawbot qr alias accepts every flag below.

apus qr
apus qr --setup-code-only
apus qr --json
apus qr --remote
apus qr --limited
apus qr --voice-node
apus qr --url wss://gateway.example/ws

Official Apus iOS and Android apps connect automatically when their setup-code metadata matches. If a request remains pending (for example, for a non-official client or mismatched metadata), review and approve it:

apus devices list
apus devices approve <requestId>

Options

--token and --password are mutually exclusive. --limited and --voice-node are mutually exclusive.

Setup code contents

The setup code carries an opaque, short-lived bootstrapToken, not the shared gateway token/password. For a wss:// endpoint (or same-host loopback), the default bootstrap flow issues:

Use --limited to keep the same node token while omitting operator.admin from the operator handoff. Pairing-mutation scope is never handed off by a setup code.

Use --voice-node for an embedded or room voice client. It keeps the node token and hands off a separate operator token limited to operator.read and operator.talk; it cannot send messages, mutate configuration, or invoke general write-scoped Gateway methods.

Plaintext LAN ws:// setup remains available, but Apus automatically uses the limited profile because a network observer could capture and race the bearer bootstrap token. Configure wss:// or Tailscale Serve, then generate a new code to get full access.

Gateway URL resolution

Mobile pairing fails closed for Tailscale/public ws:// gateway URLs: use Tailscale Serve/Funnel or a wss:// gateway URL for those. Private LAN addresses and .local Bonjour hosts remain supported over plain ws://, with limited operator access as described above.

The QR command advertises Tailscale URLs only when Apus owns the route through gateway.tailscale.mode=serve|funnel. Legacy external Serve routes that target the ordinary Gateway listener are not advertised because that listener rejects Tailscale-shaped proxy ingress.

If an older setup used gateway.bind=lan with a persistent default HTTPS Serve route, run apus doctor to inspect it. Doctor does not migrate or clear the route because its status cannot prove who owns it, even with --fix; if you confirm it is stale, clear only its root handler, configure gateway.bind=loopback plus gateway.tailscale.mode=serve manually, and restart the Gateway. Custom Serve ports and retired named-Service routes require the same manual cleanup; Doctor prints the relevant guidance.

Unless --url or --public-url is supplied, --remote requires either gateway.remote.url or gateway.tailscale.mode=serve|funnel before URL resolution runs. gateway.publicOrigin alone does not satisfy that prerequisite.

URL selection preserves existing routes: an explicit pairing override, a preferred remote URL, Tailscale Serve/Funnel, a non-preferred remote URL, then bind-derived addresses. For local QR setup, gateway.publicOrigin is the final fallback before the loopback-only error. Without --remote, the configured plugins.entries.device-pair.config.publicUrl supplies the override. Unlike QR setup, cloud enrollment explicitly asks the same resolver to prefer public ingress over discovery for fresh cloud workers.

QR setup, join codes, and cloud enrollment preserve context paths in fully qualified URLs. The device-pair plugin's /pair command retains its historical origin-only URLs, including when the configured publicUrl contains a path.

Auth resolution (no --remote)

Gateways with gateway.auth.mode="trusted-proxy" can generate setup codes without a shared token or password. The proxy still authenticates the mobile connection before it reaches the Gateway. The setup code does not bypass Cloudflare Access or another proxy login. Bootstrap expiry, device binding, and access profiles stay the same.

When no CLI auth override is passed, local gateway auth SecretRefs resolve as follows:

Condition Resolves
gateway.auth.mode="token", or inferred mode with no winning password source gateway.auth.token
gateway.auth.mode="password", or inferred mode with no winning token from auth/env gateway.auth.password
Both gateway.auth.token and gateway.auth.password are configured (including SecretRefs) and gateway.auth.mode is unset fails; set gateway.auth.mode explicitly

Auth resolution (--remote)

If effectively active remote credentials are configured as SecretRefs and neither --token nor --password is passed, the command resolves them from the active gateway snapshot. If the gateway is unavailable, the command fails fast.

This command path requires a gateway that supports the secrets.resolve RPC method. Older gateways return an unknown-method error.

Related