Soar API Setup
Connect Expo iOS and Android to a conforming Unified Platform host: env keys, host environment, sessions, and verification.
This is the Unified Mode counterpart to Supabase Setup: everything needed to run the native app against a conforming Web host instead of a Supabase project. If you haven't picked a mode yet, start at Backend Modes — note that Expo Web stays Standalone-only; in a Unified product the browser is served by the Web host.
No host is privileged by framework name — Next.js, Nuxt, and TanStack Start all work; conformance to the frozen contracts is the compatibility boundary.
1. Deploy a conforming host
Deploy one of the Web templates as a Unified Platform Host and complete its host-side setup (database, Better Auth URL and secret, App API):
Sign up on the Web and verify /api/auth/get-session works before touching the
Expo side. For the cross-template picture, see the
platform Unified Quick Start.
2. Configure the client build
Unified Mode needs two environment keys (per build profile — the mode is
independent of APP_VARIANT, so each development/preview/production build can
target its own deployment):
# .env / EAS build profile
EXPO_PUBLIC_SOAR_BACKEND_MODE=soarApi
# Deployment origin, without /api/app/v1 — the app appends the API path.
EXPO_PUBLIC_SOAR_API_BASE_URL=https://your-web-app.example.com
# Optional: product link shown when a Unified build is opened in a browser.
EXPO_PUBLIC_UNIFIED_WEB_APP_URL=https://your-web-app.example.comThe EXPO_PUBLIC_SUPABASE_* keys can stay unset — the Supabase client is never
built in this mode. The request Origin is derived from the app scheme per
variant: soar-starter-expo:// in production, soar-starter-expo-dev:// /
soar-starter-expo-preview:// for development and preview builds.
Configuration errors stop this build. A missing or malformed
EXPO_PUBLIC_SOAR_API_BASE_URL raises BackendConfigurationError, and
opening a Unified build on Expo Web raises UnifiedWebUnsupportedError —
there is no silent Supabase fallback.
3. Configure the host for this app
On the server (the selected host's deployment environment), the app expects the native support described in Native Authentication:
| Server variable | Why the Expo app needs it |
|---|---|
APP_SCHEME | Must equal the active variant's scheme (e.g. soar-starter-expo://, or the -dev/-preview variant while developing); Better Auth rejects untrusted origins with 403. |
APPLE_APP_BUNDLE_IDENTIFIER | Audience for native Apple ID-token sign-in on iOS (variant-suffixed bundle IDs included). |
GOOGLE_IOS_CLIENT_ID / GOOGLE_ANDROID_CLIENT_ID | Audience allow-list entries for native Google ID-token sign-in. |
REVENUECAT_WEBHOOK_AUTHORIZATION + REVENUECAT_PRODUCT_* | Syncs App Store / Google Play purchases into the shared subscription row. |
APNS_* | Server-side APNs credentials for the iOS test-push endpoint. |
STORAGE_PROVIDER (+ R2_* in production) | Presigned-upload object storage backing the uploads service. |
4. Sessions live in SecureStore
In Unified Mode the app authenticates with Better Auth Bearer tokens (the
set-auth-token response header), not cookies:
- The token is persisted only in
expo-secure-store(ExpoSecureSessionStore) — never AsyncStorage, never query caches, never logs. - The storage key is namespaced by build variant and API base URL, so a development or preview token is never restored by a production build.
- On relaunch the stored session is restored and validated against the server; sign-out clears both the stored token and in-memory state.
- Sessions are database-backed and revocable: revoking a session on the web
invalidates the mobile token immediately, and the app treats
401as "sign in again" — it never retry-loops and never falls back to Supabase auth.
5. RevenueCat appUserID
The purchases layer always logs in to RevenueCat with the current user's ID —
in Unified Mode that is the Better Auth user ID. This is what lets the server's
RevenueCat webhook resolve app_user_id
back to the same Better Auth user and update the one shared subscription
row that the web reads too.
Keep RevenueCat's Restore Behavior set to Transfer disabled: a receipt already attached to another account should surface a support-facing error, not silently move entitlements between users.
6. Push notifications
Unified builds register device tokens per platform: APNs on iOS, FCM on Android. The frozen v1 test-push endpoint is APNs-only, so test sending works on iOS and is reported as unsupported on Android — real FCM delivery is unaffected.
Verify the setup
Run the shared smoke test across both surfaces:
- Sign up or sign in on iOS/Android (email, Apple on iOS, Google on native), then open the same account on the Web.
- Update the profile on one surface and read it from the other.
- Create, edit, reorder, and delete a todo across both surfaces.
- Upload an avatar and confirm its completion URL is readable.
- Revoke the mobile session from the Web and verify the app returns to sign-in.
- After a sandbox purchase, confirm the same entitlement appears on both surfaces.
- Register the device token on each platform; trigger the APNs test push on iOS.
- Open the Unified build on Expo Web and confirm it shows the Web-app notice instead of signing in.
For a concrete end-to-end walkthrough of one host-and-client pair, follow Next.js + Swift Unified Setup — the steps map one-to-one onto Expo.
Related pages
Backend Modes
The decision page: comparison, mode resolution, and what does not mix.
Configuration
Every env key, including EXPO_PUBLIC_SOAR_BACKEND_MODE and the variant system.
Native Authentication
Bearer sessions, email OTP, Apple/Google ID-token sign-in.
Next.js + Swift Unified Setup
One concrete host-and-client example; steps map onto Expo.
