Quickstart
Goal: by the end of this page, your app has obtained a Loop access token on behalf of a test user and made a real authenticated call to the platform.
Prerequisites
- A redirect URI you control (e.g.
http://localhost:3000/callbackfor local dev, orhttps://your-app.example.com/auth/loop/callbackin production). - A Loop developer account at
developers.platform.loop.health.
1. Register your app
Sign in at developers.platform.loop.health and create an OAuth client:
| Field | Example |
|---|---|
| App name | ”My Lab Aggregator” |
| App type | Web app / Mobile app / Native |
| Redirect URIs | https://your-app.example.com/auth/loop/callback |
| Scopes requested | read:biomarkers, read:protocols |
| Webhook URL (optional) | https://your-app.example.com/webhooks/loop |
On submit you receive:
client_id— public, embed in your app.client_secret— confidential. Web apps only. Native and SPA apps skip this and use PKCE alone.
2. Build the authorization URL
Redirect the user to:
https://identity.platform.loop.health/v1/oauth/authorize
?response_type=code
&client_id=<your_client_id>
&redirect_uri=<your_redirect_uri>
&scope=read:biomarkers%20read:protocols
&state=<random_csrf_token>
&code_challenge=<base64url(sha256(verifier))>
&code_challenge_method=S256The user signs in (if not already) and sees the consent screen. Loop shows them:
- Your app’s name and logo.
- The exact scopes you’re asking for, in plain English.
- Allow / Deny buttons.
3. Handle the callback
When the user clicks Allow, Loop redirects to your redirect_uri with:
https://your-app.example.com/auth/loop/callback
?code=<authorization_code>
&state=<the_state_you_sent>Verify state matches what you sent. If not, abort — it’s CSRF.
4. Exchange the code for tokens
curl -X POST https://identity.platform.loop.health/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=<authorization_code>" \
-d "redirect_uri=<your_redirect_uri>" \
-d "client_id=<your_client_id>" \
-d "client_secret=<your_client_secret>" \
-d "code_verifier=<the_verifier_you_generated>"Response:
{
"access_token": "lph_at_...",
"refresh_token": "lph_rt_...",
"expires_in": 3600,
"token_type": "Bearer",
"scope": "read:biomarkers read:protocols",
"id_token": "eyJhbGciOi..."
}5. Make an authenticated call
curl https://clinical.platform.loop.health/v1/biomarkers/me \
-H "Authorization: Bearer lph_at_..."You receive only the data the user granted access to. PHI is returned with the safe-view convention.
6. Refresh when the access token expires
curl -X POST https://identity.platform.loop.health/v1/oauth/token \
-d "grant_type=refresh_token" \
-d "refresh_token=<your_refresh_token>" \
-d "client_id=<your_client_id>"You get a new access_token and (usually) a rotated refresh_token. Always save the new refresh token; the old one is invalidated.
Using the SDK
If you’re using @platform/sdk-* from inside the Loop monorepo or via GitHub Packages, the SDK handles tokens, refresh, and retry for you:
import { LoopClient } from "@platform/sdk";
const loop = new LoopClient({
clientId: process.env.LOOP_CLIENT_ID,
clientSecret: process.env.LOOP_CLIENT_SECRET,
redirectUri: "https://your-app.example.com/auth/loop/callback",
});
// Server-side, in your /callback handler:
const tokens = await loop.exchangeCode(req.query.code, req.query.state);
// Anywhere afterwards:
const biomarkers = await loop.clinical.listBiomarkers({ userId: tokens.userId });Language-specific quickstarts
For complete, copy-paste-ready examples in your language of choice:
- Node.js / Express — server-side web app with Express.
- Python / Flask — server-side web app with Flask.
- React Native — mobile app with Expo.
Next
- Authorization flow — the full state machine, including error paths.
- Scopes — choose the right scopes.
- Security — PKCE details, redirect URI rules, what to never log.
- Glossary — key terms and definitions.
- Migrating from M2M — moving from client_credentials to user-scoped tokens.