Lesson 44 of 51 · Apps and Access
Bulk Data Export (Flat FHIR)
From one patient to whole populations
The previous lesson showed SMART App Launch moving data for a single patient into an application’s user interface — a clinician’s risk calculator, a patient’s phone app. That model is built for interactive access: one user, one chart, a handful of resources at a time. It is the wrong tool when the goal is an entire population. A research database loading a study cohort, a quality-measure engine scoring every attributed patient, or an Accountable Care Organization (ACO) ingesting a payer’s member feed all need large datasets across many patients at once. Fetching those records resource-by-resource over the regular REST API — paging through search results for thousands of patients — is slow, hammers the server, and does not scale. Population-scale data movement needs a different mechanism.
Bulk Data Access (Flat FHIR)
Bulk Data Access, often called Flat FHIR, is the HL7 implementation guide that fills this gap. It is built on FHIR R4 and defines an asynchronous bulk export so a client can ask a server for a large dataset and retrieve it in bulk 1. “Asynchronous” is the key word: the server cannot assemble a population-scale extract in the time of a single HTTP request, so the work runs as a background job and the client returns later to collect the result. The flow has four stages — kick-off, status polling, and download — coordinated through the standard asynchronous request pattern 2.
1. Kick-off
The client starts an export with the $export operation. There are three
levels, depending on how much data is wanted 1:
- System level —
[base]/$exportexports data the server holds across the whole system. - All patients —
[base]/Patient/$exportexports data for all patients. - Group level —
[base]/Group/[id]/$exportexports data for the members of a defined group, such as an ACO’s attributed population or a study cohort.
2. The 202 Accepted response
Because the export runs in the background rather than returning data inline, the
server does not answer with the files. Instead it replies 202 Accepted and
includes a Content-Location header carrying a URL the client will poll for
progress 1. This is the standard FHIR
asynchronous request pattern: the request was accepted, the job is queued, and the
returned URL is where its status lives 2.
3. Status polling
The client polls that status URL until the job finishes. While the export is still running, the server keeps reporting that it is in progress; when the job is complete, the completion response is a JSON manifest that lists the output files, one entry per resource type, each with a URL to download 1.
4. Download
The output files are newline-delimited JSON (ndjson) — typically one file per resource type, with one FHIR resource per line 1. This flat, line-oriented format is what gives “Flat FHIR” its name: instead of one large nested Bundle, each line is an independent resource, so a consumer can stream the file and process records one at a time without loading everything into memory.
Authorization: backend services, not a consent screen
A single-patient SMART app authorizes against a user who clicks “allow” on a
consent screen. Bulk export has no such user — it is a backend, server-to-server
interaction between two systems. It is authorized with SMART Backend Services
using system/ scopes (for example, system/Patient.rs), where trust is
established between the organizations ahead of time rather than by a person
approving access in the moment 1.
The exporting server already knows and trusts the requesting client, so the job can
run unattended.
A kick-off, accept, and output sketch
# 1. Kick-off: request a Group-level export (asynchronous)
GET [base]/Group/123/$export
Accept: application/fhir+json
Prefer: respond-async
Authorization: Bearer <backend-services-token>
# 2. Server accepts the job and returns where to poll
HTTP/1.1 202 Accepted
Content-Location: https://ehr.example.org/fhir/bulkstatus/abc-789
# 3. Client polls the status URL until complete; the manifest lists output files
HTTP/1.1 200 OK
{
"transactionTime": "2026-06-01T00:00:00Z",
"request": "https://ehr.example.org/fhir/Group/123/$export",
"output": [
{ "type": "Patient", "url": "https://ehr.example.org/files/Patient.ndjson" },
{ "type": "Observation", "url": "https://ehr.example.org/files/Observation.ndjson" }
]
}
# 4. Each output file is ndjson — one resource per line
{"resourceType":"Patient","id":"p1", ...}
{"resourceType":"Patient","id":"p2", ...}
{"resourceType":"Patient","id":"p3", ...}
Where this is used
Bulk Data Access is the backbone of population-scale FHIR work: computing
quality measures over an attributed panel, assembling research cohorts for
study, population health analytics, and ACO reporting feeds between
organizations. Wherever the question is about many patients rather than one, the
asynchronous $export flow — kick-off, poll, download ndjson — is how the data
moves 1.
References
- HL7 FHIR Bulk Data Access (Flat FHIR) Implementation Guide. HL7 International. verified Cited at: Bulk Data Access overview; $export kick-off; asynchronous request pattern; status request; ndjson output; backend services authorization.
- HL7 FHIR Release 4 (R4), v4.0.1. HL7 International. 2019. verified Cited at: operations.html.