Skip to main content

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).

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 for user/ or system/ context consents
  • actorReference โ€” FHIR Device/Organization reference. Null = consent applies to all actors
  • provisionType โ€” must be permit or deny
  • resourceClasses โ€” empty list = wildcard (all resource types)
  • scopeValues โ€” SMART scope strings; drives permittedOperations derivation
  • scopeContext โ€” "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 /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}.


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.


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.


On-demand evaluationโ€‹

POST /api/consent/evaluate
Role: SYSTEM or ADMIN

Query parameters:

ParameterRequiredDescription
patientIdYesFHIR Patient reference
actorReferenceYesFHIR Device/Organization reference
resourceTypeYesFHIR resource type (e.g. Observation)
fhirOperationNoREAD, SEARCH, CREATE, UPDATE, DELETE โ€” preferred
httpMethodNoGET, POST, PUT, PATCH, DELETE โ€” fallback if fhirOperation absent
purposeOfUseNoDefault: 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"
}
warning

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"
}
StatusWhen
404Record does not exist or caller lacks ownership
400Validation failure โ€” detail lists which fields failed
409Optimistic lock conflict โ€” two concurrent writes to the same record; retry
403Insufficient role
500Unexpected error โ€” detail in server log only, generic message returned

Next: Regulatory Compliance โ†’