getting-startedBuild a Loop app

Build a Loop app

What this is: how to build a first-party Loop app (one owned by Loop, served to Loop users) against the platform.

Who it’s for: anyone working on loop-health, apps/admin, the developer portal, the LoopBio clinician portal, or a future Loop-owned consumer app.

Time: an afternoon to wire OAuth + first call; rest depends on the app.

How first-party differs from third-party

Loop apps run the same OAuth flow as any partner app. Eating our own dog food keeps the spec honest. The differences:

ConcernThird-partyFirst-party
OAuth client registrationSelf-service via developer portalPre-registered by platform team
Auto-approve scopesNo (user always sees consent)Yes, for scopes the platform deems implicit (openid profile email read:account)
BAARequired for PHI scopesLoop is its own covered entity
Hostnames<your-app>.example.com*.loop.health (varies per app)
BrandingYour brand on consent screenLoop brand

The auth flow + SDK usage are identical. If you can follow build a partner app, you can build a Loop app.

Standard stack

Loop apps converge on:

  • Next.js 15 (App Router)
  • Clerk for sign-in UI on the consumer side
  • @platform/sdk + @platform/sdk-<service> for platform calls
  • @platform/next-route-handlers for API route boilerplate (withAuth, respondJson, proxyToService)
  • Tailwind + shadcn-derived components for UI
  • Vercel for hosting (apps live in ~/Dev/loop/loop-platform, separate repo)

Workflow: sign-in → access token

The “exchange Clerk session for Loop access token” step: identity’s /v1/oauth/authorize accepts an internal grant where the Clerk session is the user proof (configured per first-party client). The app server-side gets back a normal access token and stores it in the user’s session.

Route handler pattern

Loop apps put route handlers behind withAuth from @platform/next-route-handlers:

// app/api/biomarkers/route.ts
import { withAuth, respondJson } from "@platform/next-route-handlers";
import { createClinicalClient } from "@platform/sdk-clinical";
import { platformHost, SERVICE_NAMES } from "@platform/hosts";
 
export const GET = withAuth(async ({ token }) => {
  const clinical = createClinicalClient({
    baseUrl: platformHost({ service: SERVICE_NAMES.CLINICAL }),
    accessToken: token,
  });
  const result = await clinical.listBiomarkers();
  return respondJson(result);
});

withAuth resolves the Clerk session, exchanges for a Loop access token, and attaches it. respondJson enforces the platform JSON error shape.

No raw fetch() to platform services from app code. The no-raw-platform-fetch convention check rejects PRs that do this.

Auth surfaces in a first-party app

  1. Sign-in — Clerk modal or hosted page. Loop apps use Clerk.
  2. Token exchange — happens server-side in withAuth middleware.
  3. Refresh — SDK handles automatically when access token expires.
  4. Sign-out — Clerk sign-out + platform revoke call to invalidate the access token.

Consuming events

Most Loop apps don’t subscribe to events directly — they query services on user actions. When they need push notifications:

  • In-app inbox: subscribe to comms.inbox.message.created.v1 through a server-sent-events endpoint (GET /v1/inbox/stream on services/comms).
  • Web push: register a subscription on services/comms; service pushes via the user’s browser push subscription.
  • Webhook: only if the app is also acting as an OAuth client for its own webhook deliveries (rare for first-party).

Brand context

If your Loop app serves multiple brands (e.g., it’s used by both loop.health and loopbio users), determine the brand from the subdomain or the user’s primary brand and pass it via:

  • Subdomain routingloop.healthbrand_id=loop is set during sign-in.
  • X-Brand-Id header — for explicit overrides (admin tools).

Don’t pass brand from user-controlled input. See Brands and multi-tenancy.

Deployment

Loop apps deploy via Vercel (~/Dev/loop/loop-platform), not via SST. Service deploys (SST) are separate from app deploys (Vercel). They share the platform SDKs as the contract.

What’s available to you

The SDKs cover every domain:

  • @platform/sdk-clinical — biomarkers, protocols, red flags
  • @platform/sdk-payments — charges, subscriptions, disputes
  • @platform/sdk-membership — tiers, win-back
  • @platform/sdk-comms — inbox, prefs, send
  • @platform/sdk-affiliates — referrals, commissions
  • @platform/sdk-content — peptides, stacks, goals
  • @platform/sdk-community — feed, posts
  • @platform/sdk-follows — social graph
  • @platform/sdk-analytics — track events
  • @platform/sdk-identity — connected apps, user profile
  • … and more — see SDK reference

Checklist for a Loop app

  • Clerk sign-in wired with the right publishable key per stage
  • Server-side token exchange for Loop access tokens
  • withAuth on every API route handler
  • Brand context resolved from subdomain or token, never user input
  • All platform calls via @platform/sdk-*
  • Errors normalized to LoopError
  • Rate-limit handling honors Retry-After
  • Convention check no-raw-platform-fetch passes
  • Vercel preview environment matches dev stage URLs