API Reference
All endpoints require a valid Bearer token. Roles are derived from the roles JWT claim.
Base URL: http://localhost:8082
Error format: RFC 7807 ProblemDetail
Response type: All endpoints return ConsentRecordView โ a safe DTO that excludes internal fields (version, fhirConsentId, organisationId).
Consent CRUDโ
Create consentโ
POST /api/consent
Role: CLINICIAN or ADMIN
Request body:
{
"patientId": "Patient/patient-123",
"actorReference": "Device/my-smart-app",
"provisionType": "permit",
"resourceClasses": ["Observation", "Patient"],
"scopeValues": ["patient/Observation.rs", "patient/Patient.rs"],
"periodStart": "2025-01-01",
"periodEnd": "2027-12-31",
"scopeContext": "patient",
"regulatoryBasis": "GDPR Art.9",
"organisationId": "Organization/example-hospital",
"note": "Verbal consent recorded"
}
Response: 201 Created โ a ConsentRecordView of the created record.
permittedOperations is derived automatically from scopeValues. patient/Observation.rs produces permittedOperations = "rs". SMART v1 format is also accepted: patient/Observation.read โ "rs", patient/Observation.write โ "cud".
Field notes:
patientIdโ FHIR Patient reference. Null foruser/orsystem/context consentsactorReferenceโ FHIR Device/Organization reference. Null = consent applies to all actorsprovisionTypeโ must bepermitordenyresourceClassesโ empty list = wildcard (all resource types)scopeValuesโ SMART scope strings; drivespermittedOperationsderivationscopeContextโ"patient"(default when patientId set),"user"(clinician-level),"system"(backend service). Derived automatically if omitted.
Get by internal IDโ
GET /api/consent/{id}
Role: CLINICIAN or ADMIN
Returns 404 if the record does not exist or the caller does not own it (prevents ID enumeration).
Get by FHIR Consent IDโ
GET /api/consent/fhir/{fhirId}
Role: CLINICIAN or ADMIN
Looks up by fhir_consent_id (the HAPI FHIR logical ID). Same ownership check as above.
List patient's consentsโ
GET /api/consent/patient/{patientId}?activeOnly=true|false
Role: CLINICIAN or ADMIN, or the patient themselves
Patients can call this for their own patient ID โ the @PreAuthorize expression accepts Patient/{subject} and {subject}.
Update consentโ
PUT /api/consent/{id}
Role: CLINICIAN or ADMIN
Partial update โ only non-null fields are applied:
{
"resourceClasses": ["Observation"],
"scopeValues": ["patient/Observation.r"],
"note": "Narrowed to read-only"
}
Re-derives permittedOperations if scopeValues is included.
Revoke consentโ
POST /api/consent/{id}/revoke?reason=<reason>
Role: CLINICIAN or ADMIN
Sets status = inactive. Record is preserved. The reason (max 256 characters) is appended to the note field.
Consent decisionโ
On-demand evaluationโ
POST /api/consent/evaluate
Role: SYSTEM or ADMIN
Query parameters:
| Parameter | Required | Description |
|---|---|---|
patientId | Yes | FHIR Patient reference |
actorReference | Yes | FHIR Device/Organization reference |
resourceType | Yes | FHIR resource type (e.g. Observation) |
fhirOperation | No | READ, SEARCH, CREATE, UPDATE, DELETE โ preferred |
httpMethod | No | GET, POST, PUT, PATCH, DELETE โ fallback if fhirOperation absent |
purposeOfUse | No | Default: TREATMENT |
Response:
{
"permitted": true,
"provisionType": "permit",
"consentRecordId": 42,
"reason": "provision.type=permit ยท Observation in provision.class ยท operation=READ in permitted_operations=rs ยท status=active ยท period valid",
"regulatoryBasis": "GDPR Art.9"
}
Restricted to ROLE_SYSTEM and ROLE_ADMIN. Every call is audit-logged for enumeration detection. Apply rate limiting at the reverse proxy / WAF layer.
Admin endpointsโ
Aggregate statsโ
GET /api/consent/admin/stats
Role: ADMIN
{
"activeCount": 1420,
"revokedCount": 83,
"totalDecisions": 48201
}
Consents by organisationโ
GET /api/consent/admin/org/{orgId}?page=0&size=50
Role: ADMIN
Paginated consent records for an organisation. Default page size 50, maximum 200 per page. Use ?page=N to paginate through large organisations.
Error responsesโ
All errors return RFC 7807 ProblemDetail:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.4",
"title": "Consent not found",
"status": 404,
"detail": "The requested consent record does not exist.",
"timestamp": "2025-06-01T14:32:00Z"
}
| Status | When |
|---|---|
| 404 | Record does not exist or caller lacks ownership |
| 400 | Validation failure โ detail lists which fields failed |
| 409 | Optimistic lock conflict โ two concurrent writes to the same record; retry |
| 403 | Insufficient role |
| 500 | Unexpected error โ detail in server log only, generic message returned |