Consent
Legal and communications agreements with FHIR Consent and versioned proof.
Palamond tracks a patient's legal and communications agreements (terms of service, privacy notice, SMS and voice outreach permissions) with the standard FHIR Consent resource, backed by a versioned DocumentReference that links to the agreement document the patient accepted. You read consents from the record plane; acceptance and withdrawal go through the agreements endpoints.
This is distinct from clinical acceptance of a treatment. When a patient accepts a proposed care plan, that goes through the enrollment endpoints, covered in Care Plans, not the Consent resource described here.
The model
Each agreement is a versioned DocumentReference, published write-once and pinned as url|version, scoped per organization and jurisdiction. The DocumentReference links to the agreement document (content.attachment.url) rather than embedding its text. When a patient accepts, the platform writes a Consent whose policy pins that exact document version, plus immutable proof of the acceptance.
| Resource | Role |
|---|---|
DocumentReference | A link to the agreement document, versioned and immutable (url|version). |
Consent | The patient's acceptance, pinned to a document version. |
Consent field | Purpose |
|---|---|
status | active when in force, inactive when withdrawn or opted out. |
category | Which agreement this is (terms, privacy, SMS, voice, or a state-specific agreement). |
patient | The patient. |
policy.uri | The pinned DocumentReference version accepted. |
dateTime | When it was accepted. |
The agreements endpoints
Consent is read-only on the record plane: acceptances are authored by the platform, never by clients, so an acceptance cannot be forged and its proof cannot be edited. Three endpoints cover the lifecycle:
GET /v1/agreements/requirementsresolves which required agreements are still unmet. Pass an optional context (healthcareService,carePlan,task, or aprotocolcanonical); it returnssatisfiedandunmet, one entry per outstanding agreement withurl,version,sourceUrl, andcategory. Already-accepted agreements never appear inunmet, so a patient who has signed some but not all sees exactly the remainder; the signed ones are theirConsentresources on the record plane.POST /v1/agreements/acceptancesrecords acceptances. The body is the list ofurl|versioncoordinates the patient accepted; the clickwrap proof (who, when, from what session) is stamped server-side. It is idempotent, so a retried call reuses the existing acceptance.POST /v1/agreements/withdrawalswithdraws an acceptance. Withdrawing a required agreement degrades or blocks service, so the platform refuses unless you also passacknowledgeConsequence: true, and it explains the consequence so your UI can too.
const req = await palamond.agreements.requirements({
healthcareService: serviceRef,
});
if (!req.satisfied) {
// Show each agreement (link to its sourceUrl) behind an explicit checkbox, then:
await palamond.agreements.accept(
req.unmet.map((a) => ({
agreementUrl: a.url,
agreementVersion: a.version,
})),
);
}Resolving what is required
The required set is the union of the organization's always-required agreements (or the platform baseline when the org has not set its own) and any agreements attached to the care plan or assessment the patient is entering. It is then filtered by the patient's jurisdiction (read from Patient.address.state). Ask the requirements endpoint rather than hardcoding the list, since it varies by org, plan, and state.
An organization can publish a region-specific version of an agreement (for example a California Terms of Service) alongside its all-regions default. When it does, the resolver returns the most specific version that matches the patient's state, superseding the default for that category, so a patient is asked to accept one Terms of Service, not two. A state can also require an extra agreement of its own; those carry the state-specific category.
Gating: the consent_required pattern
The recommended flow is the one above: check agreements.requirements for the context the patient is entering, record agreements.accept for anything unmet, and only then take the gated action.
Gating is also enforced server-side as the backstop. A workflow call that requires unmet agreements (starting an assessment, beginning a visit) answers 409 with code consent_required, and details.agreements carries the same list the requirements check returns:
import { getApiError } from '@palamond/sdk';
try {
await palamond.assessments.start({ healthcareService });
} catch (err) {
const api = getApiError(err);
if (api?.code === 'consent_required') {
// Collect acceptance of api.details.agreements, record them, then retry.
}
}Nothing is changed by the refused call, so retrying after acceptance is safe. See Errors and Limits.
const consents = await palamond.searchResources('Consent', {
status: 'active',
});Re-consent
Consent is not permanent. Two things trigger re-consent:
- A new version of an agreement. Because a
Consentpins a specific document version, publishing a newer version means the patient's prior acceptance no longer covers the current one. The requirements endpoint reports the new version as unmet; prompt the patient to accept it. - A jurisdiction change. If the patient's state changes, the required set can change too. Re-resolve requirements and capture any newly required agreements.
Withdrawal and opt-out flip the relevant Consent to inactive rather than deleting it, so the history of what was in force, and when, is preserved. An SMS opt-out (a STOP reply) flows in the same way: the platform flips the matching channel consent inactive.
The record of consents over time is the audit trail of what the patient agreed to. Read status: 'active' for what is in force now, and the full history for what was in force when.