The JHE FHIR server is served at FHIR/R5/ (the lowercase alias fhir/r5/ is also supported for backward compatibility).
Querying: JHE-native vs imported¶
Every search hits one store, selected by _source:
no
_source→ JHE-native records (the default)_source=https://jupyterhealth.org/fhir/fhir-source/90001→ one imported FHIR Source_source:below=https://jupyterhealth.org/fhir/fhir-source/→ all imported resources
GET /FHIR/R5/Observation?patient=40001 # JHE-native (default)
GET /FHIR/R5/Condition?_source=https://jupyterhealth.org/fhir/fhir-source/90001 # one source
GET /FHIR/R5/Condition?_source:below=https://jupyterhealth.org/fhir/fhir-source/ # all importedJHE-native records carry
meta.source: "https://jupyterhealth.org/jhe".Imported records carry
meta.source: "https://jupyterhealth.org/fhir/fhir-source/<id>".A single query never spans both stores.
Filtering, sorting & counting¶
Search parameters (US Core) — the set available depends on the resource type:
| Type | Examples |
|---|---|
| token | ?clinical-status=active · ?code=http://loinc.org|1234-5 · ?category=problem-list-item |
| date | ?recorded-date=ge2024-01-01 · ?date=le2024-12-31 (ge/le/gt/lt or a bare date) |
| string (starts-with) | ?name=nor matches “North Clinic” (case-insensitive) |
| reference | ?encounter=Encounter/abc · ?patient=40001 |
comma = OR:
?category=problem-list-item,encounter-diagnosisrepeat = AND / range:
?recorded-date=ge2024-01-01&recorded-date=le2024-12-31always available on any resource:
?_id=…·?_lastUpdated=ge2024-01-01
Sort with _sort (prefix - for descending):
GET /FHIR/R5/Observation?patient=40001&_sort=date # by effective[x], oldest first
GET /FHIR/R5/Condition?patient=40001&_sort=-_lastUpdated # most recently updated first_sort=date→ the resource’s date (Observationeffective[x], ConditionrecordedDate, MedicationRequestauthoredOn, …)_sort=_lastUpdated→ record update time
Count only with _summary=count:
// GET /FHIR/R5/Condition?patient=40001&_summary=count
{
"resourceType": "Bundle",
"type": "searchset",
"total": 12,
"entry": []
}Working with OMH / IEEE 1752 Observations¶
Observations coded to the Open mHealth (https://w3id.org/openmhealth) or IEEE 1752 (https://w3id.org/ieee1752) systems are the primary data type in JHE and are stored natively in the system. They are read and written as FHIR R5 resources. JHE treats the two coding systems interchangeably — IEEE 1752 schemas are Open mHealth schemas that have completed the IEEE ballot, so an Observation coded to either takes the same native path. An Observation coded to anything else is stored opaquely as an auxiliary resource instead.
Querying Observations¶
Patient-scoping parameters are optional — a paramless search returns everything you can access (a patient user’s own observations; a practitioner’s observations across all their organizations). When filtering by study (patient._has:Group:member:_id), results are additionally restricted to the study’s consented observation codes.
| Query Parameter | Example | Description |
|---|---|---|
patient._has:Group:member:_id | 30001 | Observations for patients enrolled in Study 30001 |
patient | 40001 | Observations for Patient 40001 |
patient.organization | 20001 | Observations for patients in Organization 20001 |
patient.identifier | http://ehr.example.com|abc123 | Observations for the patient with that identifier |
code | https://w3id.org/openmhealth|omh:blood-pressure:4.0 | Filter by observation type |
date | ge2024-01-01 | Filter by effective[x] time (ge/le/gt/lt) |
Notes:
subject.referencereferences a Patient IDdevice.referencereferences a Data Source (Device) IDvalueAttachment.datais Base64-encoded JSON
Examples:
# all blood-pressure observations for a patient
GET /FHIR/R5/Observation?patient=40001&code=https://w3id.org/openmhealth|omh:blood-pressure:4.0
# a study's observations, most recent effective time first
GET /FHIR/R5/Observation?patient._has:Group:member:_id=30001&_sort=-date
# newer than a given effective time (filters on effective[x] — see Effective Time below)
GET /FHIR/R5/Observation?patient=40001&date=gt2026-01-01
# an effective-time window (repeat = AND)
GET /FHIR/R5/Observation?patient=40001&date=ge2026-01-01&date=le2026-06-30
# two observation types at once (comma = OR)
GET /FHIR/R5/Observation?patient=40001&code=omh:blood-pressure:4.0,omh:heart-rate:2.0
# just the count
GET /FHIR/R5/Observation?patient=40001&_summary=count// GET /FHIR/R5/Observation?patient._has:Group:member:_id=30001&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": "eyJoZWFkZXIiOnsidXVpZCI6IjZlM2RiODg3LTRhMjAtMzIyMi05OTk4LTI5NzJhZjZmYjA5MSIsInNjaGVtYV9pZCI6eyJuYW1lc3BhY2UiOiJvbWgiLCJuYW1lIjoiYmxvb2QtcHJlc3N1cmUiLCJ2ZXJzaW9uIjoiNC4wIn0sInNvdXJjZV9jcmVhdGlvbl9kYXRlX3RpbWUiOiIyMDIxLTAzLTE0VDA5OjI1OjAwLTA3OjAwIn0sImJvZHkiOnsic3lzdG9saWNfYmxvb2RfcHJlc3N1cmUiOnsidmFsdWUiOjE0MiwidW5pdCI6Im1tSGcifSwiZGlhc3RvbGljX2Jsb29kX3ByZXNzdXJlIjp7InZhbHVlIjo4OSwidW5pdCI6Im1tSGcifSwiZWZmZWN0aXZlX3RpbWVfZnJhbWUiOnsiZGF0ZV90aW1lIjoiMjAyMS0wMy0xNFQwOToyNTowMC0wNzowMCJ9fX0=",
"contentType": "application/json"
}
}
},
...Uploading Observations¶
Observations are uploaded as FHIR Batch Bundles via POST to the base 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": "eyJoZWFkZXIiOnsidXVpZCI6IjZlM2RiODg3LTRhMjAtMzIyMi05OTk4LTI5NzJhZjZmYjA5MSIsInNjaGVtYV9pZCI6eyJuYW1lc3BhY2UiOiJvbWgiLCJuYW1lIjoiYmxvb2QtcHJlc3N1cmUiLCJ2ZXJzaW9uIjoiNC4wIn0sInNvdXJjZV9jcmVhdGlvbl9kYXRlX3RpbWUiOiIyMDIxLTAzLTE0VDA5OjI1OjAwLTA3OjAwIn0sImJvZHkiOnsic3lzdG9saWNfYmxvb2RfcHJlc3N1cmUiOnsidmFsdWUiOjE0MiwidW5pdCI6Im1tSGcifSwiZGlhc3RvbGljX2Jsb29kX3ByZXNzdXJlIjp7InZhbHVlIjo4OSwidW5pdCI6Im1tSGcifSwiZWZmZWN0aXZlX3RpbWVfZnJhbWUiOnsiZGF0ZV90aW1lIjoiMjAyMS0wMy0xNFQwOToyNTowMC0wNzowMCJ9fX0="
}
},
"request": {
"method": "POST",
"url": "Observation"
}
},
...Effective Time¶
On upload, JHE lifts the OMH body.effective_time_frame out of the Base64 payload and onto indexed columns so observations can be queried by time. These columns are surfaced on read as FHIR R5 Observation.effective[x]. The valueAttachment.data blob remains the source of truth; the elevated values are a derived projection, so a missing or unrecognised time frame simply leaves the row without an effective[x] value.
A single point in time becomes effectiveDateTime; every OMH time_interval form becomes an effectivePeriod (start + end):
OMH effective_time_frame shape | Example | FHIR R5 output |
|---|---|---|
date_time | { "date_time": "2026-07-18T09:30:00+09:00" } | effectiveDateTime: "2026-07-18T09:30:00+09:00" |
time_interval — start + end | { "start_date_time": "…T09:00…", "end_date_time": "…T10:00…" } | effectivePeriod: { start, end } |
time_interval — start + duration | { "start_date_time": "…T09:00…", "duration": { "value": 60, "unit": "min" } } | effectivePeriod: { start, end } (end = start + duration) |
time_interval — end + duration | { "end_date_time": "…T10:00…", "duration": { "value": 60, "unit": "min" } } | effectivePeriod: { start, end } (start = end − duration) |
time_interval — date + part_of_day | { "date": "2026-07-18", "part_of_day": "morning" } | effectivePeriod: { start, end } (the part-of-day window) |
Notes:
Timezone: a datetime with no offset is interpreted as UTC, matching the legacy OMH convention.
Duration units are drawn from the OMH duration value set (
ps, ns, us, ms, sec, min, h, d, wk, Mo, yr); calendar units (Mo,yr) are added relative to the anchor date (e.g. Jan 31 + 1 month → Feb 28).part_of_daymaps to a fixed window on the given day:morning06:00–12:00,afternoon12:00–18:00,evening18:00–24:00,night00:00–06:00.After a round-trip through storage, datetimes are returned in UTC (e.g.
+00:00); this is the same instant, and the original offset is retained invalueAttachment.data.
Working with Patients, Practitioners, Organizations, Groups (Studies) and Devices (Data Sources)¶
These resources are read-only projections of JHE system entities — writes to them via FHIR are not supported (use the /api/v1/ REST API to manage these). All support the same patient-scoping query parameters.
| Query Parameter | Example | Description |
|---|---|---|
_has:Group:member:_id | 30001 | Resources belonging to patients in Study 30001 |
patient | 40001 | Resources belonging to Patient 40001 |
patient.organization | 20001 | Resources belonging to patients in Organization 20001 |
patient._has:Group:member:_id | 30001 | Resources belonging to patients enrolled in Study 30001 |
patient.identifier | http://ehr.example.com|abc123 | Resources belonging to the patient with that identifier |
identifier | http://ehr.example.com|abc123 | Match the resource itself by identifier (Patient only) |
family / given / name | Smith | Patient name starts-with, case-insensitive (Patient only) |
birthdate | ge1980-01-01 | Patient date of birth (Patient only) |
// 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"
}
]
}
},
...The same scoping parameters apply to Group (Study), Device (Data Source), Organization, and Practitioner:
| Resource | Query | Returns |
|---|---|---|
Group (Study) | GET /FHIR/R5/Group?patient._has:Group:member:_id=30001 | The study with ID 30001 |
Group (Study) | GET /FHIR/R5/Group?patient=40001 | Studies Patient 40001 is enrolled in |
Group (Study) | GET /FHIR/R5/Group?patient.organization=20001 | Studies in Organization 20001 |
Device (Data Source) | GET /FHIR/R5/Device?patient._has:Group:member:_id=30001 | Data sources used in Study 30001 |
Device (Data Source) | GET /FHIR/R5/Device?patient=40001 | Data sources used in Patient 40001’s studies |
Device (Data Source) | GET /FHIR/R5/Device?patient.organization=20001 | Data sources in studies under Organization 20001 |
Organization | GET /FHIR/R5/Organization?patient._has:Group:member:_id=30001 | The organization backing Study 30001 |
Organization | GET /FHIR/R5/Organization?patient=40001 | Organizations Patient 40001 belongs to |
Organization | GET /FHIR/R5/Organization?patient.organization=20001 | Organization 20001 |
Practitioner | GET /FHIR/R5/Practitioner?patient._has:Group:member:_id=30001 | Practitioners in Study 30001’s organization |
Practitioner | GET /FHIR/R5/Practitioner?patient=40001 | Practitioners in Patient 40001’s organizations |
Practitioner | GET /FHIR/R5/Practitioner?patient.organization=20001 | Practitioners in Organization 20001 |
Working with other resources¶
Any FHIR resource type not natively modeled in JHE (such as a non OMH/IEEE 1752 Observation, QuestionnaireResponse, Condition, or MedicationRequest) can be stored and retrieved via the auxiliary resource store. Before uploading, a patient must register a FHIR Source that identifies the upstream system the data originates from.
Step 1: Register a FHIR Source¶
POST /api/v1/fhir_sources
Authorization: Bearer WMSk9Te6QpENVxudxkRjeNBFlCf5pq
Content-Type: application/json
Accept: application/json{
"data_source": 70005,
"label": "Neptune MyChart"
}Response:
{
"id": 90001,
"patient": 40001,
"dataSource": 70005,
"label": "Neptune MyChart",
"lastUpdated": "2026-06-07T04:11:52.577605Z"
}The id returned (90001) is the FHIR Source ID used in subsequent requests.
Step 2: Upload the resource¶
Name the FHIR Source one of two ways (omit both → 400):
Preferred — set
meta.sourceon the resource body:"meta": { "source": "https://jupyterhealth.org/fhir/fhir-source/90001" }Or — send the
X-JHE-FHIR-Source-IDheader (a convenience fallback; wins if both are present):X-JHE-FHIR-Source-ID: 90001
POST /FHIR/R5/QuestionnaireResponse
Authorization: Bearer WMSk9Te6QpENVxudxkRjeNBFlCf5pq
Content-Type: application/json
Accept: application/json
X-JHE-FHIR-Source-ID: 90001{
"resourceType": "QuestionnaireResponse",
"status": "completed",
"questionnaire": "Questionnaire/weekly-symptom-severity-vas",
"subject": {
"reference": "Patient/123"
},
"authored": "2026-05-28T14:30:00Z",
"item": [
{
"linkId": "cough-severity",
"answer": [{"valueInteger": 37}]
},
{
"linkId": "dyspnea-severity",
"answer": [{"valueInteger": 62}]
},
{
"linkId": "fatigue-severity",
"answer": [{"valueInteger": 48}]
}
]
}Response:
{
"resourceType": "QuestionnaireResponse",
"id": "2d50b34d-b33c-4f47-a5e8-595764d51f53",
"status": "completed",
"questionnaire": "Questionnaire/weekly-symptom-severity-vas",
"subject": {
"reference": "Patient/123"
},
"authored": "2026-05-28T14:30:00Z",
"item": [
{
"linkId": "cough-severity",
"answer": [{"valueInteger": 37}]
},
{
"linkId": "dyspnea-severity",
"answer": [{"valueInteger": 62}]
},
{
"linkId": "fatigue-severity",
"answer": [{"valueInteger": 48}]
}
]
}The id returned is a UUID assigned by JHE.
Step 3: Retrieve the resource¶
Retrieve by FHIR Source, by patient/study, or by resource ID. (The X-JHE-FHIR-Source-ID header is write-only — it is ignored on reads; select a source with _source instead.)
From one FHIR Source:
GET /FHIR/R5/QuestionnaireResponse?_source=https://jupyterhealth.org/fhir/fhir-source/90001From all imported sources:
GET /FHIR/R5/QuestionnaireResponse?_source:below=https://jupyterhealth.org/fhir/fhir-source/By patient or study (add any filter/sort from Filtering, sorting & counting):
GET /FHIR/R5/QuestionnaireResponse?patient=40001
GET /FHIR/R5/QuestionnaireResponse?patient._has:Group:member:_id=30001&status=completed&_sort=-dateBy resource ID:
GET /FHIR/R5/QuestionnaireResponse/2d50b34d-b33c-4f47-a5e8-595764d51f53All reads return a FHIR searchset Bundle (or a single resource for an ID lookup).
Permissions¶
Reads and writes are authorized differently, and a patient and a practitioner are held to different rules. This applies the same way to OMH/IEEE 1752 Observations and to auxiliary resources — there is one write guard, not two.
Reads¶
A read has no role or permission requirement — only organization membership:
A patient sees only their own data.
A practitioner sees data belonging to any patient who shares an organization with them, across all of their organizations at once.
Writes (create / update / delete)¶
A write additionally requires the actor to be authorized against the specific patient the data belongs to:
A patient may write only their own data (the
subject/FhirSource.patientmust be themselves). For an OMH/IEEE Observation, the code must also be one the patient has consented to share — i.e. requested by a study they are enrolled in and actively consented. Auxiliary resources carry no consent concept; ownership via a registered FHIR Source is the only check.A practitioner may write a patient’s data if they:
share an organization with the patient, and
hold the
patient.manage_datapermission in that organization (see Role required, below).
Consent plays no part in a practitioner-authored write. Consent is a patient’s own control over who sees their data, not a constraint on a practitioner who is otherwise authorized to manage it — a practitioner with organization membership and the right role/permission can create, update, or delete an OMH/IEEE Observation of any code, or any auxiliary resource, regardless of what the patient has or hasn’t consented to.
Role required¶
Membership alone is not enough for a practitioner write — their role in the shared organization must carry patient.manage_data:
| Role | patient.manage_data |
|---|---|
super_user | ✅ |
manager | ✅ |
member | ✅ |
viewer | ❌ |
A practitioner who only shares an organization with the patient but holds the viewer role — or no role at all — is refused with a 403.