By EHR

Epic bulk FHIR export for research.

Clinical Extract connects to Epic as a read-only Backend Systems app and exports the study cohort through Epic’s certified bulk FHIR API. Your Epic team registers it once, and each study comes back as study.csv, limited to the variables your protocol approved, with a data dictionary.

1 Works with Epic

Epic support, including Community Connect.

Epic’s certified API (ONC §170.315(g)(10)) exports a Group of patients through HL7 FHIR Bulk Data Access, authorized with SMART Backend Services. Clinical Extract uses that API for the token request, the Group kickoff, the status poll and the file requests, all read-only and all in your environment. The Epic bulk data export it runs covers your study alone: the kickoff names the cohort’s Group FHIR ID and the resource types the protocol lists.

Epic Community Connect affiliates

An affiliate runs on the host organization’s Epic instance, so the host activates the backend app once and the affiliate’s cohort is a Group on that instance. Feasibility counts and study exports for the affiliate run the same way, and a network with affiliates gets one count across all of them (see counting feasibility across a network).

Epic FHIR bulk data as NDJSON, flattened to CSV
Patient, Encounter, Condition, Observation, MedicationRequest and Procedure files land in your study folder. Clinical Extract flattens the approved variables into study.csv and describes each column in data-dictionary.json.
Coded columns
Diagnoses, biomarkers, staging, medications and procedures come out as ICD-10-CM, LOINC, RxNorm and SNOMED CT codes, each column traced to its FHIR element.
The oncology lens
ER, PR, HER2, PD-L1, EGFR, KRAS, BRAF, ALK, MSI and TNM clinical and pathologic arrive as named columns.
Roster pull and the cohort builder
Match a list of MRNs or names against Epic with graded results, or build the cohort from coded criteria and read the feasibility count before the export.

2 Connecting

How your Epic team connects Clinical Extract.

Setup on Epic is three one-time steps.

  1. Register. On Epic on FHIR (fhir.epic.com, Build Apps), Clinical Extract is a Backend Systems app with the four Bulk Data APIs (Kick-off, Status Request, File Request, Delete Request), the R4 Search API for each resource type it exports, and a JWK Set URL that serves its public key. Epic issues the Non-Production and Production client IDs.
  2. Activate. Your Epic team downloads the client record by client ID, links a background user that carries the security points, and authorizes the client for the study Group.
  3. Export. Clinical Extract signs a JWT with the private key that stays in your environment, exchanges it at the token endpoint for an access token with read scopes only, and runs the Group export.

Our guide to publishing a backend app through Epic’s program walks through the registration, and the Epic integration reference covers the API itself. For the other routes to research data in Epic (EHI export, Clarity and Caboodle, SlicerDicer, Cosmos), see the routes compared.

