ConnectQuickstart

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/callback for local dev, or https://your-app.example.com/auth/loop/callback in 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:

FieldExample
App name”My Lab Aggregator”
App typeWeb app / Mobile app / Native
Redirect URIshttps://your-app.example.com/auth/loop/callback
Scopes requestedread: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=S256

The 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:

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.