Bulk Export
The Bulk Export system implements the FHIR Bulk Data Access specification, enabling you to export large datasets asynchronously in NDJSON format. It is ideal for data migrations, analytics pipelines, compliance reporting, and population health management.
How Bulk Export Works
Bulk exports are asynchronous. The flow is:
- Kick off an export — receive a
202 Acceptedwith a polling URL - Poll the status URL until the export is complete (
200 OK) - Download the output files from the provided URLs
- Delete the export job when done
System-Level Export
Export all resources across your organization:
curl "https://api.esus.health/fhir/\$export" \
-H "Authorization: Bearer TOKEN" \
-H "Accept: application/fhir+json" \
-H "Prefer: respond-async"
Response: 202 Accepted
Content-Location: https://api.esus.health/bulk-export/aa11bb22-cc33-dd44-ee55-ff6677889900
Filter by Resource Type
curl "https://api.esus.health/fhir/\$export?_type=Patient,Observation,Condition" \
-H "Authorization: Bearer TOKEN" \
-H "Prefer: respond-async"
Filter by Date
# Only resources modified since a specific date
curl "https://api.esus.health/fhir/\$export?_since=2024-01-01T00:00:00Z" \
-H "Authorization: Bearer TOKEN" \
-H "Prefer: respond-async"
Patient-Level Export
Export all resources for all patients in your organization:
curl "https://api.esus.health/fhir/Patient/\$export" \
-H "Authorization: Bearer TOKEN" \
-H "Prefer: respond-async"
Check Export Status
Poll the Content-Location URL to check progress:
curl https://api.esus.health/bulk-export/aa11bb22-cc33-dd44-ee55-ff6677889900 \
-H "Authorization: Bearer TOKEN"
In Progress — 202 Accepted
{
"status": "in-progress",
"progress": "Export is being processed"
}
The progress field is a descriptive status string (not a percentage). The response also carries the same message in the X-Progress header.
Complete — 200 OK
{
"transactionTime": "2024-01-15T10:30:00Z",
"request": "https://api.esus.health/fhir/$export",
"requiresAccessToken": true,
"output": [
{
"type": "Patient",
"url": "/bulk-export/aa11bb22-cc33-dd44-ee55-ff6677889900/download/Patient",
"count": 1240
},
{
"type": "Observation",
"url": "/bulk-export/aa11bb22-cc33-dd44-ee55-ff6677889900/download/Observation",
"count": 5820
},
{
"type": "Condition",
"url": "/bulk-export/aa11bb22-cc33-dd44-ee55-ff6677889900/download/Condition",
"count": 980
}
],
"error": []
}
Download Export Files
Each manifest output entry exposes a URL of the form /bulk-export/<id>/download/<ResourceType>. Calling it returns a short-lived presigned URL (valid for 900 seconds) pointing at the NDJSON file — raw storage keys are never surfaced:
curl "https://api.esus.health/bulk-export/aa11bb22-cc33-dd44-ee55-ff6677889900/download/Patient" \
-H "Authorization: Bearer TOKEN"
Response:
{
"url": "https://storage.esus.health/bulk-exports/...?signature=...",
"count": 1240,
"expiresInSeconds": 900
}
Then download the NDJSON from the presigned URL:
curl "https://storage.esus.health/bulk-exports/...?signature=..." -o patients.ndjson
Output files are in NDJSON (newline-delimited JSON) format — one FHIR resource per line.
Sample patients.ndjson:
{"resourceType":"Patient","id":"3fa85f64-5717-4562-b3fc-2c963f66afa6","meta":{"versionId":"3","lastUpdated":"2024-01-10T08:00:00Z"},"name":[{"family":"Smith","given":["John"]}],"birthDate":"1985-03-15"}
{"resourceType":"Patient","id":"bb22cc33-dd44-ee55-ff66-778899001122","meta":{"versionId":"1","lastUpdated":"2024-01-12T14:00:00Z"},"name":[{"family":"Doe","given":["Jane"]}],"birthDate":"1990-07-22"}
Cancel an Export
If you no longer need an export or want to free up resources:
curl -X DELETE https://api.esus.health/bulk-export/aa11bb22-cc33-dd44-ee55-ff6677889900 \
-H "Authorization: Bearer TOKEN"
Returns 204 No Content on success.
Processing NDJSON in Common Languages
Node.js
import { createReadStream } from 'fs'
import { createInterface } from 'readline'
const rl = createInterface({ input: createReadStream('patients.ndjson') })
rl.on('line', (line) => {
const patient = JSON.parse(line)
console.log(patient.id, patient.name?.[0]?.family)
})
Python
import json
with open('patients.ndjson') as f:
for line in f:
patient = json.loads(line)
print(patient['id'], patient.get('name', [{}])[0].get('family'))
Use Cases
| Use Case | Recommended Export |
|---|---|
| Full data migration | System-level $export without filters |
| Incremental sync | System-level $export?_since=<last-sync-date> |
| Analytics on clinical data | $export?_type=Patient,Observation,Condition |
| Compliance audit export | $export?_type=AuditEvent&_since=<period-start> |
| Population health | Patient-level Patient/$export |