Skip to main content
The mobile API is the server side of the phone app: pairing, per-device tokens, and an overview endpoint sized for a phone. It is a beta feature and off by default.

Turn it on

Open Settings, Beta features and switch on Mobile app access, or set APP_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

  1. In Settings, Mobile devices, click Pair a device and confirm your password (or passkey).
  2. The page shows a one-time code, a pairing link and the server address. The code works once and expires after 10 minutes.
  3. Enter the code in the app, or open the link on the phone.
From the CLI, with a root token:
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 named Mobile: <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 at docs/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).
Last modified on October 12, 2026