Connecting Clinical Extract to EpicThree numbered steps from left to right. Step 1, register, at Epic on FHIR (fhir.epic.com, Build Apps), outside your environment: Application audience: Backend Systems; Incoming APIs: Bulk Data Kick-off, Bulk Data Status Request, Bulk Data File Request, Bulk Data Delete Request; Search APIs for each exported resource: Patient.Search (R4), Encounter.Search (R4), Condition.Search (Problems) (R4), Condition.Search (Encounter Diagnosis) (R4), Observation.Search (Labs) (R4), MedicationRequest.Search (Signed Medication Order) (R4), Procedure.Search (Orders) (R4), Procedure.Search (Surgeries) (R4); JWK Set URL: https://{your-host}/jwks.json; Issued: Non-Production Client ID, Production Client ID. An arrow carries the client ID to step 2. Step 2, activate, at Your Epic (your organization’s instance): Client record: downloaded by client ID; Background user: linked to the client; it carries the security points; Group: the client is authorized for the study Group; Community Connect: affiliates are activated by the host organization, on its instance. An arrow carries the base URL and Group FHIR ID to step 3. Step 3, export, from Clinical Extract in your environment: request POST …/oauth2/token (grant_type=client_credentials, client_assertion: JWT signed RS384, scope=system/Patient.read …); request GET …/api/FHIR/R4/Group/{Group FHIR ID}/$export (?_type=Patient,Encounter,Condition,Observation,MedicationRequest,Procedure, Prefer: respond-async), highlighted; response 202 Accepted (Content-Location: {status-url}); request GET {status-url} (202 Accepted while the export runs, with Retry-After); response 200 OK, JSON manifest (transactionTime, request,, requiresAccessToken, error[],, output[{type, url, count}]); request GET {output-url} (NDJSON, one resource per line); result study.csv, data-dictionary.json (flattened, approved variables only).1REGISTEREpic on FHIRfhir.epic.com, Build AppsAPPLICATION AUDIENCEBackend SystemsINCOMING APISBulk Data Kick-offBulk Data Status RequestBulk Data File RequestBulk Data Delete RequestSEARCH APIS FOR EACH EXPORTED RESOURCEPatient.Search (R4)Encounter.Search (R4)Condition.Search (Problems) (R4)Condition.Search (EncounterDiagnosis) (R4)Observation.Search (Labs) (R4)MedicationRequest.Search (SignedMedication Order) (R4)Procedure.Search (Orders) (R4)Procedure.Search (Surgeries) (R4)JWK SET URLhttps://{your-host}/jwks.jsonISSUEDNon-Production Client IDProduction Client ID2ACTIVATEYour Epicyour organization’s instanceCLIENT RECORDdownloaded by client IDBACKGROUND USERlinked to the client; it carriesthe security pointsGROUPthe client is authorized for thestudy GroupCOMMUNITY CONNECTaffiliates are activated by thehost organization, on its instance3EXPORTClinical Extractin your environmentrequestresponsePOST …/oauth2/tokengrant_type=client_credentialsclient_assertion: JWT signed RS384scope=system/Patient.read …GET …/api/FHIR/R4/Group/{Group FHIR ID}/$export?_type=Patient,Encounter,Condition,Observation,MedicationRequest,ProcedurePrefer: respond-async202 AcceptedContent-Location: {status-url}GET {status-url}202 Accepted while the exportruns, with Retry-After200 OK, JSON manifesttransactionTime, request,requiresAccessToken, error[],output[{type, url, count}]GET {output-url}NDJSON, one resource per linestudy.csv, data-dictionary.jsonflattened, approved variables onlyclient IDbase URLGroup FHIR ID
Connecting Clinical Extract to EpicThree numbered steps from left to right. Step 1, register, at Epic on FHIR (fhir.epic.com, Build Apps), outside your environment: Application audience: Backend Systems; Incoming APIs: Bulk Data Kick-off, Bulk Data Status Request, Bulk Data File Request, Bulk Data Delete Request; Search APIs for each exported resource: Patient.Search (R4), Encounter.Search (R4), Condition.Search (Problems) (R4), Condition.Search (Encounter Diagnosis) (R4), Observation.Search (Labs) (R4), MedicationRequest.Search (Signed Medication Order) (R4), Procedure.Search (Orders) (R4), Procedure.Search (Surgeries) (R4); JWK Set URL: https://{your-host}/jwks.json; Issued: Non-Production Client ID, Production Client ID. An arrow carries the client ID to step 2. Step 2, activate, at Your Epic (your organization’s instance): Client record: downloaded by client ID; Background user: linked to the client; it carries the security points; Group: the client is authorized for the study Group; Community Connect: affiliates are activated by the host organization, on its instance. An arrow carries the base URL and Group FHIR ID to step 3. Step 3, export, from Clinical Extract in your environment: request POST …/oauth2/token (grant_type=client_credentials, client_assertion: JWT signed RS384, scope=system/Patient.read …); request GET …/api/FHIR/R4/Group/{Group FHIR ID}/$export (?_type=Patient,Encounter,Condition,Observation,MedicationRequest,Procedure, Prefer: respond-async), highlighted; response 202 Accepted (Content-Location: {status-url}); request GET {status-url} (202 Accepted while the export runs, with Retry-After); response 200 OK, JSON manifest (transactionTime, request,, requiresAccessToken, error[],, output[{type, url, count}]); request GET {output-url} (NDJSON, one resource per line); result study.csv, data-dictionary.json (flattened, approved variables only).1REGISTEREpic on FHIRfhir.epic.com, Build AppsAPPLICATION AUDIENCEBackend SystemsINCOMING APISBulk Data Kick-offBulk Data Status RequestBulk Data File RequestBulk Data Delete RequestSEARCH APIS FOR EACH EXPORTED RESOURCEPatient.Search (R4)Encounter.Search (R4)Condition.Search (Problems) (R4)Condition.Search (Encounter Diagnosis)(R4)Observation.Search (Labs) (R4)MedicationRequest.Search (SignedMedication Order) (R4)Procedure.Search (Orders) (R4)Procedure.Search (Surgeries) (R4)JWK SET URLhttps://{your-host}/jwks.jsonISSUEDNon-Production Client IDProduction Client IDclient ID2ACTIVATEYour Epicyour organization’s instanceCLIENT RECORDdownloaded by client IDBACKGROUND USERlinked to the client; it carries thesecurity pointsGROUPthe client is authorized for the studyGroupCOMMUNITY CONNECTaffiliates are activated by the hostorganization, on its instancebase URLGroup FHIR ID3EXPORTClinical Extractin your environmentrequestresponsePOST …/oauth2/tokengrant_type=client_credentialsclient_assertion: JWT signed RS384scope=system/Patient.read …GET …/api/FHIR/R4/Group/{Group FHIR ID}/$export?_type=Patient,Encounter,Condition,Observation,MedicationRequest,ProcedurePrefer: respond-async202 AcceptedContent-Location: {status-url}GET {status-url}202 Accepted while the export runs, withRetry-After200 OK, JSON manifesttransactionTime, request,requiresAccessToken, error[],output[{type, url, count}]GET {output-url}NDJSON, one resource per linestudy.csv, data-dictionary.jsonflattened, approved variables only
Figure 1. Connecting Clinical Extract to Epic. Clinical Extract is registered once on Epic on FHIR as a Backend Systems app; your Epic team downloads its client record, links a background user and authorizes the study Group; Clinical Extract then runs the Group export in your environment. Community Connect affiliates are activated on the host organization’s instance. Resource lists and URLs are abridged.

