Secondary Use FHIR Server Implementation Guide
0.1.0 - ci-build
Secondary Use FHIR Server Implementation Guide - Local Development build (v0.1.0) built by the FHIR (HL7® FHIR® Standard) Build Tools. See the Directory of published versions
Design proposal, not implemented runtime behaviour. This page describes a proposed national integration profile. Its data-permit, identity, SPE delivery and national pseudonymisation requirements are not implemented by this prototype. For executable hospital-pilot configuration and implemented limits, see the repository's
docs/HOSPITAL.md. Normative words below apply to the proposal, not a claim of product conformance.
This Bulk Export IG defines how a data user (for example a researcher or a public health body) obtains health data from one or more data holders (health care providers) for secondary use, without those data holders having to build a bespoke extraction pipeline per research project. It covers the path from a feasibility count on a not-yet-known cohort, through a permitted export, to the delivery of de-identified NDJSON files inside a Secure Processing Environment (SPE).
Use this guide when all of the following apply:
The following preconditions SHALL be met before any transaction on this page is executed:
Endpoint for its Bulk Export Server. For the discovery of that endpoint, GF Addressing MAY be used.This guide does not define the legal framework (data permits, data use agreements, ethical review), the cohort science, or the analysis performed inside the SPE. It defines the technical interface between data user and data holder.
GF Bulk Export is based on the HL7 FHIR Bulk Data Access (Flat FHIR) IG, STU3. All exchanges are the standard asynchronous request pattern: a kick-off request that returns 202 Accepted with a Content-Location, a polling location that returns a JSON manifest when the job is complete, and NDJSON output files referenced from that manifest. This guide adds national choices; it does not redefine the operation.
Two elements are layered on top of the Bulk Data IG:
_type and _typeFilter are bounded by the EHDS priority data categories for primary use and their European FHIR profiles (HL7 Europe Base and Core, HL7 Europe Laboratory Report, HL7 Europe Imaging Report). Where a resource type is covered by an IHE QEDm named option, its PCC-44 search parameter combinations are reused, so a data holder that already implements QEDm for primary use needs no second query surface.The data user first establishes whether a cohort is large enough to be worth a permit (feasibility), and only then requests the data itself. The research-subjects group is the shared handle between these two phases: it is created once, by criteria, and reused by every subsequent export.
Endpoint in the HDAB catalogue, defines the characteristics of the research subjects (the patient cohort) and the characteristics of the data needed, and holds a data permit for the feasibility phase.Group at each data holder that carries the cohort criteria and no members.Group.quantity. No member identities are returned in this phase.Group/{id}/$export with _type and _typeFilter, polls the status location, and retrieves the manifest.sequenceDiagram
participant du as Data User<br/>(Bulk Export Client)
participant hdab as HDAB<br/>(catalogue + permits)
participant dh as Data Holder<br/>(Bulk Export Server)
participant prs as Pseudonymization<br/>Service (PRS)
participant spe as Secure Processing<br/>Environment (SPE)
Note over du,hdab: Precondition
du->>hdab: Find data holder + Endpoint,<br/>request feasibility permit
hdab-->>du: Endpoint(s) + feasibility permit
Note over du,dh: Cohort definition and feasibility
du->>dh: POST [base]/Group<br/>(member-filter criteria, no members)
dh-->>du: 201 Created — Group/{id}
du->>dh: PUT [base]/Group/{id} (status=active)
activate dh
dh->>dh: Resolve criteria, apply EHDS<br/>secondary-use opt-out, add members
dh-->>du: 200 OK
deactivate dh
du->>dh: GET [base]/Group/{id}?_elements=quantity
dh-->>du: Group.quantity (count only)
du->>hdab: Feasibility result + data request
hdab-->>du: Data permit for export (names the SPE)
Note over du,spe: Export the permitted resource types
loop for each resource type set in the permit
du->>dh: GET [base]/Group/{id}/$export<br/>?_type=...&_typeFilter=...<br/>Prefer: respond-async
dh-->>du: 202 Accepted + Content-Location
dh->>prs: Evaluate blinded identifiers
prs-->>dh: Recipient-scoped pseudonyms
du->>dh: GET [content-location]
dh-->>du: 202 Accepted, X-Progress, Retry-After
du->>dh: GET [content-location]
dh-->>du: 200 OK — manifest (output[].url)
du->>dh: GET [manifest output url]
dh-->>spe: application/fhir+ndjson
end
Note over du,spe: Analysis
du->>spe: Log in with permit-bound credentials
spe-->>du: Analysis results only
The EHDS Regulation uses two different notions of "category", and this guide keeps them apart:
A data permit therefore states both: the Article 51 category as its legal scope, and the FHIR resource types with their _typeFilter criteria as its technical scope. Only the latter appears in a request.
A data holder claiming conformance to this guide SHALL support export of the resource types for at least one priority data category, and SHALL declare the supported types and the search parameters usable in _typeFilter in its CapabilityStatement, as required by the Bulk Data IG.
| Priority data category (Annex I) | FHIR resource types | Profiles | Search parameters usable in _typeFilter |
|---|---|---|---|
| Patient summary (EPS) | Patient, Condition, AllergyIntolerance, MedicationStatement, Immunization, Procedure |
HL7 EU Core | Patient: gender, birthdate, address-countryCondition: category, code, clinical-status, onset-date, recorded-dateAllergyIntolerance: code, clinical-status, dateMedicationStatement: code, status, effectiveImmunization: vaccine-code, status, dateProcedure: code, status, date |
| ePrescriptions and eDispensations | MedicationRequest, MedicationDispense, Medication |
HL7 EU Core | MedicationRequest: code, status, intent, authoredonMedicationDispense: code, status, whenhandedoverMedication: code (no patient context; exported as a referenced resource) |
| Medical test results (laboratory) | Observation, DiagnosticReport, Specimen, ServiceRequest |
HL7 EU Laboratory Report | Observation: category, code, status, date, value-quantityDiagnosticReport: category, code, status, dateSpecimen: type, collectedServiceRequest: code, status, authored |
| Medical imaging studies and reports | ImagingStudy, ImagingSelection, DiagnosticReport, DocumentReference, Procedure, ServiceRequest |
HL7 EU Imaging Report | ImagingStudy: modality, bodysite, started, statusDiagnosticReport: category, code, status, dateDocumentReference: type, category, date |
| Discharge reports and encounter context | Encounter, Composition, DocumentReference |
HL7 EU Core | Encounter: class, type, status, dateComposition: type, category, status, dateDocumentReference: type, category, date |
| Provenance (all of the above) | Provenance |
IHE mXDE / QEDm Provenance Option | target, recorded |
National choices on this scope:
patient + category [+ code] [+ date] combinations from QEDm [PCC-44] are the baseline for _typeFilter expressions on Observation, DiagnosticReport, Condition, Procedure and Encounter. The patient dimension comes from the group, so a _typeFilter expression SHALL NOT repeat a patient or subject criterion.Provenance travels with the data. Unless the permit excludes it, Provenance resources whose target is an exported resource SHALL be included, so the data user can attribute a data element to its source document (including translation/transcoding).The Bulk Export Client acts on behalf of the data user. It:
Prefer: respond-async on kick-off and SHALL poll the Content-Location using exponential backoff, honouring Retry-After;_typeFilter expression with an OperationOutcome, and SHALL NOT retry the same expression;Accept-Encoding: gzip when retrieving output files.The Bulk Export Server is operated by, or on behalf of, the data holder. It:
POST [base]/Group, the Group read, and GET [base]/Group/{id}/$export per the Bulk Data IG;CapabilityStatement.rest.resource and the _typeFilter-usable search parameters in rest.resource.searchParam;_typeFilter expressions with a 4XX and a FHIR OperationOutcome, rather than silently ignoring them;AuditEvent for each kick-off, status and output-file request;DELETE [content-location] so a client can cancel a job or signal that retrieval is finished.The national Pseudonymization Service produces pseudonyms from patient identifiers using HKDF and an OPRF, as specified in GF Pseudonymisation.
The SPE is the environment designated in the data permit in which exported data may be processed.
All transactions are secured as described in Security. [base] is the Bulk Export Server base URL obtained from the HDAB catalogue.
The client creates a Group that declares the Bulk Cohort Group profile. Each criterion is one member-filter modifier extension holding a single resource-scoped FHIR query. Criteria within one expression are ANDed; separate member-filter extensions are intersected.
POST [base]/Group HTTP/1.1
Content-Type: application/fhir+json
Authorization: Bearer <access-token-bound-to-data-permit>
{
"resourceType": "Group",
"meta": { "profile": ["http://hl7.org/fhir/uv/bulkdata/StructureDefinition/bulk-cohort-group"] },
"modifierExtension": [
{
"url": "http://hl7.org/fhir/uv/bulkdata/StructureDefinition/member-filter",
"valueExpression": {
"language": "application/x-fhir-query",
"expression": "Condition?code=http://snomed.info/sct|34000006&clinical-status=active"
}
},
{
"url": "http://hl7.org/fhir/uv/bulkdata/StructureDefinition/member-filter",
"valueExpression": {
"language": "application/x-fhir-query",
"expression": "Patient?birthdate=ge1980-01-01"
}
}
],
"identifier": [
{ "system": "http://hdab.eu/NamingSystem/hdab-data-permit", "value": "PERMIT-2026-0142" }
],
"type": "person",
"actual": false,
"status": "draft",
"name": "IBD cohort, permit PERMIT-2026-0142"
}
After Group creation by the Bulk Export client, the Bulk Export Server starts (asynchronously) to turn group criteria into group members. This process is called 'group activation'.
On activation the server SHALL evaluate the member-filter expressions against its own population, SHALL remove every subject with a registered EHDS secondary-use opt-out, and SHALL populate Group.member, Group.quantity, Group.members-refreshed and set Group.actual to true.
Group membership is a snapshot. Refreshing the members of an existing group re-evaluates the criteria, including the opt-out register, and MAY change quantity. Bulk Export clients MAY trigger a member refresh by updating a Group and setting actual to false.
Activation may take some time; clients SHALL use exponential backoff and honour Retry-After. Under a feasibility-only permit the client SHALL use _elements to request every root element of Group except member and contained. That yields the cohort size (quantity), whether the group is activated (actual) and when its membership was last computed (the members-refreshed extension).
GET [base]/Group/{id}?_elements=id,meta,implicitRules,language,text,extension,modifierExtension,identifier,active,type,actual,code,name,quantity,managingEntity,characteristic HTTP/1.1
Accept: application/fhir+json
members-refreshed is an extension rather than a root element of Group, so it is requested through extension. The server marks the reduced resource with the SUBSETTED tag in meta.tag.
The response SHALL NOT carry Group.member; under a feasibility-only permit a read that requests member detail SHALL be refused with 403 Forbidden and an OperationOutcome. If the permit sets a cohort threshold and Group.quantity is below it, the server SHALL NOT return Group.quantity. This limits re-identification through repeated narrow queries.
One kick-off per FHIR resource type. _type names the resource type; _typeFilter narrows it.
GET [base]/Group/{id}/$export?
_type=Observation
&_typeFilter=Observation%3Fcategory%3Dlaboratory%26date%3Dge2024-01-01 HTTP/1.1
Accept: application/fhir+json
Prefer: respond-async
Authorization: Bearer <access-token-bound-to-data-permit>
Rules for this guide:
_type SHALL be supplied. An omitted _type would export everything the token authorises, which is never what a data permit grants._typeFilter expressions for the same resource type are ORed, as in the Bulk Data IG. A _typeFilter expression does not select patients — the group does._typeFilter SHALL NOT contain search result parameters (_sort, _include, _elements, _summary), and SHALL have the search context of a single resource type._elements SHOULD be used to request only the fields the research question needs. Output resources that were reduced carry the SUBSETTED tag in meta.tag._since and _until filter on meta.lastUpdated, which is a record-keeping date. Clinical time windows SHALL be expressed with _typeFilter on the resource's own date parameter. Both parameters are available only when the data permit disables date shifting, see Default date-shift policy.The response is 202 Accepted with an absolute Content-Location.
GET [content-location] HTTP/1.1
Accept: application/json
While the job runs the server returns 202 Accepted, optionally with X-Progress and Retry-After. On completion it returns 200 OK, an Expires header, and the output manifest:
{
"transactionTime": "2026-03-04T09:00:00Z",
"request": "https://fhir.dataholder.nl/Group/{id}/$export?_type=Observation",
"requiresAccessToken": true,
"output": [
{ "type": "Observation", "url": "https://spe.example.nl/permit-2026-0142/observation_1.ndjson", "count": 184233 }
],
"error": []
}
A partial success is reported as 200 OK with one or more OperationOutcome NDJSON files in error. A failed job is reported as 5XX with an OperationOutcome.
Organisation- and system-level authentication follows GF Authentication: OAuth 2.0 with the FAPI 2.0 security profile, private_key_jwt client authentication, sender-constrained access tokens, and mTLS transport. Because secondary use is attributable to a named researcher and not only to an organisation, the Bulk Export Client SHALL additionally establish the identity of that natural person through the VAD (Vertrouwde AuthenticatieDienst) over OpenID Connect, in the pattern implemented by the MGO DVP Proxy. That subject identity SHALL be carried into the token request and recorded in the AuditEvent of every transaction on this page.
Authorization for bulk export is not based on a treatment relationship or on patient consent — neither exists here. The HDAB data permit is the authorisation artefact; see Assumed data permit contents for what this guide expects it to carry.
authorization_details object, per GF Authorization) and in Group.identifier. A server SHALL refuse an $export whose group was created under a different permit.quantity read only. An $export presented under a feasibility permit SHALL be refused with 403 Forbidden._typeFilter criteria outside the ones the permit lists, rather than silently trimming the result set.requiresAccessToken SHOULD be true; output file URLs are not capability URLs in this ecosystem.This guide does not define data permits; that is the HDAB's domain. It does assume that the items below are derivable from a permit, because each of them drives a transaction or a control on this page. A permit that leaves an item open forces the data holder to fall back on the most restrictive interpretation.
| Element | Assumed content | Where it is used |
|---|---|---|
| Permit identifier | A single identifier for the permit, in a naming system such as http://hdab.eu/NamingSystem/hdab-data-permit |
Carried in Group.identifier and in the access token; binds a cohort to the permit it was resolved under |
| Phase | Feasibility, or export | A feasibility permit allows group create, update and the count read; only an export permit allows $export |
| Purpose of use | The secondary-use purpose the HDAB granted | Carried in the authorization_details object, per GF Authorization |
| Data user organisation | The organisation whose Bulk Export Client connects | The private_key_jwt client identity, per GF Authentication |
| Named researcher(s) | The natural persons who may act under the permit | Established through the VAD and recorded in the AuditEvent of every transaction |
| Data holder(s) | The data holders the permit covers | The client creates one Group per listed data holder |
| Cohort criteria | The permitted research-subject characteristics | Expressed as member-filter expressions; the server refuses criteria outside them |
| Minimum cohort size | The disclosure threshold below which a cohort is too small to release | The server withholds Group.quantity below it and refuses the corresponding export |
| EHDS data categories | The Article 51 secondary-use categories the permit covers, typically electronic health data from EHRs (Article 51(1)(a)) | Legal scope of the permit; it is not requestable and does not by itself determine _type |
| FHIR resource types, filters and element constraints | The resource types, the _typeFilter criteria per type, and the elements the permit allows |
Bounds _type, _typeFilter and _elements; the server refuses anything outside them |
| Date-shift setting | Default shifting, or a narrowed or disabled shift with the exempt resource types and elements named | Governs the default date-shift policy and whether _since and _until are accepted |
Provenance inclusion |
Whether source attribution travels with the data | Provenance is exported unless the permit excludes it |
| Secure Processing Environment | The one environment the output may be delivered to | Destination of the NDJSON files, and the recipient scope for the Pseudonymization Service |
| Validity and retention period | How long the permit is valid and how long the exported dataset may be held | Bounds the lifetime of the group and the dataset in the SPE |
The EHDS secondary-use opt-out is applied by the data holder, at group activation, and again at every re-activation. It is not a client-side filter and it is not visible to the data user: an opted-out subject is simply absent from Group.member and therefore from quantity and from every export of that group. A data holder that cannot reliably evaluate the opt-out register SHALL NOT activate the group.
Direct identifiers SHALL be replaced by pseudonyms produced by the Pseudonymization Service before data becomes readable in the SPE, with the SPE of the data permit as the pseudonym recipient. Because pseudonyms are recipient-scoped, datasets from two permits cannot be joined, while records for the same subject delivered by different data holders under one permit can.
Pseudonymisation alone does not make a dataset anonymous. Indirect identifiers (rare diagnosis codes, exact dates, postal areas, extreme values) remain re-identifying. Suppressing them is a joint responsibility of the data permit (which SHOULD constrain _elements), the data holder (which SHOULD refuse exports below a minimum cohort size and applies the default date-shift policy), and the SPE (which performs output checking).
Exact dates are among the strongest indirect identifiers in an exported dataset. Unless the data permit specifies otherwise, the Bulk Export Server SHALL apply the following default date-shift policy to every exported resource:
date and dateTime values are moved by a deterministic, subject-specific offset in the range −15 through +15 days;meta.lastUpdated is shifted with the same offset; leaving a record-keeping timestamp unshifted next to a shifted clinical date would reveal the offset.Because the exported meta.lastUpdated no longer matches the value the server selects on, _since and _until SHALL NOT be used while shifting is in effect; a kick-off request that supplies either SHALL be refused with a 4XX and an OperationOutcome.
Cohort criteria and _typeFilter expressions are evaluated against the source dates, before the shift. An exported date may therefore fall just outside the clinical time window that selected the resource. A data user working with a narrow window SHALL widen it by the shift range or obtain a permit that disables shifting.
A data permit MAY disable or narrow date shifting where the research question requires exact dates, for example outbreak or seasonality research. The permit then names the resource types and elements that are exempt, and that exemption is carried in the access token like every other permit constraint. Only when the permit disables shifting altogether does meta.lastUpdated carry its source value and does the server accept _since and _until.
All exchanges SHALL use TLS 1.2 or later. Both the Bulk Export Client and the Bulk Export Server SHALL record AuditEvent resources following the BALP patterns, for kick-off, status and export requests, including the data permit identifier and the VAD-established researcher identity.