Palamond Docs
Building Patient-Facing Apps

Authentication

Sign patients in, keep sessions fresh, and register new patients.

Patient-facing apps authenticate a real person and then act as them. The client manages the OAuth session: it stores tokens, attaches them to every request, and refreshes them when they expire.

Email and password sign-in

Request an offline scope so you receive a refresh token and the session survives page reloads.

import { palamond } from './client';

async function signIn(email: string, password: string): Promise<void> {
  const login = await palamond.startLogin({ email, password, scope: 'openid offline' });
  await palamond.processCode(login.code!);
}

After processCode resolves, the session is active:

const patient = palamond.getProfile(); // the signed-in Patient

startLogin can return a next step instead of a final code (for example a project selection). Check the returned object and follow the step it indicates before calling processCode.

Sessions and tokens

The client stores tokens and adds the Authorization header to each request. You rarely touch them, but the accessors exist:

palamond.isAuthenticated(); // boolean
palamond.getAccessToken();  // current access token, or undefined

Tokens persist in storage, so a returning user is still signed in. Check isAuthenticated() on load and redirect to sign-in when it is false; the onUnauthenticated option handles a request failing mid-session.

Refreshing

Access tokens are short lived; with an offline scope the client renews them automatically. To refresh proactively on app start:

await palamond.refreshIfExpired(); // no-op while the token is valid

Signing out

await palamond.signOut();

Federated sign-in

For social or enterprise identity providers, use the redirect flow. Called with no code in the URL it navigates the user to the provider; called on your redirect route it exchanges the returned code for a session.

// 1. Begin the flow (navigates away).
await palamond.signInWithRedirect();

// 2. On your redirect route, the same call completes the sign-in.
const profile = await palamond.signInWithRedirect();

Register a new patient

patient.register is the whole sign-up flow in one call: it creates the account, signs it in, and homes the patient into their organization. When it resolves, the session is active exactly as after a sign-in, and the return value is the signed-in Patient.

const patient = await palamond.patient.register({
  firstName: 'Ada',
  lastName: 'Voss',
  email: '[email protected]',
  password,
  projectId: 'your-project-id',
});
  • projectId names the project the account is created in; your administrator provides it together with your client id. Account creation resolves the project from your client id, but the step that makes the account a Patient requires the project named explicitly.
  • The patient's organization comes from your app's client binding, so an app serving one clinic passes nothing extra. When the patient registers under a specific clinic's registration link, pass that link's clinicSlug instead:
await palamond.patient.register({
  firstName, lastName, email, password,
  projectId: 'your-project-id',
  clinicSlug: 'sunrise-dermatology',
});

A registration with an email that already has an account fails with the platform's message; send the user to sign-in instead. The names go on the account and the new Patient; the patient maintains the rest of their demographics themselves via the record plane.

After registration, the signed-in profile is a Patient scoped to one organization. See Organization Scoping for how that shapes every read.

Home an account that missed it

Homing is its own endpoint underneath (POST /v1/onboarding), and you can call it directly for an account that was created but never homed, for example when a registration was interrupted:

await palamond.patient.home({ clinicSlug: 'sunrise-dermatology' });

Pass client (your app's ClientApplication/<id> reference) instead of clinicSlug to home via the app's binding. The call is idempotent; a patient already homed elsewhere answers 409 conflict, which means this account belongs to another clinic, so show a "contact your clinic" path rather than retrying.

On this page