Authentication and OAuth
API keys, OAuth 2.1 with PKCE (SMART on FHIR standalone launch), OpenID Connect id_token, SMART v2 patient scopes, token lifetimes and the consent lifecycle.
Anpheros Platform accepts three kinds of credentials. Every request carries exactly one.
| Credential | Looks like | Who | Sees |
|---|---|---|---|
| API key | sk_test_… (sandbox) / sk_live_… (production) |
a project's server | the patients the project created (in sandbox, including its own copy of 30 synthetic patients) |
| OAuth access token | at_… |
an application acting for one patient under a consent | that patient only, within the granted SMART scopes |
| Firebase ID token | JWT | a developer in the dashboard, or a patient in "Connections" | their own organisations / their own records |
Send it as Authorization: Bearer <credential> (API keys also work as X-Api-Key).
API keys
- Created in the dashboard or via
POST /admin/projects/{id}/keys; shown once; stored as a SHA-256 hash. - Scopes:
read,write. A key never crosses its project's environment (sk_test_cannot reach production data and vice versa). - Per-key rate limit (default 600/min) is enforced platform-wide (shared store), with
Retry-Afteron 429. There is also a per-IP limit of 600/min. - Optional headers on writes:
X-Anpheros-Author-Type,X-Anpheros-Author-Ref,X-Anpheros-Source-System,X-Anpheros-Origin-Id,X-Anpheros-Acting-As(a Practitioner/PractitionerRole of your own directory),X-Anpheros-On-Behalf-Of(an Organization of your own directory). They populate Provenance.
OAuth 2.1 (SMART on FHIR, standalone launch)
Discovery: GET /.well-known/smart-configuration.
GET /oauth/authorize?response_type=code&client_id=…&redirect_uri=…&scope=…&state=…&code_challenge=…&code_challenge_method=S256[&purpose=treatment][&patient=<pairwise id>][&lang=ro]Shows the hosted consent page. PKCE S256 is mandatory; theredirect_urimust be registered exactly.- The patient signs in (Google or email via Firebase), chooses the record (own or a dependent's), the duration (30/90/180/365 days) and allows. The platform issues a one-time code (10 minutes).
POST /oauth/tokenwithgrant_type=authorization_code&code=…&code_verifier=…&redirect_uri=…&client_id=…(confidential clients addclient_secret). Response:access_token(30 min),refresh_token(30 days, rotating),patient(your pairwise id for the record),grant_id,scope.- Refresh:
grant_type=refresh_token. Each refresh token is single-use. Presenting a used refresh token revokes the whole chain (theft detection). A parallel duplicate of a refresh request is refused and may also revoke the chain, so never rotate concurrently. - Revoke:
POST /oauth/revoke(token + client_id). Revoking a refresh token also revokes its access tokens.
Token responses carry Cache-Control: no-store and Pragma: no-cache, as SMART requires.
OpenID Connect (openid fhirUser)
Ask for openid fhirUser alongside the patient scopes and the token response also contains an id_token: a JWT signed with RS256, with iss (the platform's base URL), sub (your pairwise id for the person), aud (your client_id), exp, iat and fhirUser — the absolute URL of the person's Patient resource, readable with the access token you just received. Discovery lives at GET /.well-known/openid-configuration; the public keys at GET /oauth/jwks (kid in the token header). The nonce parameter is not supported yet (it is optional in the authorization-code flow). launch/patient and offline_access are accepted; the patient field of the token response is the launch context.
Other grant types: client_credentials (confidential clients; behaves like an API key), and the first-party identity grant (trusted clients only).
Scopes
SMART v2 patient scopes: patient/<Type>.<cruds> with an optional ?category=<code> filter (the only filter the server enforces; any other filter is refused at authorize time). patient/*.rs reads everything; offline_access adds a refresh token. Patient demographics and the directory are always readable under a grant. A resource type outside the grant returns 403; a patient outside the grant returns 404.
Consent lifecycle
Consents expire automatically, can be revoked by the patient at any time (Connections screen / /me), and every read under a consent is logged with the application id. Webhooks consent.granted / consent.revoked notify the project.
Errors
401 unknown/expired credential · 403 the credential exists but may not do this · 404 the resource does not exist for you · 429 rate limited (Retry-After). See errors.md.