AnpherosAnpheros PlatformDevelopers

Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no cardYour first API call in 2 minutes →

Create an OAuth app and the consent screen

Step 3: register an OAuth application, send the person to the consent screen with PKCE and SMART v2 scopes, exchange the code for a 30-minute token, refresh and revoke, with copyable JavaScript and Python.

Step 3 of 5. An API key is for your server and your own test data. When a real person uses your app, the person signs in with Anpheros, sees a consent screen with your app's name, what it asks for and for how long, and approves it. Your app then gets a token for that one person's record, limited to what they approved. This is OAuth 2.1 with PKCE, in the SMART on FHIR format.

In short:

Step Call Who
1. Register the app POST /admin/projects/{project_id}/applications you, once
2. Send the person to consent GET /oauth/authorize?… your app opens the browser
3. The person approves consent screen: scopes, purpose, 30/90/180/365 days the person
4. Exchange the code POST /oauth/token your app
5. Call the API Authorization: Bearer at_… your app
OAuth 2.1 with PKCE: the person approves, your app gets a token
Your appPerson (browser)Anpheros1. open /oauth/authorize + code_challenge2. sign in3. consent screen: scopes, purpose, days4. approve · 90 days5. redirect_uri?code=…&state=…6. POST /oauth/token + code_verifier7. at_… (30 min) + rt_… (30 days)8. GET /v1/… · Bearer at_…

1. Register your app

In the dashboard, or with the Admin API and your dashboard user token (a Firebase ID token):

curl -X POST https://platform.anpheros.com/admin/projects/proj_…/applications \
     -H "Authorization: Bearer $FIREBASE_ID_TOKEN" -H 'content-type: application/json' -d '{
  "name": "CardioNotes",
  "client_type": "public",
  "redirect_uris": ["com.cardionotes.app:/oauth/callback", "http://localhost:5173/callback"],
  "purpose": "Shows your blood pressure readings and lets you add new ones."
}'
{"id":"app_…","client_id":"client_…","client_type":"public","redirect_uris":["com.cardionotes.app:/oauth/callback","http://localhost:5173/callback"],"status":"active","…":"…"}

2. Send the person to the consent screen

Create a PKCE pair and a state, keep both, and open the authorize address in the browser:

// in the browser or in Node 18+
const b64url = buf => btoa(String.fromCharCode(...new Uint8Array(buf))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const verifier = b64url(crypto.getRandomValues(new Uint8Array(32)));          // 43 characters
const challenge = b64url(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier)));
const state = crypto.randomUUID();

const url = new URL('https://platform.anpheros.com/oauth/authorize');
url.search = new URLSearchParams({
  response_type: 'code',
  client_id: 'client_…',
  redirect_uri: 'http://localhost:5173/callback',
  scope: 'patient/Observation.rs patient/Observation.c offline_access',
  state,
  code_challenge: challenge,
  code_challenge_method: 'S256',
}).toString();
location.href = url;   // keep verifier and state until the person comes back
import base64, hashlib, secrets, urllib.parse

verifier = secrets.token_urlsafe(32)
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode()
state = secrets.token_urlsafe(16)
print("https://platform.anpheros.com/oauth/authorize?" + urllib.parse.urlencode({
    "response_type": "code", "client_id": "client_…", "redirect_uri": "http://localhost:5173/callback",
    "scope": "patient/Observation.rs patient/Observation.c offline_access",
    "state": state, "code_challenge": challenge, "code_challenge_method": "S256"}))

Scopes use the SMART v2 format patient/<Type>.<cruds>: r read, s search, c create, u update, d delete. Examples: patient/Observation.rs, patient/Condition.rs, patient/*.rs (read everything), patient/Observation.rs?category=laboratory (lab results only). Add offline_access for a refresh token and openid fhirUser for an id_token. An unknown scope is refused with 400, not ignored. Discovery document: https://platform.anpheros.com/.well-known/smart-configuration.

3. What the person sees

The consent screen shows your app's name, organization, logo and privacy link, the scopes in plain language, the purpose, whose record (their own or a family member's) and for how long: 30, 90, 180 or 365 days, 90 by default. In the sandbox the screen carries a "TEST · Sandbox" banner.

If the person approves, the browser goes to your redirect_uri with ?code=…&state=…. Check that state is the one you sent. The code is valid for 10 minutes. If they decline, you get ?error=access_denied&state=….

4. Exchange the code for a token

curl -X POST https://platform.anpheros.com/oauth/token \
     -d grant_type=authorization_code -d code="$CODE" -d code_verifier="$VERIFIER" \
     -d redirect_uri=http://localhost:5173/callback -d client_id=client_…
{"access_token":"at_…","token_type":"Bearer","expires_in":1800,"scope":"patient/Observation.rs patient/Observation.c offline_access","patient":"…","grant_id":"…","refresh_token":"rt_…"}

5. Call the API, refresh and revoke

curl "https://platform.anpheros.com/v1/patients/$PATIENT/observations?limit=10" -H "Authorization: Bearer $ACCESS_TOKEN"

# when the access token expires
curl -X POST https://platform.anpheros.com/oauth/token \
     -d grant_type=refresh_token -d refresh_token="$REFRESH_TOKEN" -d client_id=client_…

# when the person signs out of your app
curl -X POST https://platform.anpheros.com/oauth/revoke -d token="$REFRESH_TOKEN" -d client_id=client_…

Refresh tokens rotate: each refresh returns a new one, and reusing an old one revokes the whole chain (a sign that a token was stolen). The person can also revoke your app at any time from their Connections in Anpheros; your next call then gets 401 The consent behind this token is no longer active. Both SDKs implement this whole flow, including PKCE and refresh: see Authentication and OAuth.

Common errors

You get Fix
400 redirect_uri is not registered for this client. Register the exact redirect_uri, with the same scheme, host, port and path
400 on an unknown scope Check the spelling: patient/Observation.rs, not patient/Observation.read
invalid_grant Authorization code already used or expired. / PKCE verification failed. The code expired (10 minutes) or was already used, or the code_verifier is not the one behind the code_challenge
403 The consent behind this token does not allow 'write' on Observation. Ask for patient/Observation.c in the scope

Next step

Step 4: instead of asking every few minutes whether something changed, let the platform tell you: Receive your first webhook.

Frequently asked questions

Can a mobile app keep a client secret?

No. Register a mobile or browser app as public and use PKCE, which is required for every app. Only a backend should be confidential.

How long does the person's approval last?

As long as they chose on the consent screen: 30, 90, 180 or 365 days, 90 by default. They can withdraw it earlier at any time.

Do I need OAuth for my own test data?

No. For the sandbox and for data your own organization manages, use an API key on your server. OAuth is for acting on behalf of a person.

Ask your AI

Paste this into Claude, ChatGPT, Cursor or Claude Code. Your assistant reads this guide and adapts it to your code.

Read https://developers.anpheros.com/guides/oauth-app.md and help me register an OAuth app and run the consent flow with PKCE in my app. Use the Anpheros sandbox (base URL https://platform.anpheros.com) with my API key from the ANPHEROS_KEY environment variable, never written in the code. Give me complete, runnable code, the response I should expect, and how to fix the errors in https://developers.anpheros.com/guides/troubleshooting.md if a call fails.

Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no cardYour first API call in 2 minutes →

Product updates by email

New features, guides and the end of the beta. One email a month at most.

Send me occasional Anpheros Platform updates by email (new features, API changes, guides). I'll confirm by email and can unsubscribe in one click from any email. Privacy