Auth API¶
The OAuth 2.0 Authorization Code grant flow with PKCE is used to issue Access, Refresh, and ID tokens.
The Patient authorization code is generated by the server and then shared out-of-band as a secret link with the user.
OAuth is configured from the Django Admin page (See Getting Started above).
Endpoints and configuration details can be discovered from the OIDC metadata endpoint:
/o/.well-known/openid-configurationThe returned Access Token should be included in the
Authorizationheader for all API requests with the prefixBearer.Because the Patient authorization code is generated by the server, the PKCE code challenge and code verifier must be static values and set by the env vars (example below). The client then sends this
code_verifieralong with the authorization code to obtain tokens.
PATIENT_AUTHORIZATION_CODE_CHALLENGE = '-2FUJ5UCa7NK9hZWS0bc0W9uJ-Zr_-Pngd4on69oxpU'
PATIENT_AUTHORIZATION_CODE_VERIFIER = 'f28984eaebcf41d881223399fc8eab27eaa374a9a8134eb3a900a3b7c0e6feab5b427479f3284ebe9c15b698849b0de2'
Client POST
Content-Type: application/x-www-form-urlencoded
code=4AWKhgaaomTSf9PfwxN4ExnXjdSEqh&grant_type=authorization_code&redirect_uri=https%3A%2F%2Fexample.com%2Fauth%2Fcallback
&client_id=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
&code_verifier=f28984eaebcf41d881223399fc8eab27eaa374a9a8134eb3a900a3b7c0e6feab5b427479f3284ebe9c15b698849b0de2Admin REST API¶
The Admin API is used by the Web UI SPA for Practitioner/Patient/Organization/Study management and Patient data provider apps/clients to manage Patient consents.
Profile¶
The
profileendpoint returns the current user details.
// GET /api/v1/users/profile
{
"id": 10001,
"email": "peter@example.com",
"firstName": "Peter",
"lastName": "ThePatient",
"patient": {
"id": 40001,
...
}
}Patient Consents¶
The
consentsendpoint returns the studies that are pending and consented for the specified Patient. In this example, the Patient has been invited to Demo Study 2 and has already consented to sharing blood glucose data with Demo Study 1.
// GET /api/v1/patients/40001/consents
{
"patient": {
"id": 40001,
//...
},
"consolidatedConsentedScopes": [
{
"id": 50002,
"codingSystem": "https://w3id.org/openmhealth",
"codingCode": "omh:blood-pressure:4.0",
"text": "Blood pressure"
}
],
"studiesPendingConsent": [
{
"id": 30002,
"name": "Demo Study 2",
"organization": { ... }
"dataSources": [ ... ],
"pendingScopeConsents": [
{
"code": {
"id": 50002,
"codingSystem": "https://w3id.org/openmhealth",
"codingCode": "omh:blood-pressure:4.0",
"text": "Blood pressure"
},
"consented": null
}
]
}
],
"studies": [
{
"id": 30001,
"name": "Demo Study 1",
"organization": { ... },
"dataSources": [ ... ],
"scopeConsents": [
{
"code": {
"id": 50001,
"codingSystem": "https://w3id.org/openmhealth",
"codingCode": "omh:blood-glucose:4.0",
"text": "Blood glucose"
},
"consented": true
}
]
}
]
}To respond to requested consents, a POST is sent to the same
consentsendpoint with the scope and theconsentedboolean.
// POST /api/v1/patients/40001/consents
{
"studyScopeConsents": [
{
"studyId": 30002,
"scopeConsents": [
{
"codingSystem": "https://w3id.org/openmhealth",
"codingCode": "omh:blood-pressure:4.0",
"consented": true
}
]
}
]
}
A
PATCHrequest can be sent with the same payload to update an existing ConsentA
DELETErequest can be sen with the same payload excludingscopeConsents.consentedto delete the Consent
FHIR REST API¶
Patients¶
The
FHIR Patientendpoint returns a list of Patients as a FHIR Bundle for a given Study ID passed as query parameter_has:Group:member:_idor alternatively a single Patient matching the query parameteridentifier=<system>|<value>
| Query Parameter | Example | Description |
|---|---|---|
_has:Group:member:_id | 30001 | Filter by Patients that are in the Study with ID 30001 |
identifier | `http://ehr.example.com | abc123` |
// GET /fhir/r5/Patient?_has:Group:member:_id=30001
{
"resourceType": "Bundle",
"type": "searchset",
"entry": [
{
"resource": {
"resourceType": "Patient",
"id": "40001",
"meta": {
"lastUpdated": "2024-10-23T12:35:25.142027+00:00"
},
"identifier": [
{
"value": "fhir-1234",
"system": "http://ehr.example.com"
}
],
"name": [
{
"given": [
"Peter"
],
"family": "ThePatient"
}
],
"birthDate": "1980-01-01",
"telecom": [
{
"value": "peter@example.com",
"system": "email"
},
{
"value": "347-111-1111",
"system": "phone"
}
]
}
},
...Observations¶
The
FHIR Observationendpoint returns a list of Observations as a FHIR BundleAt least one of Study ID, passed as
patient._has:Group:member:_idor Patient ID, passed aspatientor Patient Identifier passed aspatient.identifier=<system>|<value>query parameters are requiredsubject.referencereferences a Patient IDdevice.referencereferences a Data Source IDvalueAttachmentis Base 64 Encoded Binary JSON
| Query Parameter | Example | Description |
|---|---|---|
patient._has:Group:member:_id | 30001 | Filter by Patients that are in the Study with ID 30001 |
patient | 40001 | Filter by single Patient with ID 40001 |
patient.identifier | `http://ehr.example.com | abc123` |
code | `https://w3id.org/openmhealth | omh:blood-pressure:4.0` |
// GET /fhir/r5/Observation?patient._has:Group:member:_id=30001&patient=40001&code=https://w3id.org/openmhealth|omh:blood-pressure:4.0
{
"resourceType": "Bundle",
"type": "searchset",
"entry": [
{
"resource": {
"resourceType": "Observation",
"id": "63416",
"meta": {
"lastUpdated": "2024-10-25T21:14:02.871132+00:00"
},
"identifier": [
{
"value": "6e3db887-4a20-3222-9998-2972af6fb091",
"system": "https://ehr.example.com"
}
],
"status": "final",
"subject": {
"reference": "Patient/40001"
},
"device": {
"reference": "Device/70001"
},
"code": {
"coding": [
{
"code": "omh:blood-pressure:4.0",
"system": "https://w3id.org/openmhealth"
}
]
},
"valueAttachment": {
"data": "eyJib2R5IjogeyJlZmZlY3RpdmVfdGltZV9mcmFtZSI6IHsiZGF0ZV90aW1lIjogIjIwMjQtMDUt\nMDJUMDc6MjE6MDAtMDc6MDAifSwgInN5c3RvbGljX2Jsb29kX3ByZXNzdXJlIjogeyJ1bml0Ijog\nIm1tSGciLCAidmFsdWUiOiAxMjJ9LCAiZGlhc3RvbGljX2Jsb29kX3ByZXNzdXJlIjogeyJ1bml0\nIjogIm1tSGciLCAidmFsdWUiOiA3N319LCAiaGVhZGVyIjogeyJ1dWlkIjogIjZlM2RiODg3LTRh\nMjAtMzIyMi05OTk4LTI5NzJhZjZmYjA5MSIsICJtb2RhbGl0eSI6ICJzZW5zZWQiLCAic2NoZW1h\nX2lkIjogeyJuYW1lIjogImJsb29kLXByZXNzdXJlIiwgInZlcnNpb24iOiAiMy4xIiwgIm5hbWVz\ncGFjZSI6ICJvbWgifSwgImNyZWF0aW9uX2RhdGVfdGltZSI6ICIyMDI0LTEwLTI1VDIxOjEzOjMx\nLjQzOFoiLCAiZXh0ZXJuYWxfZGF0YXNoZWV0cyI6IFt7ImRhdGFzaGVldF90eXBlIjogIm1hbnVm\nYWN0dXJlciIsICJkYXRhc2hlZXRfcmVmZXJlbmNlIjogImh0dHBzOi8vaWhlYWx0aGxhYnMuY29t\nL3Byb2R1Y3RzIn1dLCAic291cmNlX2RhdGFfcG9pbnRfaWQiOiAiZTZjMTliMDQyOGM4NWJiYjdj\nMTk4MGNiOTRkZDE3N2YiLCAic291cmNlX2NyZWF0aW9uX2RhdGVfdGltZSI6ICIyMDI0LTA1LTAy\nVDA3OjIxOjAwLTA3OjAwIn19",
"contentType": "application/json"
}
}
},
...Observations are uploaded as FHIR Batch bundles sent as a POST to the root endpoint
// POST /fhir/r5/
{
"resourceType": "Bundle",
"type": "batch",
"entry": [
{
"resource": {
"resourceType": "Observation",
"status": "final",
"code": {
"coding": [
{
"system": "https://w3id.org/openmhealth",
"code": "omh:blood-pressure:4.0"
}
]
},
"subject": {
"reference": "Patient/40001"
},
"device": {
"reference": "Device/70001"
},
"identifier": [
{
"value": "6e3db887-4a20-3222-9998-2972af6fb091",
"system": "https://ehr.example.com"
}
],
"valueAttachment": {
"contentType": "application/json",
"data": "eyJzeXN0b2xpY19ibG9vZF9wcmVzc3VyZSI6eyJ2YWx1ZSI6MTQyLCJ1bml0IjoibW1IZyJ9LCJkaWFzdG9saWNfYmxvb2RfcHJlc3N1cmUiOnsidmFsdWUiOjg5LCJ1bml0IjoibW1IZyJ9LCJlZmZlY3RpdmVfdGltZV9mcmFtZSI6eyJkYXRlX3RpbWUiOiIyMDIxLTAzLTE0VDA5OjI1OjAwLTA3OjAwIn19"
}
},
"request": {
"method": "POST",
"url": "Observation"
}
},
...