Turn it on
Open Settings, Beta features and switch on Mobile app access, or setAPP_EXPERIMENTAL=mobile-api. Set APP_MOBILE_API=off to disable it entirely; that wins over the Settings switch. While it is off, every /api/v1/mobile/ route answers 404 with code experimental_feature_disabled.
Pair a phone
- In Settings, Mobile devices, click Pair a device and confirm your password (or passkey).
- The page shows a one-time code, a pairing link and the server address. The code works once and expires after 10 minutes.
- Enter the code in the app, or open the link on the phone.
mobile pair asks for your password (and authenticator code when enrolled), the same as apps env get --reveal.
What a paired phone can do
Each phone gets its own API token namedMobile: <device name>. It shows up in Settings, API tokens and can be revoked there or from the Mobile devices page.
The token expires after 30 days by default (
APP_MOBILE_TOKEN_TTL, for example 7d or 720h, at most 365 days). A security policy with a shorter maximum token lifetime wins. The abilities are also capped at what the pairing user holds.
Endpoints
POST /api/v1/mobile/pair is rate limited per IP (APP_MOBILE_PAIR_PER_MINUTE, default 10) and per code. Five wrong guesses burn a code. Every failure returns the same 400 with code pairing_invalid, so a caller cannot tell an unknown code from an expired or used one.
Overview and caching
GET /api/v1/mobile/overview returns everything the home screen needs in one request. Lists are capped (50 apps, top 5 attention items); apps_total and attention.count carry the full counts. The response carries an ETag. Send it back as If-None-Match and an unchanged overview costs a 304 with no body.
Typed client
Every route here is in the generated OpenAPI document atdocs/public/openapi.json (see API reference), so the app can generate a typed TypeScript client from it instead of hand writing request shapes.
Audit
Pairing code created, device paired, failed pairing and device revoked each write an audit entry (mobile.pairing_created, mobile.paired, mobile.pair_failed, mobile.revoked).