3 Export time

Export time at Epic’s reported rates.

Jones et al. (JAMIA, 2024) measured (g)(10) bulk export at five sites: Epic sites ran at 502 to 2,827 resources per minute, and it took the sites 2 to 119 days (mean 65) from submitting cohort criteria to the first successful bulk request. Clinical Extract asks Epic only for the study cohort and the resource types the protocol names. A 212-patient study of about 58,000 resources exports in under two hours at those rates, and with the criteria and the count from the cohort builder, your Epic team sets up the study Group when the protocol is approved.

The export runs asynchronously. Clinical Extract polls the status URL at the interval Epic’s Retry-After header gives, reads the X-Progress header while the export runs, and downloads every file the manifest lists with the bearer token. Source: Jones et al. J Am Med Inform Assoc. 2024, doi:10.1093/jamia/ocae040.

Every header of the kickoff, status and manifest exchange is in our guide to bulk FHIR export.

4 Facts

Epic connection facts.

Table 1. Endpoint, registration, authentication, export type and output on Epic. Checked September 2026.
Endpoint FHIR R4 base https://{interconnect}/api/FHIR/R4 on your organization’s Interconnect server; the sandboxes on fhir.epic.com and vendorservices.epic.com use the same shape
Registration Epic on FHIR (fhir.epic.com): a Backend Systems app with the Bulk Data Kick-off, Status Request, File Request and Delete Request APIs plus the R4 Search API for each exported resource (Patient, Encounter, Condition, Observation, MedicationRequest, Procedure); a Non-Production and a Production client ID
Authentication SMART Backend Services: a JWT signed RS384 with Clinical Extract’s private key (iss and sub the client ID, aud the token endpoint, lifetime under five minutes), exchanged at https://{interconnect}/oauth2/token for an access token with one system/<Resource>.read scope per exported type plus system/Group.read; the public key served at Clinical Extract’s JWK Set URL
Export type Group export: GET Group/{Group FHIR ID}/$export with _type (and _typeFilter when the study narrows by search parameters); 202 Accepted with a Content-Location status URL under api/FHIR/BulkRequest
Status and files 202 with X-Progress and Retry-After while the export runs; 200 with the JSON manifest; NDJSON files fetched with the bearer token (requiresAccessToken: true), up to 3,000 resources per file, one resource per line
Output study.csv (UTF-8, one row per cohort patient, one column per approved variable) with data-dictionary.json (description, fhirSource and example per column); the NDJSON files and the manifest kept beside them in your study folder

The table scrolls sideways.

Sources: Epic on FHIR, HL7 Bulk Data Access, SMART Backend Services.

5 Specimens

What the exchange and the output look like.

Every value is synthetic. The cohort is a 212-patient breast cancer study with 24 approved variables; the manifest counts and the row shown come from it.

Listing 1. The export kickoff: one request for the study cohort's Group and the resource types the protocol names, answered at once with the status URL.
GET https://{interconnect}/api/FHIR/R4/Group/{Group FHIR ID}/$export
    ?_type=Patient,Encounter,Condition,Observation,MedicationRequest,Procedure
