Summary and recommendation
Epic exposes a FHIR R4 API (base: https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4) secured via OAuth 2.0 SMART on FHIR, supporting both Authorization Code flow for user-facing apps and Backend Services (JWT client credentials) for system-to-system access.
Practitioner and PractitionerRole are the primary resources for clinical staff identity;
Patient covers patient portal users.
Production access requires App Orchard review, which can take weeks to months.
Integrating Epic into an identity graph requires careful handling of opaque, instance-specific FHIR IDs
they are base64-encoded, non-portable across environments, and must be resolved from external identifiers like NPI before they can anchor a reliable identity record.
API quick reference
| Has user API | Yes |
| Auth method | OAuth 2.0 (SMART on FHIR) — supports Authorization Code flow for user-facing apps and Backend Services (JWT client credentials) for system-to-system access |
| Base URL | Official docs |
| SCIM available | No |
| SCIM plan required | Enterprise |
Authentication
Auth method: OAuth 2.0 (SMART on FHIR) - supports Authorization Code flow for user-facing apps and Backend Services (JWT client credentials) for system-to-system access
Setup steps
- Register your application in Epic's App Orchard (appmarket.epic.com) or via your organization's Epic instance.
- Specify redirect URIs, requested FHIR scopes, and app type (patient-facing, clinician-facing, or backend service).
- For Backend Services: generate an RSA or EC key pair, register the public JWK with Epic, and sign JWT assertions using the private key.
- For Authorization Code flow: redirect users to Epic's /oauth2/authorize endpoint; exchange the returned code for an access token at /oauth2/token.
- Include the access token as a Bearer token in the Authorization header of all API requests.
- Production access requires Epic review and approval via App Orchard; sandbox access is available at open.epic.com without approval.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| system/Practitioner.read | Read Practitioner (clinical staff/user) records via backend service. | Listing or reading clinician/staff user records in system context |
| system/Practitioner.write | Write/update Practitioner records via backend service. | Creating or updating clinician/staff user records |
| user/Practitioner.read | Read Practitioner records in the context of an authenticated user. | User-context read of clinician records |
| system/Patient.read | Read Patient records via backend service. | Reading patient user records |
| openid | OpenID Connect scope for identity token issuance. | OIDC-based SSO and identity verification |
| fhirUser | Returns the FHIR resource URL of the authenticated user in the ID token. | Identifying the logged-in user's FHIR resource |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | string | Epic-assigned FHIR logical ID for the Practitioner resource. | server-assigned | immutable | Used as the resource identifier in all API calls. |
| identifier | array of Identifier | External identifiers such as NPI, DEA, or internal Epic user ID. | optional | supported | system/value pairs; NPI uses system 'http://hl7.org/fhir/sid/us-npi'. |
| active | boolean | Whether the practitioner record is active. | optional | supported | Setting to false deactivates the record. |
| name | array of HumanName | Practitioner's name(s); includes family, given, prefix, suffix. | required | supported | use='official' for legal name. |
| telecom | array of ContactPoint | Contact details: phone, email, etc. | optional | supported | system values: phone, email, fax. |
| address | array of Address | Practitioner's address(es). | optional | supported | |
| gender | code | Administrative gender: male | female | other | unknown. | optional | supported | |
| birthDate | date | Date of birth. | optional | supported | |
| qualification | array of Qualification | Credentials and certifications (e.g., MD, RN). | optional | supported | Includes code, period, and issuer. |
| communication | array of CodeableConcept | Languages the practitioner communicates in. | optional | supported | |
| photo | array of Attachment | Image of the practitioner. | optional | supported | Support varies by Epic instance. |
Core endpoints
Read Practitioner by ID
- Method: GET
- URL:
https://{epic-base}/api/FHIR/R4/Practitioner/{id} - Watch out for: The {id} is Epic's internal FHIR ID, not the NPI or login username. Use search by identifier to resolve from external IDs.
Request example
GET /api/FHIR/R4/Practitioner/eM5CWtq15N0WJeuCet5bJlQ3
Authorization: Bearer {access_token}
Response example
{
"resourceType": "Practitioner",
"id": "eM5CWtq15N0WJeuCet5bJlQ3",
"active": true,
"name": [{"use":"official","family":"Smith","given":["John"]}],
"telecom": [{"system":"email","value":"jsmith@hospital.org"}]
}
Search Practitioners
- Method: GET
- URL:
https://{epic-base}/api/FHIR/R4/Practitioner?family={name}&identifier={system}|{value} - Watch out for: Not all search parameters are supported on every Epic instance. Supported parameters vary by Epic version and configuration.
Request example
GET /api/FHIR/R4/Practitioner?family=Smith&_count=10
Authorization: Bearer {access_token}
Response example
{
"resourceType": "Bundle",
"type": "searchset",
"total": 2,
"link": [{"relation":"next","url":"..."}],
"entry": [{"resource":{"resourceType":"Practitioner","id":"abc123"}}]
}
Create Practitioner
- Method: POST
- URL:
https://{epic-base}/api/FHIR/R4/Practitioner - Watch out for: Write access to Practitioner is not universally enabled; Epic instances must explicitly configure write permissions. Many Epic deployments are read-only via FHIR.
Request example
POST /api/FHIR/R4/Practitioner
Content-Type: application/fhir+json
{
"resourceType":"Practitioner",
"name":[{"use":"official","family":"Doe","given":["Jane"]}],
"active":true
}
Response example
HTTP 201 Created
Location: /api/FHIR/R4/Practitioner/newId123
{
"resourceType":"Practitioner",
"id":"newId123"
}
Update Practitioner
- Method: PUT
- URL:
https://{epic-base}/api/FHIR/R4/Practitioner/{id} - Watch out for: Full resource replacement (PUT) is required; partial updates via PATCH have limited support in Epic FHIR R4.
Request example
PUT /api/FHIR/R4/Practitioner/eM5CWtq15N0WJeuCet5bJlQ3
Content-Type: application/fhir+json
{
"resourceType":"Practitioner",
"id":"eM5CWtq15N0WJeuCet5bJlQ3",
"active":false
}
Response example
HTTP 200 OK
{
"resourceType":"Practitioner",
"id":"eM5CWtq15N0WJeuCet5bJlQ3",
"active":false
}
Read PractitionerRole (role/department assignment)
- Method: GET
- URL:
https://{epic-base}/api/FHIR/R4/PractitionerRole?practitioner={id} - Watch out for: PractitionerRole is the correct resource for department/specialty/location assignments; role data is not embedded in the Practitioner resource itself.
Request example
GET /api/FHIR/R4/PractitionerRole?practitioner=eM5CWtq15N0WJeuCet5bJlQ3
Authorization: Bearer {access_token}
Response example
{
"resourceType":"Bundle",
"entry":[{"resource":{
"resourceType":"PractitionerRole",
"practitioner":{"reference":"Practitioner/eM5CWtq15N0WJeuCet5bJlQ3"},
"organization":{"reference":"Organization/org1"},
"code":[{"text":"Physician"}]
}}]
}
Read Patient (patient user record)
- Method: GET
- URL:
https://{epic-base}/api/FHIR/R4/Patient/{id} - Watch out for: Patient records represent patient portal users, not clinical staff. MyChart (patient portal) account status is not directly exposed via FHIR.
Request example
GET /api/FHIR/R4/Patient/erXuFYUfucBZaryVksYEcMg3
Authorization: Bearer {access_token}
Response example
{
"resourceType":"Patient",
"id":"erXuFYUfucBZaryVksYEcMg3",
"active":true,
"name":[{"use":"official","family":"Doe","given":["Jane"]}],
"telecom":[{"system":"email","value":"jane@example.com"}]
}
Token endpoint (OAuth 2.0 Backend Services)
- Method: POST
- URL:
https://{epic-base}/oauth2/token - Watch out for: JWT must be signed with the private key whose public JWK was registered during app setup. The 'sub' and 'iss' claims must match the registered client_id.
Request example
POST /oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
&client_assertion={signed_jwt}
Response example
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "system/Practitioner.read"
}
Rate limits, pagination, and events
Rate limits: Epic does not publicly document specific numeric rate limits in its open developer documentation. Rate limiting behavior is instance-specific and negotiated per organization or app registration. The open sandbox (open.epic.com) may impose undocumented throttling.
Rate-limit headers: No
Retry-After header: No
Rate-limit notes: No publicly documented rate limit headers or Retry-After behavior found in official Epic FHIR documentation. Contact Epic or your Epic technical account manager for production rate limit details.
Pagination method: token
Default page size: 10
Max page size: Not documented
Pagination pointer: next (link relation in FHIR Bundle response)
Webhooks available: No
Webhook notes: Epic does not offer a native outbound webhook system for user/practitioner change events via its public FHIR API. Epic's FHIR Subscriptions (R4) support is limited and instance-specific.
Alternative event strategy: Use FHIR Subscription resource (where supported by the Epic instance) or poll the Practitioner/Patient search endpoints periodically to detect changes. Some Epic integrations use HL7 ADT/MDM messages via Epic's integration engine (Bridges) for event-driven workflows.
SCIM API status
- SCIM available: No
- SCIM version: Not documented
- Plan required: Enterprise
- Endpoint: Not documented
Limitations:
- Epic does not expose a native SCIM 2.0 endpoint for user provisioning.
- User provisioning into Epic is typically handled via Epic's internal Identity Management tools, HL7 interfaces, or custom integrations built on Epic's APIs.
- Some organizations use third-party middleware (e.g., Okta, Azure AD) with custom Epic FHIR-based connectors, but these are not native SCIM implementations.
- Enterprise-tier customers may work with Epic's professional services for custom provisioning workflows.
Common scenarios
Three scenarios cover the core provisioning surface.
First, clinician lookup by NPI: obtain a backend service token with system/Practitioner.read, search GET /api/FHIR/R4/Practitioner?identifier=http://hl7.org/fhir/sid/us-npi|{npi_value}, extract the FHIR id from the Bundle, then fetch PractitionerRole for department and specialty assignments
NPI search support must be confirmed per instance.
Second, practitioner deactivation: read the current resource, set active=false, and submit a full PUT replacement with system/Practitioner.write scope;
partial PATCH has limited R4 support.
Third, full roster pagination: issue GET /api/FHIR/R4/Practitioner?_count=50, follow the link[relation=next] URL in each Bundle response until no next link is returned
do not reconstruct the next URL manually, as it is opaque and instance-specific.
Look up a clinician's profile and role by NPI
- Obtain a backend service access token via POST to /oauth2/token with a signed JWT client assertion and system/Practitioner.read scope.
- Search for the Practitioner by NPI: GET /api/FHIR/R4/Practitioner?identifier=http://hl7.org/fhir/sid/us-npi|{npi_value}
- Extract the Practitioner FHIR id from the Bundle entry.
- Fetch PractitionerRole: GET /api/FHIR/R4/PractitionerRole?practitioner={fhir_id} to retrieve department, specialty, and organization assignments.
Watch out for: NPI search support must be confirmed with the specific Epic instance; not all identifier search parameters are enabled universally.
Deactivate a practitioner record
- Obtain a backend service access token with system/Practitioner.write scope.
- Read the current Practitioner resource: GET /api/FHIR/R4/Practitioner/{id}.
- Modify the 'active' field to false in the retrieved resource body.
- Submit a full resource replacement: PUT /api/FHIR/R4/Practitioner/{id} with the updated resource.
- Verify the response returns HTTP 200 with active=false.
Watch out for: Write access must be explicitly enabled on the Epic instance. Deactivating a FHIR Practitioner record does not automatically revoke Epic application login access - that requires action within Epic's internal security administration.
Paginate through all Practitioners in an organization
- Obtain a backend service access token with system/Practitioner.read scope.
- Issue initial search: GET /api/FHIR/R4/Practitioner?_count=50
- Parse the returned FHIR Bundle; check 'link' array for a relation='next' URL.
- Follow the 'next' URL to retrieve the subsequent page.
- Repeat until no 'next' link is present in the Bundle response.
Watch out for: The 'next' link URL is opaque and instance-specific; do not reconstruct it manually. Default and maximum page sizes vary by Epic instance and are not publicly documented.
Why building this yourself is a trap
The most significant caveat in Epic's FHIR API is scope: it manages clinical identity records, not Epic application access. Deactivating a Practitioner resource via FHIR does not revoke Epic login credentials or security class assignments - those live in Epic's internal EMP/SER records and are only accessible through Hyperspace administration.
Write operations (POST/PUT on Practitioner) are disabled by default on most instances and require explicit configuration by an Epic administrator. Epic does not expose a native SCIM 2.0 endpoint; provisioning into Epic's access model requires HL7 interfaces, Epic's internal Identity Management tooling, or custom middleware.
Rate limits are instance-specific and not publicly documented - contact your Epic technical account manager for production thresholds. SMART on FHIR scopes must be pre-approved at app registration; requesting unapproved scopes at runtime returns an error with no override path.
Automate Epic workflows without one-off scripts
Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.