## On this page

Medplum supports the [Bulk FHIR API 2.0.0](https://hl7.org/fhir/uv/bulkdata/STU2/). The Bulk FHIR API uses [Backend Services Authorization](https://www.hl7.org/fhir/smart-app-launch/backend-services.html).

## Use Cases 
- **Population Health Reporting**: Export data for entire patient populations for analytics and reporting dashboards
- **Data Migration**: Transfer large datasets between FHIR-compliant systems during system migrations
- **Regulatory Compliance**: Meet ONC, MIPS, and other regulatory requirements for bulk data access
- **Research Data Extraction**: Export de-identified or consented patient data for clinical research studies
- **Data Warehousing**: Populate analytics data warehouses with comprehensive FHIR data exports

The premise of the Bulk FHIR API is that it allows you to create a **bulk export of data for multiple patients**. There are different ways to export data:
- From a Group of patients, which will export everything in each patient's compartment
- As a system level export of all FHIR resources in a Project

The export process is asynchronous, and you will need to poll a status URL returned when you start the export. After the [BulkDataExport resource](/content/docs/api/fhir/medplum/bulkdataexport/index.html) with the export results is available, it will contain a set of URLs where you can download the exported data in [NDJSON](https://jsonlines.org/) format.

## Access Policy Requirements 
Because the bulk export process is asynchronous, your [AccessPolicy](/content/docs/access/access-policies/index.html) must grant you access to the `AsyncJob` resourceType. This is required to poll the status of the export operation. Without access to `AsyncJob`, you will not be able to check the status of your export or retrieve the results.

Your AccessPolicy should include an entry like this:

```json
{
  "resourceType": "AccessPolicy",
  "resource": [
    {
      "resourceType": "AsyncJob",
      "readonly": true
    }
  ]
}
```

## Group Export 
To specify which patients need to be included in the export, construct a [Group](/content/docs/api/fhir/resources/group/index.html) resource and add specific patients as `Group.member.entity.`
To start the process of exporting the resources, make an HTTP `GET` request for `/fhir/R4/Group/<GROUP_ID>/$export?_outputFormat=ndjson`. This initiates a Bulk Data Export transaction and return links to download URLs for requested resources.

```bash
curl 'https://api.medplum.com/fhir/R4/Group/<GROUP_ID>/$export?_outputFormat=ndjson' \
  -H 'Authorization: Bearer <ACCESS_TOKEN>'
```

| Resource in Medplum App | Usage in Bulk FHIR |
| --- | --- |
| [Group](https://app.medplum.com/Group) | All patients you want to include must be included as Group.member.entity |

## System Level Export 
An export can also be performed for all resources in a Project by making a `GET` request for `/fhir/R4/$export`.

```py
import http.client
import time
import json
import os
from typing import Any, TypedDict, List

class ExportOutput(TypedDict):
  type: str
  url: str

class BulkExportResponse(TypedDict):
  transactionTime: str
  request: str
  requiresAccessToken: bool
  output: List[ExportOutput]
  error: List[Any]

access_token = '[Requires valid access token]'
conn = http.client.HTTPSConnection('api.medplum.com')
conn.request(
  'GET', '/fhir/R4/$export', None, {
    'Authorization': 'Bearer ' + access_token,
    'Content-Type': 'application/fhir+json',
  })
init = conn.getresponse()
if init.status != 202:
  raise RuntimeError('Failed to start bulk export')
status_url: str | None = init.getheader('Content-Location')
if status_url == None:
  raise RuntimeError('No status URL found')
init.read()
conn.request(
  'GET', status_url, None, {
    'Authorization': 'Bearer ' + access_token,
  })
status = conn.getresponse()
while status.status == 202:
  status.read()
  time.sleep(1)
  conn.request(
    'GET', status_url, None, {
      'Authorization': 'Bearer ' + access_token,
    })
  status = conn.getresponse()
if status.status != 200:
  raise RuntimeError('Error exporting data')
body = status.read()
export: BulkExportResponse = json.loads(body)

def download_export_to_file(export_record: ExportOutput, access_token: str) -> None:
  from urllib.parse import urlparse
  url: str = export_record['url']
  parsed = urlparse(url)
  host = parsed.netloc
  path = parsed.path
  if parsed.query:
    path += '?' + parsed.query
  if parsed.scheme == 'https':
    download_conn = http.client.HTTPSConnection(host)
  else:
    download_conn = http.client.HTTPConnection(host)
  download_conn.request(
    'GET', path, None, {
      'Authorization': 'Bearer ' + access_token,
    })
  export_data = download_conn.getresponse()
  data: bytes = export_data.read()
  download_conn.close()
  file_path: str = os.path.join('medplum_resources', export_record['type'] + '.ndjson')
  with open(file_path, 'w') as f:
    f.write(data.decode('utf-8'))

os.makedirs('medplum_resources', exist_ok=True)
for record in export['output']:
  download_export_to_file(record, access_token)
```

## Related Reading 
- [Reporting and Analytics](/content/docs/analytics/index.html) overview
- [ONC Certification](/content/docs/compliance/onc/index.html) compliance docs
- [Standardized API for patient and population services](https://www.healthit.gov/test-method/standardized-api-patient-and-population-services) on HealthIT.gov
- [Use Cases](/content/docs/api/fhir/operations/bulk-fhir#use-cases/index.html)
- [Access Policy Requirements](/content/docs/api/fhir/operations/bulk-fhir#access-policy-requirements/index.html)
- [Group Export](/content/docs/api/fhir/operations/bulk-fhir#group-export/index.html)
- [System Level Export](/content/docs/api/fhir/operations/bulk-fhir#system-level-export/index.html)
- [Related Reading](/content/docs/api/fhir/operations/bulk-fhir#related-reading/index.html)