Accept: application/fhir+json
Prefer: respond-async
Authorization: Bearer {access token}

202 Accepted
Content-Location: https://{interconnect}/api/FHIR/BulkRequest/{request-id}
Listing 2. The completed status response: the manifest lists one NDJSON file per requested type with its line count, and says the files need the bearer token.
{
  "transactionTime": "2026-09-28T14:02:11Z",
  "request": "https://{interconnect}/api/FHIR/R4/Group/{Group FHIR ID}/$export?_type=Patient,Encounter,Condition,Observation,MedicationRequest,Procedure",
  "requiresAccessToken": true,
  "output": [
    { "type": "Patient", "url": "https://{interconnect}/api/FHIR/BulkRequest/{request-id}/{file-1}", "count": 212 },
    { "type": "Encounter", "url": "https://{interconnect}/api/FHIR/BulkRequest/{request-id}/{file-2}", "count": 3120 },
    { "type": "Condition", "url": "https://{interconnect}/api/FHIR/BulkRequest/{request-id}/{file-3}", "count": 1684 },
    { "type": "Observation", "url": "https://{interconnect}/api/FHIR/BulkRequest/{request-id}/{file-4}", "count": 48310 },
    { "type": "MedicationRequest", "url": "https://{interconnect}/api/FHIR/BulkRequest/{request-id}/{file-5}", "count": 3905 },
    { "type": "Procedure", "url": "https://{interconnect}/api/FHIR/BulkRequest/{request-id}/{file-6}", "count": 912 }
  ],
  "error": []
}
Listing 3. One line of Observation.ndjson: a complete FHIR resource per line, here the estrogen receptor status of patient eKx3p9Qa (LOINC 16112-5; SNOMED CT 10828004, Positive).
{"resourceType":"Observation","id":"eObs-3f9a","status":"final","category":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/observation-category","code":"laboratory"}]}],"code":{"coding":[{"system":"http://loinc.org","code":"16112-5","display":"Estrogen receptor [Interpretation] in Tissue"}]},"subject":{"reference":"Patient/eKx3p9Qa"},"effectiveDateTime":"2024-03-20","valueCodeableConcept":{"coding":[{"system":"http://snomed.info/sct","code":"10828004","display":"Positive"}]}}
Listing 4. study.csv, the header row and row 1 of 212: one row per patient in the cohort, one column per approved variable (10 of 24 columns shown). Values are synthetic.
patient_ref,birth_year,gender,dx_code,dx_date,stage_group,er_status,pr_status,her2_status,first_chemo
eKx3p9Qa,1961,female,C50.412,2024-03-14,IIA,Positive,Positive,Negative,paclitaxel
Listing 5. The er_status entry in data-dictionary.json: the description with its code system, the FHIR element the value came from, and an example value.
{
  "er_status": {
    "description": "Estrogen receptor status (LOINC 16112-5)",
    "fhirSource": "Observation.valueCodeableConcept",
    "example": "Positive"
  }
}

6 Questions

Questions about Epic

Does Epic support bulk FHIR export?

Epic is certified to ONC §170.315(g)(10), which requires the HL7 Bulk Data Group export behind SMART Backend Services, and Epic on FHIR lists the four Bulk Data APIs. Clinical Extract uses them as a Backend Systems app.

Can you export data from Epic for a research study?

Clinical Extract exports the study cohort as a Group, keeps the variables the protocol approved, and writes study.csv and its data dictionary to your study folder.

Where does the Group FHIR ID come from?

Your Epic team provisions the Group for the study and authorizes the client for it; the cohort builder gives them the criteria and the count. Clinical Extract runs the export against that Group ID on your Interconnect base URL.

What does Clinical Extract need from our Epic team?

Three things: the client record downloaded by client ID, a background user linked to the client, and the study Group authorized for it. Later studies reuse the client and the background user.

Does it work for Epic Community Connect affiliates?

The affiliate runs on the host organization’s instance, so the host activates the app and the affiliate’s cohort is a Group there. Counting feasibility across a network covers affiliates alongside the sites that run their own Epic.

How long does an Epic bulk export take?

Hours for a study, because the request covers the cohort and its resource types only. Jones et al. measured 502 to 2,827 resources per minute at Epic sites; a 212-patient study of about 58,000 resources finishes in under two hours at those rates.

Next

See Clinical Extract run on one of your studies.

Tell us which EHR you run and what the study or registry needs. We reply within one business day to set a meeting time.

Request a demo