Scopes
A scope is a permission. Apps request scopes; users grant them. Every API call must carry an access token that includes the required scope, or the service returns 403 insufficient_scope.
Naming convention
<verb>:<resource> — verbs are read, write, manage (read + write), and admin (system-only, never granted to third parties).
OpenID Connect
| Scope | What it grants | Consent screen shows |
|---|---|---|
openid | OIDC discovery + an id_token | ”Sign in with Loop” |
profile | name, brand affiliation, locale | ”View your name and basic profile” |
email | verified email address | ”View your email address” |
Identity
| Scope | What it grants | Consent screen shows |
|---|---|---|
read:account | account metadata, created_at, status | ”View your Loop account info” |
manage:connected_apps | list and revoke OAuth grants | ”Manage which apps are connected to your Loop account” |
Clinical
| Scope | What it grants | Consent screen shows |
|---|---|---|
read:biomarkers | lab results: testosterone, lipid panel, inflammation, etc. | ”View your lab results” |
write:biomarkers | ingest a lab PDF or structured result | ”Upload lab results on your behalf” |
read:protocols | active and historical protocols and their actions | ”View your current and past protocols” |
write:protocols | start or stop a protocol | ”Start or stop protocols for you” |
read:check_ins | weekly subjective check-in data | ”View your weekly check-ins” |
write:check_ins | record a check-in | ”Record check-ins on your behalf” |
read:recommendations | personalized protocol recommendations | ”View protocol recommendations for you” |
read:red_flags | safety-flag history (clinician-grade) | “View health safety flags on your account” |
Patient graph
| Scope | What it grants | Consent screen shows |
|---|---|---|
read:profile | enriched customer profile + order history | ”View your full Loop profile and order history” |
read:wearables | wearable data: Oura, WHOOP, Libre, Dexcom syncs | ”View your wearable device data” |
write:wearables | trigger a wearable resync | ”Sync your wearables on your behalf” |
Payments + Commerce
| Scope | What it grants | Consent screen shows |
|---|---|---|
read:payments | payment history, refunds, disputes | ”View your payment history” |
read:subscriptions | active subscriptions and renewal dates | ”View your subscriptions” |
manage:subscriptions | pause, resume, skip, swap, cancel | ”Manage your subscriptions on your behalf” |
read:invoices | invoice list and PDFs | ”View your invoices” |
Memberships + Cash + Entitlements
| Scope | What it grants | Consent screen shows |
|---|---|---|
read:membership | tier, status, history | ”View your Loop membership tier” |
read:cash_balance | Loop Cash balance and ledger | ”View your Loop Cash balance” |
read:entitlements | feature access list | ”View what features you have access to” |
Affiliates (affiliate-only apps)
| Scope | What it grants | Consent screen shows |
|---|---|---|
read:affiliate_self | this affiliate’s own commissions, tier, payouts | ”View your affiliate commissions and payouts” |
read:affiliate_customers | customers attributed to this affiliate | ”View customers attributed to you” |
manage:affiliate_winback | trigger win-back outreach to lapsed referrals | ”Send win-back messages to your lapsed referrals” |
Communications
| Scope | What it grants | Consent screen shows |
|---|---|---|
read:inbox | in-app messages | ”View your Loop inbox” |
manage:inbox | mark read / archive | ”Manage your Loop inbox messages” |
read:preferences | comm channel preferences | ”View your notification preferences” |
manage:preferences | update comm preferences | ”Manage your notification preferences” |
write:messages | send a message on behalf of the user (rare; mostly internal) | “Send messages from your Loop account” |
Community + Follows
| Scope | What it grants | Consent screen shows |
|---|---|---|
read:community | feed, posts the user can see, comments | ”View your community feed” |
write:community | post, comment, like | ”Post and comment in community on your behalf” |
read:follows | follows + blocks | ”View who you follow” |
write:follows | follow / unfollow / block / unblock | ”Follow and unfollow people on your behalf” |
Content
| Scope | What it grants | Consent screen shows |
|---|---|---|
read:content | public peptide, stack, goal, research catalog | ”View Loop’s content library” |
read:research_papers | curated research bibliography | ”View Loop’s research library” |
Analytics (for the user’s own data)
| Scope | What it grants | Consent screen shows |
|---|---|---|
read:my_outcomes | the user’s own protocol outcomes + biomarker trends | ”View your protocol outcomes and trends” |
Admin scopes — never granted to third parties
These are returned in the client_credentials (M2M) flow only, when the calling service is a trusted internal client. Listed for completeness:
admin:identity,admin:clinical,admin:payments,admin:affiliates,admin:comms,admin:platform,admin:jobs, …
Third-party clients that request an admin:* scope have their authorization request rejected.
Scope groups (sugar for the consent screen)
Some bundles get a single human-friendly line on the consent screen:
| Bundle | Underlying scopes | Consent line |
|---|---|---|
loop.health.basic | openid profile email read:account | ”Identify you and view your basic profile” |
loop.health.full_clinical | read:biomarkers read:protocols read:recommendations read:red_flags read:check_ins | ”View all your clinical data” |
loop.health.subscription_self_service | read:subscriptions manage:subscriptions read:invoices | ”Manage your subscription” |
Bundles compose; you can request both individual scopes and bundles in the same scope parameter.
Scope evolution policy
- New scopes are additive. They don’t invalidate existing tokens.
- Renaming a scope ships the new name and keeps the old one as an alias for two release cycles, then removes it.
- Removing a scope ships a deprecation notice, an alarm if any active token still carries it, and a 90-day removal window.
- The full historical list is maintained in
docs/decisions/scope-changelog.md.
Using typed constants
Instead of string literals in your SDK calls, use the typed scope constants:
import { SCOPES } from "@platform/scopes";
// In your app's middleware
requireScope(SCOPES.READ_BIOMARKERS, SCOPES.ADMIN_CLINICAL);
// Checking scope satisfaction
import { satisfiesScope } from "@platform/scopes";
if (satisfiesScope(tokenScopes, SCOPES.READ_BIOMARKERS)) {
// user has access
}The SCOPES object has autocomplete-friendly keys like READ_BIOMARKERS, ADMIN_CLINICAL, MANAGE_SUBSCRIPTIONS, etc.