Bulk FHIR ($export) is the right tool for periodic, cohort-based extracts for analytics, quality reporting and AI evaluation sets. It is the wrong tool for near-real-time warehouse sync. Expect asynchronous jobs measured in hours, NDJSON files that expire (14 days at Epic), Group-only support at major EHRs, and a de-identification step before any AI use.
Population data is suddenly on every roadmap. AI teams want training and evaluation sets, payers have CMS-0057-F Provider Access and Payer-to-Payer APIs due from January 1, 2027, and quality and value-based care programs need cohort extracts. The HL7 Bulk Data Access IG, also called Flat FHIR, is now at v3.0.0 (STU 3). But what the spec allows and what each EHR implements are different things. This guide covers the protocol, the vendor limits, the performance and cost drivers, and how to de-identify before data reaches a model.
It is an asynchronous FHIR operation ($export) that returns large amounts of data for a system, a group or a set of patients as newline-delimited JSON (NDJSON) files. You start a job, poll a status URL, then download files listed in a manifest.
The flow from the export specification:
[base]/$export, [base]/Patient/$export or [base]/Group/[id]/$export with Prefer: respond-async. Success is 202 Accepted with a Content-Location header pointing to the status endpoint.X-Progress and Retry-After. Respect Retry-After.transactionTime, requiresAccessToken, and arrays of output, deleted and error files.requiresAccessToken is true, file requests need the same access token protection as the kick-off.Authorization should follow SMART Backend Services, with scopes that cover every exported resource type.

STU 3 adds partial manifests (allowPartialManifests) so servers can release files before the whole job finishes, output organized by resource instance (organizeOutputBy), an _until parameter to pair with _since, a Group profile for characteristic-based cohorts, and promotes _typeFilter from experimental.
According to the IG's change log, STU 2 had already added POST kick-off with a Parameters resource, _elements, a patient filter and includeAssociatedData. The Argonaut accelerator is specifying $bulk-submit and $bulk-publish for a future release. Do not build on v3 features until your source EHR advertises them; most production EHRs implement earlier versions, as the next section shows.
Less than the spec. Epic supports only Group export (based on v1.0.1 plus some later features) and does not support _since. Oracle Health supports Group export (v1.0.1 plus some v2 parameters) and Patient export (v2.0.0) that requires explicit patient IDs. Both run exports on the provider's operational systems.
| Epic | Oracle Health (Millennium) | |
|---|---|---|
| Export levels | Group only | Group and Patient (Patient needs a list of IDs, no "all patients") |
| Spec version | 1.0.1 plus some later features | Group: 1.0.1 plus some v2.0.0 experimental parameters; Patient: 2.0.0 |
Incremental (_since) | Not supported | Not documented on the bulk page |
| Filters | _type, _typeFilter, includeAssociatedData | Unsupported parameters return 422 unless Prefer: handling=lenient |
| Result retention | 14 days, then deleted | Not documented on the bulk page |
| Polling guidance | Every 10 min for groups under 100 patients, every 30 min above | 429 when too many requests |
| Who defines the group | The health system (you get the Group FHIR ID from them) | The health system, in Ignite Management Tooling |
Sources: Epic's bulk data tutorial and Oracle's Bulk Data Access documentation. Epic is explicit about fit: it lists "data synchronization with data warehouses," "periodic loads of large amounts of clinical data" and "incremental data loads" as poor use cases, and says results are not incremental and cannot be retrieved until the whole job is ready.
For evaluation sets and cohort-scoped fine-tuning data, yes, after de-identification and with a clear legal basis. For continuously refreshed training corpora across a whole health system, usually no: the extract model, vendor limits and load on operational systems make it a poor fit.
Where bulk works well for AI:
Where it does not: real-time decision support (use EHR-native APIs or CDS Hooks), whole-enterprise data lakes (ask about the EHR vendor's warehouse options), and anything that needs updates within minutes.
Use one of HIPAA's two methods. Safe Harbor removes 18 identifier types, including all date elements except year, and all but the first three ZIP digits (those too, where that area has 20,000 people or fewer). Expert Determination has a qualified expert document that re-identification risk is "very small." Free text needs extra work in both cases.
The rules are in 45 CFR 164.514. FHIR-specific traps:
Encounter.identifier, DocumentReference metadata, Coverage member IDs, device identifiers, and URLs in Attachment.url.DocumentReference, Binary) contain names, addresses and dates in free text. Rule-based scrubbing misses some; measure your residual rate on a labeled sample.If the use is research, public health or operations and you need ZIP codes or dates, a limited data set under a data use agreement may fit better than full de-identification.

Land raw NDJSON unchanged in object storage, then flatten into columnar tables with declared, versioned view definitions. SQL on FHIR v2 ViewDefinitions are the emerging standard for this; the SQL on FHIR specification's continuous build is at 2.1.0-pre.
transactionTime, encrypted, with access limited to the pipeline role.error files reviewed, not ignored.Population size, number of resource types, date range and the source system's load set the run time. Your costs come from storage, compute for flattening and de-identification, and engineering time per health system. Narrow filters reduce all of them.
_type and _typeFilter "whenever possible" to cut response time and storage. Binary is excluded by default at Epic and only retrievable via _type.Retry-After and vendor guidance; aggressive polling risks 429s and goodwill._since? Design for it: where incremental export is missing, keep cohorts small and stable, and diff full extracts on your side instead of re-processing everything downstream.Prefer: handling=lenient silently drops unsupported parameters. Verify your filters actually applied.The spec also lets a server reply 429 to prevent a new export while one is still running, so do not launch parallel jobs for the same client without checking the server's policy. For running cost of the AI layer on top, see the cost to run an AI product.
Treating bulk as a sync mechanism, assuming every server supports _since and Patient-level export, ignoring the error and deleted arrays, keeping PHI files past their purpose, and sending raw exports to a model provider without a BAA or de-identification.
Also common: forgetting that Group membership is controlled by the health system and can change between runs, and not recording transactionTime, which you need for any later incremental logic. For governance of the AI that consumes this data, see HIPAA-compliant AI agents and the HIPAA Security Rule and AI in 2026.
We build bulk export clients on SMART Backend Services, land NDJSON in encrypted, access-controlled storage, validate against the manifest, de-identify in a separate zone with measured residual risk on notes, and flatten with versioned view definitions so every number in a dashboard or evaluation set traces back to a source file. We agree cohorts and schedules with each health system and tell you when bulk is the wrong tool. BeevR signs a BAA as a business associate, models can run inside your own AWS account under the AWS BAA, and you own the source code and IP from day one.
Our senior, founder-led team in Hanoi runs a multi-tenant EHR live across four US clinics at about 300 patients a day, with a working demo every Friday and fixed-scope, fixed-price phases from $10K per phase. See our EHR integration services and healthcare AI agent development.
Building an analytics or AI pipeline on EHR data? Book a 30-minute call and we will check your use case against what your source EHRs actually support.