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.com

The 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 variableWhy the Expo app needs it
APP_SCHEMEMust 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_IDENTIFIERAudience for native Apple ID-token sign-in on iOS (variant-suffixed bundle IDs included).
GOOGLE_IOS_CLIENT_ID / GOOGLE_ANDROID_CLIENT_IDAudience 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 401 as "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.

On this page