Internet Explorer is no longer supported. To avoid issues, switch to Chrome, Edge, or Firefox.
  • FHIR
  • API Specifications
  • Build Apps
  • Documentation 
    • App Developer Guidelines
    • App Creation & Request Process Overview
    • App Creation & Request Process Step-by-Step
    • User Context
    • Requesting a Listing in Showroom
    • Designing an Efficient Application
    • OAuth 2.0 Specification
    • FHIR Tutorial
    • App Default FHIR Version
    • ID Types for FHIR APIs
    • Sandbox Test Data
    • Patient-Facing Apps Using FHIR
    • Search Parameters
    • FHIR Bulk Data Access Tutorial
    • CDS Hooks Tutorial
    • Troubleshooting
    • Sex, Gender, & Names
    • Vaccine Credentials
    • International Patient Summary Specification
  • Jump To 
    • Showroom
    • open.epic
    • Open@Epic Conference
  • Login 
    Log in with
    Epic UserWebEpic UserWeb
    or

SMART on FHIR (OAuth 2.0)

*

*

*

*

*

* Indicates required field

Loading...

SMART on FHIR Launch URL

Document not found

That document wasn't found. It may have moved or require you to log in to view.

open.epic Terms of Use

open.epic Terms of Use

  • Privacy Policy
  • open.epic API Subscription Agreement
  • Developer Guide
  • Disclosures

Last updated September 9, 2022

Before using this website or any Materials, please read these terms of use so you understand your responsibilities and Epic’s. You are accepting these terms by using this website or any Materials. Epic may change these terms from time to time - for example, to reflect changes in the Materials we offer or for legal, regulatory, or security reasons. So, please check back periodically for updates. By continuing to use this website or any Materials after an update, you are accepting the updated terms.

Introduction

Open.epic enables Epic Community Members and developers to use the open.epic Components and certain interfaces and other Epic-developed technologies to connect and interact with Epic Community Members’ Epic software. This website includes Materials to use as you develop, test, market, offer, deploy, and support products or services that interact with the technology available through open.epic.

The sections below describe the terms of use for the Materials and this website that apply specifically to Epic Community Members, those that apply specifically to developers, and those that apply to everyone. Capitalized words or phrases used in these terms are defined in the “Definitions” section below.

The interfaces, application programming interfaces (APIs), and other technologies available through open.epic represent a subset of the interoperability technology that Epic offers to support the access, exchange, and use of electronic health information and other data. Other value-added APIs, technologies, and services may be available under different terms through different processes and programs.

For Epic Community Members

If you are an Epic Community Member, your use of Materials related to the open.epic Components is subject to the open.epic API Subscription Agreement.

You may use other Materials available through open.epic at your own discretion, subject to the terms “For Everyone” below and any applicable terms of your agreement(s) with Epic. You are solely responsible for how you use these Materials and this website to interoperate with products and services.

For Developers

If you are not an Epic Community Member, the following terms, and the terms “For Everyone” below, apply to your use of the Materials and this website.

  1. This website is the source of truth for current versions of the Materials. You can keep copies of the Materials for yourself. You may distribute them only by linking others directly to the Materials on the website.

  2. Epic provides the Materials for your use, and you decide how to use them to interoperate with Epic software and provide your products and services to Epic Community Members and end users. With this freedom comes the responsibility to make sure your products and services are behaving appropriately. Accordingly, as between you and Epic, you are solely responsible for your products and services, including how they interact with Epic Community Members’ systems, and for all liability and consequences (e.g., Claims by or on behalf of patients or related to patient harm; data corruption; mapping or saving data to patient records incorrectly; fraudulent or other unethical conduct, such as inappropriately prescribing narcotics or zeroing out a balance due; degraded system response time, performance, and availability; privacy breaches; and security vulnerabilities) that arise from or relate to the use of or inability to use your products or services, or that arise from or relate to data made available to or from your products or services or direct or indirect use of that data. You are also responsible for complying with all applicable laws, including not infringing on Epic’s or others’ intellectual property rights.

  3. You agree to indemnify, hold harmless and defend Epic, its subsidiaries, and all of its and their employees, officers, directors, contractors, and other personnel from and against any Claim that in any way arises out of or relates to the use of or inability to use any of your products or services. This obligation covers, for example, any Claim arising out of or related to:
    1. the accuracy, completeness, integrity, or compliance of your products and services; or
    2. any data made available to or from your products or services, or the direct or indirect use of that data in Epic Community Members’ Epic or downstream systems.

  4. Some interoperability technology made available through open.epic requires an Epic Community Member to have a license to other functionality or build additional workflows. Where possible, please help our mutual customers avoid surprises by working closely with them and directing them to Epic with any questions related to Epic software.

  5. If you want to use open.epic or Epic's name or other trademarks, you may do so only in accordance with our Trademark Usage Guidelines. These terms of use do not authorize you to use an Epic Community Member’s logo or other intellectual property.

  6. Direct access to Epic’s software (including an Epic Community Member’s system) is not needed to develop, test, deploy, or support your product or service. You can test your product or service through the open.epic sandbox or by working with a particular Epic Community Member. Direct access to Epic’s software can be granted only with approval from both an Epic Community Member and Epic.

For Everyone

The following additional terms apply to all users of the Materials and of this website, except that a Community Member’s use of any Materials related to the open.epic Components is instead governed by the open.epic API Subscription Agreement.

  1. THE MATERIALS AND THIS WEBSITE MAY INCLUDE INACCURACIES AND ERRORS, AND YOU USE THEM AT YOUR OWN RISK. THE MATERIALS AND THIS WEBSITE ARE PROVIDED AS IS WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. FOR EXAMPLE, WE DO NOT MAKE ANY PROMISES ABOUT THE CONTENT OR FEATURES OF THE MATERIALS OR THIS WEBSITE, INCLUDING THEIR ACCURACY, RELIABILITY, AVAILABILITY, OR ABILITY TO MEET YOUR NEEDS.

  2. You own what you develop using the Materials. Epic owns the Materials, as well as any Epic-provided improvements to the Materials or learnings based on the Materials, such as enhancements made by Epic to testing tools or documentation. We want to encourage a vibrant developer environment. If you suggest a way to improve the Materials, you are granting Epic the right to use your suggestion in any manner, including as part of the Materials for others to use, and without any obligation or notice to you.

  3. The testing sandbox made available through open.epic is meant to simulate an electronic health record environment and is for testing purposes only. The testing sandbox should be populated only with sample or synthetic data. You agree not to put individually identifiable information in any Epic-provided sandboxes. You acknowledge that Epic may wipe or otherwise remove data from those sandboxes in its sole discretion, including any individually identifiable data discovered in a sandbox.

  4. This website and the Materials are not intended and should not be used for performance, scalability, or security testing. Even so, if you identify an actual or potential security vulnerability with Epic software or the open.epic Components, you will inform Epic about such vulnerability as soon as possible so that Epic can address the issue.

  5. Wisconsin law will govern all disputes arising out of or relating to these terms of use, regardless of conflict of laws rules. These disputes will be resolved exclusively in the federal or state courts of Dane County, Wisconsin, and you and Epic consent to personal jurisdiction in those courts.

  6. These terms of use will be severable so that if a provision is not enforceable, it and related terms will be interpreted to best accomplish the essential purpose of that unenforceable provision. However, severability will not apply if it significantly changes the benefit of these terms to you or Epic.

  7. If these terms of use terminate (e.g., because Epic stops offering the Materials or this website), these terms “For Everyone” and clauses 2 and 3 under the terms ”For Developers” above will survive.

  8. In these terms, the word “include” and its variants are not words of limitation, and examples are for illustration and not limitation.

Definitions

  1. Claim means all claims, demands, investigations, inquiries, and actions, and all liabilities, damages, fines, and expenses arising out of or relating thereto, including settlement costs and attorneys’ fees.

  2. Epic Community Member means a healthcare organization that has a license and support agreement with Epic to use Epic software. For purposes of these terms of use, an Epic Community Member’s affiliated healthcare organizations that use Epic software under its license and support agreement with Epic are considered part of the Epic Community Member.

  3. Materials means resources made available on open.epic to support your use of the open.epic Components and other interoperability technology described on open.epic, including testing sandboxes, documentation, specifications, and other information related to Epic’s implementation of various interfaces and APIs.

  4. open.epic Components means the designated application programming interfaces listed here and other functionality licensed directly by Epic Community Members from Epic under the open.epic API Subscription Agreement to facilitate interoperability.

JWT Authentication Troubleshooting

When developing Backend OAuth 2.0 applications, the issue developers most often run into is a HTTP 400 status code with an invalid_client error, which looks like this:

{
    "error": "invalid_client",
    "error_description": null
}

This error can be caused by a few different underlying issues. A few of the most common causes are listed below.

Issues with the App Listing

  1. Ensure the backend systems consumer type and OAuth 2.0 boxes are checked on the app listing.
    1. In Epic on FHIR, the OAuth 2.0 box is always checked.
  2. Ensure you have uploaded a public key to your environment.
  3. If creating a user/patient facing app that uses JWTs, make sure that “Require Refresh Tokens” is checked, so that you can then upload your public key.

Issues with the Request Body

  1. Ensure in the body of the POST, “client_assertion” is used and NOT “client-assertion”.
  2. Ensure you aren’t double-encoding the “client_assertion_type” parameter’s value of “urn:ietf:params:oauth:client-assertion-type:jwt-bearer”.

Issues with the Token Request Formatting

  1. Ensure the request URL ends in /token and NOT /authorize.
  2. Ensure HTTP POST verb is used, NOT GET.
  3. Ensure the Content-Type of “application/x-www-form-urlencoded” is used.
  4. Ensure the content is in the body of the request, NOT the URL.

Issues with the JWT

  1. Ensure the iat and nbf claims are NOT in the future.
  2. Ensure the exp claim is in the future.
  3. Ensure the exp is no more than 5 minutes in the future.
  4. Ensure the exp is not more than 5 minutes after the iat and nbf claims.
  5. Ensure the app's client ID is in both the iss and sub claims.
  6. Ensure the correct client ID is used. For the Sandbox, non-prod client ID should be used.
  7. Ensure the jti is no longer than 151 characters and contains a unique ID.
  8. Ensure the JWT was not expired at the time of use.

Issues with the Signature

  1. Ensure the private key used to sign the JWT corresponds to the public key registered with the sandbox or Epic customer.
    1. If you re-upload your public key, you need to wait for the changes to sync. This might take up to 60 minutes to sync with the Sandbox, and 12 hours to sync at an Epic organization's Epic instance.
    2. Note the “Sandbox Public Key” on your app is for use against the Epic sandbox. When you are ready to integrate with an Epic customer, you will be asked to upload non-PRD and PRD public keys for each customer.
    3. If your app is cloud-hosted, it is OK to re-use public keys across customers. This assumes you can protect a single copy of the key and are using a different key for non-PRD and PRD.
  2. Ensure the signature algorithm indicated is the algorithm your code cryptographically signs the JWT with.
  3. Ensure the signature algorithm used is supported. RS256 and RS384 are RS384 is preferred.

If you are unable to identify the issue with your request and would like additional assistance, you can request optional technical support from Epic through Vendor Services at an hourly billable rate.

App Developer Guidelines

As an app developer, you are obliged to be familiar with and practice principles for responsible healthcare app development. For any app that uses the Materials, as defined in the Terms of Use, you and your apps should practice the following guidelines. If you have reason to suspect your app is not following the guidelines or is misbehaving and would like Epic to suspend use of your app until the issue is resolved, or if you have any questions about these guidelines, please contact us at open.epic.com/Home/Contact.

1 Guidelines for Well-Behaved Apps

Epic recommends apps be well-behaved, adhering to the following principles:

  1. A well-behaved app does not put patients or users at risk of harm.
  2. A well-behaved app does not create or increase the risk of a security breach in any system to which it connects. The app does not introduce any code of a destructive nature into any system to which the app connects.
  3. A well-behaved app respects the privacy of patients and their families, clinicians, other end users, and anyone else.
  4. A well-behaved app is stable, predictable, and does not negatively impact operations for users or Epic Community Members. A well-behaved app performs as expected at scale and does not generate excessive or unexpected load on a user’s or Epic Community Member’s systems.
  5. A well-behaved app doesn’t corrupt or otherwise cause material inconsistencies in the Epic Community Member’s data and doesn’t cause the Epic Community Member’s other systems to behave inaccurately or unexpectedly.

1.1 Safety Guidelines

The app should reasonably adhere to usability standards that impact patient safety, specifically safety-enhanced design(g)(3) and accessibility-centered design.(g)(5)

If the app allows an end user to search for a patient, the app should collect enough information from the end user to uniquely identify the correct patient or display enough information to the end user to enable the end user to uniquely identify the correct patient.(g)(7)

If the app is launched within an Epic patient workspace for patient-specific workflows, the app should maintain the patient identity within the session. If the app is launched separately for patient-specific workflows, the app should prominently display the patient’s identifying information to avoid any confusion by the end user about which patient’s record she is interacting with.

Because it is possible that your app’s connectivity to an Epic Community Member’s Epic system may be interrupted, you should clearly inform users of your app that it might not always be available to them and that they should not rely on it in an emergency.

1.2 Security Guidelines

The app should encrypt data stored in non-volatile storage using industry-standard encryption algorithms, such as AES-128 or higher. If encryption keys must be written to disk, then the app should make appropriate use of key storage functionality provided by the host platform/operating system and require the administrative user to authenticate to gain access to the encryption keys.

The app should support securing, sending, or receiving data secured with the TLS 1.2 or higher encryption protocol. The app should verify that the server’s TLS certificate is valid and signed by a certificate authorized, recognized, and trusted by the host operating system.

Data exchange between your app and Epic’s APIs and between your app and any other system should be secured with industry standard encryption while in transit,(d)(9) and use authentication and authorization protocols.(d)(1)

Best practice for authentication and authorizing access is to use OAuth 2.0. Use of other authentication mechanisms may be appropriate in limited scenarios but should be considered on a case-by-case basis. Other authentication mechanisms typically do not support fine-grained resource authorization, so that should be considered.

The app should protect authentication credentials provided by Epic or Epic Community Members, such as user names, user passwords, access tokens, or refresh tokens. Credentials used to access Epic Community Member’s Epic systems should be unique to each Epic Community Member, should only be used in conjunction with Epic APIs, and should never be passed to non-Epic systems. If the app needs to persist this data to non-volatile storage, it should use approved secure storage functionality provided by the host platform/operating system.

If the app prompts the user for credentials such as passwords or biometric data, or other sensitive information such as credit card numbers or financial account information, you should take care to protect this data. If the data must be written to non-volatile storage, it should never be stored in clear text. The app should make use of one-way hashing algorithms such as SHA-256 or secure storage functionality provided by the host platform/operating system to persist this data. Your app should secure all data on an end-user’s device(d)(7) (d)(8), and enforce inactivity time-outs.(d)(5)

You should keep your apps’ client identifiers confidential and should not disclose them to any other party or use them for any other purpose. Each of your apps should have its own unique client identifier. Apps that are confidential clients and using refresh tokens should keep their client secret safe.

The app is expected to leverage well-known, established software packages to perform low-level cryptography and authentication operations. Authentication and cryptography software should be actively maintained and have published processes for reporting and responding to security vulnerabilities. Apps are strongly discouraged from implementing custom cryptography or authentication functionality.

You should evaluate your app for security vulnerabilities using resources such as OWASP Top 10 and address any applicable vulnerabilities in your app.

1.3 Privacy and Data Use Guidelines

You should provide users (e.g., the Epic Community Member or end user that uses your app) with a clear and understandable data use or privacy policy that includes an explanation of any data elements that your app obtains from or writes to an Epic Community Member’s Epic system and how and where that data is used. If you or your app do any of the following, then, for each, you should disclose such action to, and obtain appropriate consent from, the user: obtaining data from or writing data to an Epic Community Member's Epic system, retaining data processed by your app (including the length of time for which the data is retained), making any secondary uses of the data, providing any other party or product any access to the data, transferring the data to any other party or product.

A patient-facing app should obtain such consent from end users and provide end users with an option to decline consent or to revoke their consent in the future. A patient-facing app should not circumvent the display of any end user authentication or authorization mechanisms from Epic or any Epic Community Member.

When you use, retain, or distribute data that you obtained from an Epic Community Member’s Epic system, you should document who used, retained, or distributed what data, when they did so, and for what purpose they did so. You should provide users access to that documentation upon request.

You should provide clear and prominent notice to users how they may stop the app from reading, writing, or retaining data.

1.4 Reliability and Scalability Guidelines

Your app should be properly tested and should be stable, predictable, and not negatively impact clinical operations or patient safety. Your app should not generate excessive load on a user’s systems or an Epic Community Member’s systems, or cause other systems to behave inaccurately or unexpectedly. Whenever possible, the app should be tested in a non-production environment to gauge the impact on the Epic Community Member’s systems prior to release in production.

1.5 Data and System Integrity Guidelines

The app should appropriately manage end users’ assumptions and expectations of healthcare software to avoid negative outcomes. If the app permits end users to input data that is typically input and stored in an Epic system, or if the app uses an API to look up data from an Epic system and also permits end users to edit that data, then end users are likely to assume or expect that the app will file the new or updated data to Epic. If the app facilitates part of a workflow, then end users are likely to assume or expect the app to trigger downstream steps in the workflow (e.g., trigger a message to another end user, trigger an interface message, etc.).

You and your apps should not corrupt or otherwise cause material inconsistencies in any data used by your apps.(d)(2)

2 Intellectual Property

Direct access to Epic’s software (including an Epic Community Member’s system) is not required to develop, test, deploy, or support your product or service. You can test your product or service through the open.epic/Epic on FHIR sandbox or by working with a particular Epic Community Member. Direct access to Epic’s software can only be granted by both an Epic Community Member and Epic.

3 Transparency and Honesty

You should not misrepresent products, product capabilities, business relationships, timelines, or anything else related to Epic or open.epic in any way. This includes but is not limited to accurate representation of

  1. Your relationship with Epic or Epic Community Members;
  2. Your status or the status of your app in any Epic process;
  3. Your status or the status of your app in the sales cycle or implementation with any Epic Community Member;
  4. Your apps and their features and functionality;
  5. Your progress designing, developing, testing, or enhancing your apps;
  6. Epic products and their features and functionality;
  7. Similar functionality that Epic’s products have to your products;
  8. Your apps’ and Epic products’ abilities to interface with each other;
  9. Your apps’ total cost to the Epic Community Member or end user, including all license/sales, maintenance, subscription, implementation, training, hardware, and other fees;
  10. The data that your apps access from Epic systems, how you or your apps use that data, any secondary uses that you or your apps make of the data, and any access to that data that you or your apps provide to other parties or products; and
  11. Your apps’ cleared or approved use under any applicable regulations (e.g., FDA clearance).


Footnotes

45 CFR 170.315 (d)(1) (Authentication, Access Control, Authorization): "Verify against a unique identifier(s) (e.g., username or number) that a user seeking access to electronic health information is the one claimed; and [...] establish the type of access to electronic health information a user is permitted based on the unique identifier(s) provided"↩

45 CFR 170.315 (d)(2) (Auditable Events and Tamper-resistance): "The health IT records actions pertaining to electronic health information [...] when health IT is in use; changes to user privileges when health IT is in use; and records the date and time [each action occurs]. [...] The health IT records the audit log status [...] when the audit log status is changed and records the date and time each action occurs. [...] The health IT records the information [...] when the encryption status of locally stored electronic health information on end-user devices is changed and records the date and time each action occurs.”↩

45 CFR 170.315 (d)(5) (Automatic Access Time-out): "Automatically stop user access to health information after a predetermined period of inactivity. [...] Require user authentication in order to resume or regain the access that was stopped."↩

45 CFR 170.315 (d)(7) (End-user Device Encryption): "Technology that is designed to locally store electronic health information on end-user devices must encrypt the electronic health information stored on such devices after use of the technology on those devices stops [or] technology is designed to prevent electronic health information from being locally stored on end-user devices after use of the technology on those devices stops."↩

45 CFR 170.315 (d)(8) (Integrity): "Verify [...] upon receipt of electronically exchanged health information that such information has not been altered."↩

45 CFR 170.315 (d)(9) (Trusted Connection): "Health IT needs to provide a level of trusted connection using either 1) encrypted and integrity message protection or 2) a trusted connection for transport."↩

45 CFR 170.315 (g)(3) (Safety-enhanced Design): "User-centered design processes must be applied to each capability technology."↩

45 CFR 170.315 (g)(5) (Accessibility-centered Design): "The use of a health IT accessibility-centered design standard or law in the development, testing, implementation and maintenance of that capability must be identified."↩

45 CFR 170.315 (g)(7) (Application Access - Patient Selection): " The technology must be able to receive a request with sufficient information to uniquely identify a patient and return an ID or other token that can be used by an application to subsequently execute requests for that patient’s data."↩

45 CFR 170.315 (g)(8) (Application Access - Data Category Request): "Respond to requests for patient data (based on an ID or other token) for each of the individual data categories specified in the Common Clinical Data Set and return the full set of data for that data category (according to the specified standards, where applicable) in a computable format."↩

45 CFR 170.315 (g)(9) (Application Access - All Data Request): "Respond to requests for patient data (based on an ID or other token) for all of the data categories specified in the Common Clinical Data Set at one time and return such data (according to the specified standards, where applicable) in a summary record formatted [...] following the CCD document template."↩

App Creation & Request Process Overview

Table of Contents

  • Overview
  • Creating, Activating, and Licensing an App
    • Registering an App / Provisioning Client IDs
    • Facilitating Epic Community Member Downloads
    • Provisioning Client Secrets
  • Requesting an App
    • Before Starting the Request Process
    • Obtaining the open.epic API Subscription Agreement
    • Downloading Records
    • Confirm the Client ID was Downloaded
  • Deactivate an App

Overview

Epic on FHIR enables Epic community members to download client records for applications registered on the Epic on FHIR website. If the app is in Showroom customers can find the app by doing a keyword search for the app or vendor name. In order to download client records, each Epic community member must have someone with the "Purchase Apps" security point to download an app's client record on behalf of the organization.

The steps below describe the process of:

  • The app registration, creation, and activation process.
  • An Epic community member signing the open.epic API Subscription Agreement.
  • The community member downloading an Epic on FHIR app.

Log in to see step-by-step instructions for using the website to complete these workflows.

Creating, Activating, and Licensing an App

Registering an App / Provisioning Client IDs

When a developer registers an app, the website creates an app record in the Epic database and assigns the app production and non-production client IDs. To register an app, you will need to know:

  1. The app name
  2. The app consumer type
  3. The necessary APIs
  4. The default FHIR version
  5. Any redirect URIs
  6. Whether you plan to use refresh tokens

After the developer has completed development and testing, they can mark their app ready for production use. The app cannot be used in any community member environments, either production or non-production, until the app has been marked ready for production.

Whether or not an app will auto-sync depends on whether it meets all auto-sync criteria. Apps that need to register client secrets or public keys that otherwise meet the auto-sync criteria will sync after a client secret or public key is provisioned to that organization via the "Review & Manage Downloads" button on the Build Apps tab.

The developer cannot update an app record once it has been marked ready for production. If the developer needs to make changes, they must register a new app record.

Facilitating Epic Community Member Downloads

To allow an Epic community member to download an app, simply provide them with the Production or Non-Production Client ID from the app's detail page.

If your app uses backend OAuth 2.0 or refresh tokens, you must also upload production and non-production public keys and/or client secrets. App developers will need to provide this information when the download request is made. Each public key or client secret should be different for each customer and for each environment. Public keys should be exported to a base64 encoded X.509 certificate before being uploaded. Follow the steps in Provisioning Client Secrets to add a client secret when a community member requests the app.

Provisioning Client Secrets

For apps that use refresh tokens, each instance of the application must be assigned a client secret. For apps that qualify for auto-synchronization functionality, a client secret must be set before the app can sync to the Epic community member's environment. For apps that do not meet auto-sync criteria, a client secret can be added once the community member has requested the app.

Epic recommends that you use a unique client secret for each community member and for production versus non-production.

Activating an app, you will see two options: you can either set a client secret, or choose to enable the app without client secrets. If you decide to forgo adding client secrets, refresh tokens will be unavailable for that organization. You can either use a client secret generated by the website or you can provide your own client secret.

Requesting an App

Before Starting the Request Process

Epic community members should do the following prior to the request process:

  • Work closely with their Epic technical coordinator (TC) to research available integrations.
  • Ensure the right Epic products and interfaces are in place to install an app.
  • Evaluate potential costs of the app up front. In addition to developer's fees, consider other third-party software, hardware, and content costs, new interfaces, and additional license or subscription volume that may be triggered by the application.

When an Epic community member has committed to moving forward with an app registered on the Epic on FHIR website, they or the developer should organize a kickoff call to align key stakeholders on the goals, scope, processes, milestones, and timeline of the project. Key stakeholders include representatives from all three organizations: the developer, the Epic community member (e.g., operational sponsor, project manager, analyst, ECSA, network engineer), and Epic (e.g., the technical coordinator, TS who support affected applications, and, if applicable, the EDI representative). If you are not sure who from Epic to include, ask your Epic representative for assistance.

Including the right stakeholders from each organization at the start of the project enhances communication, sets the right expectations upfront, and helps identify and avoid potential issues before they impact the success of the project.

Epic does not endorse, certify, or verify the integrity, safety, performance, or practices of the developers who use Epic on FHIR or their software.

Obtaining the open.epic API Subscription Agreement

Community members who wish to use FHIR APIs with a third-party application registered on the Epic on FHIR website must sign the open.epic API Subscription Agreement. This agreement applies to the organization, not to individual apps, so this step is only required for organizations who have not previously integrated with an application that uses technology licensed under this agreement.

Downloading Client Records

To be able to download a client record, the following steps must have been completed:

  1. The developer has created an account on the Epic on FHIR website,
  2. The developer has registered their product on the Epic on FHIR website,
  3. The developer has marked their product ready for production use ("Active" status), and
  4. The community member has signed the open.epic API Subscription Agreement.

From the Downloads page, community members can see apps which their organization has previously downloaded from the Epic on FHIR website. To download client records for additional apps, the community member's staff with the "Purchase Apps" security point can search using the app’s client ID, which must be obtained from the developer. Either the Production or Non-Production Client ID can be used.

The website will provide details about the app associated with the client ID and give the option to proceed with the download. If the app uses refresh tokens, the duration of the refresh token can also be specified at this time.

Any apps using backend OAuth 2.0 or refresh tokens will need a JWT public key and/or a client secret uploaded to the website after you request a client ID download. This information is provided and uploaded by the app developer.

Confirm the Client ID was Downloaded

To confirm the client record was successfully downloaded, return to the Downloads page. The application should now appear on this page along with a request status. Apps using refresh tokens or backend OAuth 2.0 will require app developer action. All other applications should have their download approved automatically. This download syncs the app's client record to the community member's Epic environments, allowing APIs listed on that client to be authorized by their server.

Epic community members can see the "Notes for Epic Customers" section at the top of the page for details on how to verify that a client record now exists in their Epic environments.

Deactivate an App

If you no longer use an application and want to prevent it from accessing data in the future, you can deactivate an app that has not yet been deployed to a Production environment by clicking the “Deactivate” button. Once an app has been deployed to a Production environment, you must coordinate with your customers to ensure you safely uninstall the application from workflows and have them deactivate the app directly within their environment.

App Creation & Request Process Step-by-Step

Table of Contents

  • Overview
  • Creating, Activating, and Licensing an App
    • Registering an App / Provisioning Client IDs
    • Recommendations for Creating your App Listing
    • Facilitating Epic Community Member Downloads
    • Provisioning Client Secrets
  • Requesting an App
    • Before Starting the Request Process
    • Obtaining the open.epic API Subscription Agreement
    • Downloading Client Records
    • Confirm the Client ID was Downloaded

Overview

Epic on FHIR enables Epic community members to download client records for applications registered on the Epic on FHIR website. In order to download client records, each Epic community member must have someone with the "Able to Purchase Apps?" security point to download an app's client record on behalf of the organization.

The steps below walk through the process of:

  • The app registration, creation, and activation process.
  • An Epic community member signing the open.epic API Subscription Agreement.
  • The community member downloading an Epic on FHIR app.

Creating, Activating, and Licensing an App

Registering an App / Provisioning Client IDs

When a developer registers an app, the website creates an app record in the Epic database and assigns the app production and non-production client IDs. The steps for a user to register an app are:

  1. Navigate to the Build Apps page
  2. Select "Create My First App”
  3. Complete the Create an App form
  4. Save

After the developer has completed development and testing, they can mark their app ready for production use. The app cannot be used in any community member environments, either production or non-production, until the app has been marked ready for production. The steps for a user to activate an app are:

  1. Navigate to the Build Apps page
  2. Select the app that will be activated
  3. Finalize details about the app
  4. Check the box to confirm compliance with the Terms and Conditions
  5. Save and mark ready

Whether or not an app will auto-sync depends on whether it meets all auto-sync criteria. Apps that need to register client secrets or public keys that otherwise meet the auto-sync criteria will sync after a client secret or public key is provisioned to that organization via the "Review & Manage Downloads" button on the Build Apps tab.

The developer cannot update an app record once it has been marked ready for production. If the developer needs to make changes, they must register a new app record.

Recommendations for Creating your App Listing

When you first create your app, customers can search for the app by client ID on the Epic on FHIR Download page and view your application name, summary, and thumbnail. If you choose to list your app on Connection Hub once you have your first live customer, there will be additional marketing fields that you may choose to fill out, which are described below.

Application Name

This is the name of your product. Keep your app name consistent throughout your app listing. Don’t include “Epic” in your app name. Don’t include any trademarks from other organizations without permission.

Public Documentation URL

This should be a public web page about your product. If you mention Epic in your public documentation, be sure to follow our Trademark Usage Guidelines.

Summary

The best summaries are 2-3 sentences and provide a high-level explanation of the product. The summary is meant to catch the reader’s interest, so avoid including too much detail here. You can go into more detail in the description.

The summary can be no longer than 500 characters, including spaces.

Description

The description should give the reader a clear understanding of what the product does and how it integrates with Epic. The best descriptions follow a Why, What, How format – Why a customer should want to use this product, What the product does, and How the product integrates with Epic. Consider splitting your description into multiple short paragraphs or using bullet points to improve readability.

The description can be no longer than 1,998 characters (including spaces).

Data Use Questionnaire

We highly encourage developers to fill out the Data Use Questionnaire if the app is Patient-facing to benefit mutual Customers of Developers and Epic. The answers you provide to the Data Use Questionnaire are displayed to a patient during the app authorization flow, The questionnaire responses can help the patient make an informed decision about whether they want to grant your app access to their data based on the data use practices of the app. Failure to answer the DUQ will result in a warning being shown to the patient when they are prompted to authorize your app.

Thumbnail

This should be the logo for your organization or product. The recommended image dimensions are 500 x 300.

Screenshots

This is your opportunity to show viewers what your app looks like and how it works. Consider adding captions to your images to explain the main purpose of the image. If your product does not have a user interface, consider including a data flow diagram describing your integration.

The screenshot height is 1080 pixels, and the recommended dimensions are 1920 x 1080. You can include up to 10 screenshots.

Facilitating Epic Community Member Downloads

To allow an Epic community member to download an app, simply provide them with the Production or Non-Production Client ID from the app’s detail page.

If your app uses backend OAuth 2.0 or refresh tokens, you must also upload production and non-production public keys and/or client secrets. App developers will need to provide this information when the download request is made by navigating to Build Apps page and selecting "Review & Manage Downloads" for the appropriate app. Each public key or client secret should be different for each customer and for each environment. Public keys should be exported to a base64 encoded X.509 certificate before being uploaded. Follow the steps in Provisioning Client Secrets to add a client secret when a community member requests the app.

Provisioning Client Secrets

For apps that use refresh tokens, each instance of the application must be assigned a client secret. For apps that qualify for auto-synchronization functionality, a client secret must be set before the app can sync to the Epic community member's environment. For apps that do not meet auto-sync criteria, a client secret can be added once the community member has requested the app.

Epic recommends that you use a unique client secret for each community member and for production versus non-production.

After marking an auto-sync app as Ready for Production or after a download request for an app that does not auto-sync, follow these steps to assign a client secret for a community member:

  1. Go to your Build Apps page and locate the app for which you want to assign a client secret. On that app, select "Review & Manage Downloads"

  2. You should see a list of app requests with a status of "Not responded" indicating that action is needed before the app can be downloaded to the community member's environments. Select the key icon next to the organization for which you'd like to set a client secret.
    • Note that these app requests may not appear immediately after an app is marked as Ready for Production. These requests typically appear within 5 minutes, but please allow an hour for these requests to appear as a high volume of web traffic may increase the normal response times.

  3. You will see two options: you can either set a client secret, or choose to enable the app without client secrets. If you decide to forgo adding client secrets, refresh tokens will be unavailable for that organization. Select "Get Secrets". Suggested client secrets for use with this organization are generated. If you'd like to use the auto-generated secrets, save these client secrets off in a secure location. Alternately, you can enter your own client secrets.

  4. Once you have your client secrets saved off, click "Enable."

  5. You will be returned to the app management window, where the status for that organization will now show as "Keys enabled."

Requesting An App

Before Starting the Request Process

Epic community members should do the following prior to the request process:

  • Work closely with their Epic technical coordinator (TC) to research available integrations.
  • Ensure the right Epic products and interfaces are in place to install an app.
  • Evaluate potential costs of the app up front. In addition to developer's fees, consider other third-party software, hardware, and content costs, new interfaces, and additional license or subscription volume that may be triggered by the application.

When an Epic community member has committed to moving forward with an app registered on the Epic on FHIR website, they or the developer should organize a kickoff call to align key stakeholders on the goals, scope, processes, milestones, and timeline of the project. Key stakeholders include representatives from all three organizations: the developer, the Epic community member (e.g., operational sponsor, project manager, analyst, ECSA, network engineer), and Epic (e.g., the technical coordinator, TS who support affected applications, and, if applicable, the EDI representative). If you are not sure who from Epic to include, ask your Epic representative for assistance.

Including the right stakeholders from each organization at the start of the project enhances communication, sets the right expectations upfront, and helps identify and avoid potential issues before they impact the success of the project.

Epic does not endorse, certify, or verify the integrity, safety, performance, or practices of the developers who use Epic on FHIR or their software.

Obtaining the open.epic API Subscription Agreement

Community members who wish to use FHIR APIs with a third-party application registered on the Epic on FHIR website must sign the open.epic API Subscription Agreement. This agreement applies to the organization, not to individual apps, so this step is only required for organizations who have not previously integrated with an application that uses technology licensed under this agreement.

Downloading Client Records

To be able to download a client record, the following steps must have been completed:

  1. The developer has created an account on the Epic on FHIR website,
  2. The developer has registered their product on the Epic on FHIR website,
  3. The developer has marked their product ready for production use ("Active" status), and
  4. The community member has signed the open.epic API Subscription Agreement.

From the Downloads page, community members can see apps which their organization has previously downloaded from the Epic on FHIR website. To download client records for additional apps, the community member's staff with the "Purchase Apps" security point can search using the app’s client ID, which must be obtained from the developer. Either the Production or Non-Production Client ID can be used.

The website will provide details about the app associated with the client ID and give the option to proceed with the download. If the app uses refresh tokens, the duration of the refresh token can also be specified at this time.

Any apps using backend OAuth 2.0 or refresh tokens will need a JWT public key and/or a client secret uploaded to the website after you request a client ID download. This information is provided and uploaded by the app developer.

Confirm the Client ID was Downloaded

To confirm the client ID was successfully downloaded, return to the Downloads page on the Epic on FHIR website. The application should now appear on this page along with a request status. Apps using refresh tokens or backend OAuth 2.0 will require app developer action. All other applications should have their download approved automatically. This download syncs the app's client record to your organization's environments, allowing APIs listed on that client to be authorized by your server.

See the "Notes for Epic Customers" section at the top of the page for details on how to verify that a client record now exists in your Epic environments.

User Context

Application User Context

Epic defines three different user contexts for an application:

  1. Patients
  2. Clinicians, Staff, or Administrative Users*
  3. Backend Systems

*The "Clinicians, Staff, or Administrative Users" context is commonly referred to as "Provider" context, so the rest of this tutorial uses that term.

Choosing a User Context

Most applications have an end user who is interacting with them, and these users determine the user context of your application. To choose the appropriate user context, consult this table:

Primary User Type User Has Epic Access User Context
Patient Yes Patient
Patient No Backend System
Provider Yes Clinicians, Staff, or Administrative Users
Provider No Backend System
Background Process with No User Interaction N/A Backend System

Multi-Context Applications

Your application might not fit neatly into one user context, and in that case it could be a good fit for a multi-context application. With multi-context applications, you register separate apps or clients with Epic, one for each user context you support (typically no more than three).

Limit APIs to the Required Application

When building a multi-context application, an app that has multiple Client IDs, you might have overlap with which APIs are called by each client. For example, both a provider-facing and backend application might need to search for patients with the same API. While this search can be valid, you should in general only include the APIs necessary on a specific application. We recommend this for a few reasons:

  1. On the principle of least access, you should only request access to precisely what your workflow requires, and no more.
  2. You decrease the work needed to install your application. By precisely defining the APIs you need, you are also limiting the security that Epic community member's need to enable for your application to function.

For example, if your backend component searches for patients, but your provider-facing component does not, then you should only add the patient-searching API to your backend component.

Limit Use of Backend Apps

Whenever possible, your application should avoid using a backend component so that Epic can conduct end-user specific security checks during your API calls. By using a backend component, rather than a patient or provider-facing component, those security checks are run on a background user rather than the specific user interacting with your app.

This means your application should always use the OAuth 2.0 token of the user who authorized the application when one is available. The exception is for specific APIs that are not supported for the user context of your end users. It might be appropriate to include a backend component to call these APIs, but ensure you have robust security within your application to control how end users can interact with these APIs from your system. Your security policies should include:

  • Never sharing access tokens or authentication credentials with your end user's device or browser. This way, a malicious end user cannot impersonate your application. Your backend server should be the only entity making API calls.
  • Having your own authentication and session management systems. For most use cases, only authenticated end users should be able to trigger APIs.
  • Validating all end-user input before forwarding the input to an API.

Use Cases for Backend Apps

Below is a list of common use cases for backend applications:

  • Book Anywhere integrations
  • Printing and faxing
  • Remote patient monitoring (RPM, patient unauthenticated)
  • Personnel management
  • Patient Integrated Voice Response (IVR)
  • Public dashboards
  • Patient chatbot (patient unauthenticated)

Creating a Multi-Context App with Epic on FHIR

To support a multi-context app, you will need to create separate applications for each intended context. This provides you with a set of production and non-production client identifiers for you to use with each context of your app. Each application needs to include the APIs that will be used in that specific context. For example, if there is an API that will be used in the provider context, but not in the patient context, list that API on only the provider context application record.

Use consistent names across multi-context application records. Names followed by their context can be useful to show which client IDs are associated with which contexts. For example, if "OurNewHealthApp" is the name of the multi-context integration, "OurNewHealthApp - Provider" could be the name of the application record designed for the provider context app.

Epic Community Member Downloads

To allow an Epic community member to download your multi-context app, you will need to provide them with the Production or Non-Production Client IDs for each of the applications that together support the full multi-context integration.

API Supported User Types

Overview

Epic has determined that some APIs are not appropriate to be called directly by end users or patients. For example, an API used as part of an administrative user's workflow is likely not appropriate for a patient-facing application because a patient should not be interacting with that data.

To reflect these use cases, Epic has assigned a list of supported user types to each Epic API and introduced validation for user type on all API calls.

User Type Validation

Starting in the May 2022 version of Epic, Epic community members can enable user type validation for their APIs that checks the user context of the application against the supported user types of the API being called. If the context of the user calling the API does not match the API's supported user types, then the API call fails.

When Does an API Support a Given User Type?

An API can support a combination of three possible user types, and when an API supports a given user type, it means the following:

  1. Patients: This API is appropriate to call directly based on input from Patients, and its output can be shown directly to the authorized patient.
  2. Provider: This API is appropriate to call directly based on input from Provider users, and its output can be shown directly to the authorized provider.
  3. Backend Systems: This API can be called by a backend system, and when called from a backend application, it must have its input and output validated rather than letting end users interact with it directly.

API and User Type in Your Application

If your application uses OAuth 2.0, then the API user type needs to match the user context you chose when building the application.

If your application uses an alternative authentication method (such as basic authentication), Epic cannot authenticate against a specific user regardless of the app's selected context. These API calls are therefore treated as a Backend System user type even if the app's user context is Patient or Provider.

Should I Edit My App's User Context?

If an API does not support your app's user context, first validate that your chosen user context matches your use case using the table above. If you can appropriately switch to a supported context, then update your app.

If it is not appropriate to switch the context of your app, another option is to create a multi-context application. For most applications, this means provisioning an additional backend component that interacts with as few APIs as needed.

Additional User Security Validation

Epic's user context validation is just one step in our API security checks. This means that a request might pass context validation but fail at a later step. For example, Epic assesses the security of the individual user associated with a request. If a user does not have the correct security within Epic, the call fails, and the Epic community member needs to assess the use case and update the user's security as appropriate.

Refer to our troubleshooting tutorial for help assessing why a given API call has failed.

Requesting a Listing in Showroom

Connection Hub

Connection Hub is open to any application or integration using mature, repeatable technologies by attesting to having a live connection to an Epic system. A Connection Hub listing offers you the ability to publicly share information about your product on Showroom.

All steps below assume that you have already logged in to your user account. If you do not yet have an Epic on FHIR account, complete registration by clicking "Sign Up" on the top right corner of the website.

  1. An app record is required to represent your connection to Epic. If your organization already has a distributable app record on the website, proceed to step 2. If you need more information on creating your app, see our App Creation and Request Process document. Remember to fill out the Summary and Thumbnail as these fields will be publicly visible.
    • HL7 or Interfaces-only apps: Your application may already by live with Epic customers and using technologies to exchange data (e.g., HL7 interfaces) without ever having created an app record. In this case, we recommend only selecting the "Backend Systems" user type. Additionally, in the Description field, briefly explain the technologies your app uses to exchange data.
  2. After your product has a live, active connection to a customer’s Epic system, click on your app from the My Apps page.
  3. Scroll to the bottom of your app's App Information page and click the Request Connection Hub Listing button in the bottom right corner of the App Information page.
  4. Review and accept the Connection Hub Terms of Use.
  5. Within 15 days, your organization's point of contact should hear back about payment for the listing fee before finalizing your listing.

Toolbox

Showroom is Epic's website where anyone can discover services and software that integrates with Epic. Within Showroom, Toolbox is a designation for products that use established integration technology in select categories to meet Epic defined recommended connection practices for that area. It's an inclusive program where many vendors who make a product in an eligible category can participate. The selected categories, the design requirements, and products in the categories will be evaluated annually to maintain focus on the most impactful and innovative areas for our customers.

Toolbox Categories

The selection of Toolbox categories is based on a number of criteria including but not limited to: interest from customers; technical feasibility; potential of additional services creating positive outcomes for customers and their patients; and availability of Epic resources to provide services, among others. Epic regularly evaluates categories for Toolbox eligibility.

The list below represents the current Toolbox categories.

 Category Name  Category Definition
Acute Virtual Care Acute Virtual Care products are in-room cameras used to monitor admitted patients virtually, enabling and supporting virtual nursing, continuous remote monitoring, and teleconsult workflows for admitted patients. The clinician-facing video feed from these cameras can be viewed via an external web application or embedded within Epic Monitor. The patient-facing video feed can be viewed via an external web application or embedded within MyChart Bedside TV. These products may use computer vision-based detection on the in-room video feed to monitor patients for fall risk or other adverse events.
Ambient Voice Recognition: Outpatient and ED Notes Ambient Voice Recognition (AVR): Outpatient and ED Notes apps integrate with Epic’s Ambient Module for ambulatory and emergency encounters. Clinicians record their clinical encounters from beginning to end in Haiku, verbalizing their evaluative process throughout. When they finish recording, the audio is processed by a third-party ambient listening vendor. The vendor processes the conversation and converts it into a note. The documentation is then sent to Hyperspace for the clinician to review and sign. With integration in this area, clinicians can expect to spend less time writing notes, provide more attention to patients during visits, and have reduced after-hours time.
Anesthesia Infusion Injectable Hardware Anesthesia infusion injectable hardware integrations support a hardware device that manages manual IV bolus injections by identifying medication and concentration, performing allergy checks, measuring doses, and automatically documenting the process in real-time in Epic's Anesthesia application. This integration enhances workflow efficiency and accuracy, increasing clinician focus on patient care and reducing manual documentation.
Automated Dispensing System Automated Dispensing Systems (ADS) manage and include computerized devices that store, dispense, and track medications at the place of service. ADS commonly receive patient and order information to determine what medications are available for dispensing and send dispense information back to Epic when medications are removed from the ADS.
Bayesian Medication Dosing Decision Support A third-party decision support system used to provide personalized medication dosing recommendations. These products launch from Hyperspace using SMART on FHIR, pull patient and medication information, and provide the end user with optimized medication dosing recommendations. Bayesian analysis is commonly used for medications with narrow therapeutic windows or medications requiring precise dosing based on patient factors.
Bedside Dining Bedside Dining applications present patients dining menus and options to place dining orders within MyChart Bedside.
Bedside TV Hardware To provide a TV-centric experience in hospitals, vendors need to support hardware-level features such as deploying and app/OS updating, making Bedside TV the main screen, and facilitating seamless video calls using in-room cameras.
Care Companion Care Plan Content Apps that create signed XML documents used to import MyChart Care Companion care plans into customer environments. These care plans deliver notifications, analyze data provided by patients and connected devices, and orchestrate changes to the plan and escalations as needed to help patients, their caregivers, and care managers stay on top of a patient's care. A care plan comprises modules of patient-assigned tasks, which are delivered to patients as periodic reminders. These tasks could be reading education materials, responding to questionnaires, performing periodic symptom check-ins, or measuring weight, height, or blood pressure for example.
Clinical Knowledge Set The Clinical Knowledge Set integration is based on the HL7 Infobutton standard, which allows clinicians to retrieve targeted information provided by third parties, specific to the context of the patient or clinical workflow. Rather than searching third party knowledge base, searches are performed automatically based off elements of the patient's chart like diagnoses or orders.
Community Resource Network (CRN) Community Resource Network integrations support closed loop referrals from Epic community members to other organizations on the health grid, like post-acute facilities, community resources, DME suppliers, and more. Case Managers can find availability for their patients nearing discharge, and send information via vendors to post-acute facilities who can accept or decline placement based on the patients’ needs and history, as well as capacity and provider availability. Similarly, clinicians who identify Social Drivers of Health impacting patients can refer them to community organizations who can help address patients’ needs, all without leaving their workflows within Epic.
Computer Telephony Integration (CTI) Computer telephony integration (CTI) apps allow Epic users to make and receive telephone calls in Hyperspace. Incoming calls bring the user to the activity in Epic that matches the purpose of the call and with the caller identified. Outgoing calls allow the user to initiate a call in the phone system to a selected phone number with a single click.
Digital Pathology Digital Pathology is an integration between Beaker (Epic's laboratory application) and a 3rd party Image Management System (IMS) to allow pathologists to gain access via a hyperlink to high-resolution, digitized images of case slides for examination and interpretation. The hyperlink can be set up as one link for the overall case with all the associated slide images (Case-level) and/or as multiple links to the individual slide images (Slide-level).
Embedded Payment Processor The embedded payment processor integration (often referred to as the external payment page or EPP) allows MyChart websites, Welcome kiosks and tablets, and/or Hyperspace users to collect credit card and bank account payments in real time, while helping comply with standards to safely handle payment information. This is done by embedding a gateway’s URL as a payment page in Epic. The gateway’s website handles the collection of the payment directly and posts non-sensitive transaction information back to Epic.
Encoder Encoder integrations involve using a third-party system to determine and file diagnosis, procedure, and DRG codes based on clinical documentation. Coders or CDS users launch from Epic into the encoder, which interfaces with Epic to ensure accurate coding and data storage. Encoders can integrate for facility coding workflows and/or for Clinical Documentation Improvement (CDI) workflows.
Fully Autonomous Coding Fully Autonomous Coding applications utilize AI and machine learning to assign billing codes to patient charges or accounts. Fully Autonomous Coding apps receive patient information, clinical documentation, result and charge details from various outgoing interfaces and APIs. Without user intervention, the Fully Autonomous Coding app processes this information, updated charges are sent back to the EHR via an incoming financial transactions (DFT) interface and filed to Professional Billing or Hospital Billing. If the flow cannot be completed autonomously, feedback should be sent to Epic allowing the coder to complete the review in Epic.
Health Information Service Provider Using Care Everywhere and communication with other third-party systems, Health Information Service Providers (HISPs) play a crucial role in enabling seamless communication for users within Epic applications by providing secure message routing and enabling provider directory exchanges.
Hemodynamics System Hemodynamic Monitoring Systems are used in Cardiac Cath Labs, Cardiac EP Labs, and some Interventional Radiology Labs to monitor patients during invasive procedures. These systems monitor the patient’s vitals and pressures associated with specific cardiac structures. Epic’s procedure log is used in conjunction with the existing hemodynamic monitoring system, and the hemodynamic system writes relevant data into Epic’s procedure log to keep all the data integrated with the patient’s chart in Epic.
Home Dialysis Home dialysis machines are used by patients to administer hemodialysis and peritoneal dialysis at home. These machines also collect clinical data such as vitals that are written to device-entered or patient-entered flowsheets in Epic, maintaining continuity of the patient’s chart throughout treatment.
Hospital Utilization Review Creation Hospital Utilization Review Creation apps provide guidelines for treatment of various medical conditions and the associated reimbursement. Apps in this category integrate with the Utilization Review activity (Clinical Case Management). Utilization managers launch the application and document a patient’s symptoms, events, and treatments to see if the patient meets sufficient criteria for admission or discharge and what level of care is needed in the hospital. Afterwards, the app will send the criteria and notes added by the case manager into the Utilization Review activity in Epic.
Identity Verification for MyChart This Toolbox category definition is Under Construction with expected future renovations. An Identity Verification for MyChart application uses the specification in MyChart self-signup workflows to integrate with reusable identity platforms. This functionality aims to allow users to assert their identity and complete MyChart signup, regardless of their MyChart account status or existing patient record. Additionally, this integration will be used during account recovery to help patients who cannot complete recovery through traditional 2FA methods.
Imaging Appropriate Use Criteria Imaging Appropriate Use Criteria (AUC) apps provide decision support for appropriateness of imaging procedures given clinical indications. When imaging orders are placed in Epic, AUC apps are notified and given some clinical context to determine the appropriateness of the order. The decision support outcome may file directly in an Epic OurPractice (OPA, formerly BPA) response, while other times, the app’s UI is shown to request additional information.
Implantable Cardiac Devices Implantable cardiac device monitoring systems are used to streamline cardiac device data workflows, support informed clinical decisions, and deliver efficient, comprehensive care. The IHE Implantable Device – Cardiac Observations (IDCO) profile is used to communicate data from an implantable cardiac device (such as a pacemaker or ICD) with Epic and allows that data to be incorporated into the patient chart, helping ensure the legal medical record contains complete, accurate data. Device interrogations can happen in-clinic during a scheduled appointment or remotely where the patient initiates a device check from home and sends them in through the device vendor’s system.
Infusion Pumps Bi-directional infusion pump integration allows clinicians to send order details from Epic to auto-program the pump, and then receive documentation information from the pump, such as rate changes and volumes, to file into the patient’s chart.
Interpretation Services Integration An integrated telehealth interpretation services application uses the native video visit interpretation configuration available in Epic to allow a clinician or staff member to send an invitation to a remote interpretation service for a specified language. The remote interpreter will join via the health system’s integrated telehealth provider to conduct video or audio remote interpretation between the patient and the treatment team. Requests and connection events will be audited (via the integrated telehealth platform) in the customer’s environment.
Medication Ordering Decision Support A third-party decision support system used to suggest medication changes. These products launch from Hyperspace using CDS Hooks to pull medication order information and provide the end user with medication ordering recommendations. The ordering decision support provided should augment the customer's existing medication warning configuration by evaluating patient-specific information to provide a more tailored warning.
Oncology Clinical Pathways Decision Support A third-party decision support system used to suggest oncology treatment protocols. These products launch from Beacon’s Treatment Plan navigator, embedded within Hyperspace, and post the user’s selected oncology protocol back to Epic.
Outbound Emergency Medical Services Transportation Ordering Emergency Medical Services (EMS) Transportation Ordering applications enable hospital staff to order EMS-based transportation services for patients requiring additional medical care during transportation, commonly between medical facilities, by integrating Epic with Computer Aided Dispatch (CAD) systems. These integrations center around outbound transportation from the ordering location. Companies offering these applications may provide transportation services themselves or facilitate integration with transportation services employed directly by the hospital system.
Outpatient CDI (Risk Adjustment) This Toolbox category definition is Under Construction with expected future renovations. This functionality integrates risk adjustment recommendations from third-party systems directly into Epic's pre-visit and point-of-care workflows, facilitating comprehensive HCC reviews in a singular decision support tool. CDI staff can review suggestions during pre-visit workflows and tee them up for providers to review during point-of-care workflows, without needing to navigate to other systems or disparate decision support advisories. Supplemental data explaining the suggestions and evidence will be displayed to end users, and providers' actions will be sent back to the third-party systems, creating a feedback loop with vendors that was not previously available.
Outpatient Pharmacy Patient Locker The Patient Locker integration for Outpatient Pharmacy is for third-party systems that store pre-filled prescriptions in a locker for patient pick-up. Pharmacists/technicians complete their usual workflow to fill prescribed medications, but rather than waiting for patients to come to the pharmacy and pick up the prescription in person the medication is stored in a patient locker. Once patients know their prescription is ready to be picked up they travel to the locker and the system is able to collect necessary patient identification, dispense the correct medication, and offer counseling services if required.
Outpatient Pharmacy Will-Call The Will-Call integration for Outpatient Pharmacy is for third-party systems that improve the organization of prescribed fills waiting to be picked up. When the prescriptions are filled, they are stored in bags, bins, etc. such that an indicator (e.g., a light) can call attention to the prescription location. This system allows pharmacists, technicians, or other users to easily find the prescription a patient at the counter has come to pick up.
Payer Medical Necessity Guideline Creation Payer Medical Necessity Guideline Creation apps provide care guidelines for treatment of various medical conditions for authorization review. Apps in this category integrate with the Guideline Review activity (Tapestry Utilization Management).
Payment Device Integrations The Payment Device Integration allows payments to be collected in real time. This is handled by outgoing requests sent to your endpoint during a payment workflow. The first request checks whether the device is initialized while the second request is needed for a payment transaction response. The payment is processed outside of Epic and through the payment processor.
Point-of-care Middleware Integrations Point-of-care Middleware Integrations are middleware that connect lab hardware to Epic to facilitate receiving results from point-of-care devices into Beaker.
Printing Output Management Integrations Output Management applications integrate with Epic Print Services (EPS) to collect the jobs exiting from the service that are bound for physical printer devices, which would normally be done through Windows print queues
Procedural Consent Content Import This Toolbox category definition is Under Construction with expected future renovations. A clinical consents content vendor provides consent content such as procedure-specific risks, benefits and alternatives that is imported into customer environments. The content can then be pulled into consents created in Epic by clinical staff, spanning many clinical workflows and specialties.
Real Time Location Systems A Real-Time Location System (RTLS) is used within the healthcare industry to provide real-time visibility into where patients, staff, and assets are located within their facilities. RTLS uses a tag that gets assigned to the entity (patient/staff/asset) they want to locate and a sensory network that’s deployed within the facility to determine location of the tag. Epic integrates with RTLS leveraging an HL7v3 interface to communicate incoming patient, staff, and medical device locations, and tag associations. In Epic, the RTLS data is used to provide visibility and automate workflows.
Remote Patient Monitoring with Devices RPM with devices allows providers to monitor and manage their patients’ chronic conditions. Devices that are distributed to a patient gather patient-generated health data such as blood pressure, heart rate, or weight. Data is sent back to Epic from the device. These products require patients to be explicitly enrolled in an RPM episode in Epic. Clinicians review the data in Hyperspace and take appropriate follow-up actions.
Standard Lab Middleware Integrations Standard Lab Middleware Integrations are middleware that connect lab hardware to Epic to facilitate receiving results from lab instruments into Beaker.
Vitals Gateway and Middleware Device Integration Vendors in this category receive vitals data from devices like bedside monitors or anesthesia carts. Vendors support either a Spot Check Vitals integration, a Continuous Vitals integration, or both. Spot Check Vitals devices receive discrete data that is validated on the device and then sent to EpicCare Inpatient Clinical Documentation. Continuous Vitals devices receive a constant stream of data which is then sent to EpicCare Inpatient Clinical Documentation or Epic’s Anesthesia application.
Waveform Visualization Waveform visualization apps display waveform data in near real time via a web and/or mobile app that launches from Epic.
Wayfinding Wayfinding applications using a patient’s appointment information, such as encounter department, to help users navigate healthcare organizations.

Request a Blueprint and Evaluation

You may request the Blueprint for any of the Toolbox categories above by submitting a request on open.epic.com. Participation in Toolbox is at Epic’s discretion and requesting Toolbox services requires enrollment in Vendor Services. If you decide to proceed, you will share information about your existing or planned product with Epic to determine the next steps. Apps in a Toolbox-eligible category developed to meet the corresponding Blueprint must complete Toolbox Readiness to receive Toolbox designation in Showroom.

Workshop

Workshop features products from developers who are working together with Epic to bring new, innovative technology and ideas to healthcare. Workshop participation is offered by Epic to a few vendors in specific categories to pilot and mature technology that will later be made more broadly available.

Rev Cyclers Program

The Rev Cyclers Program is intended to help customers and vendors optimize their use of Epic software to improve their revenue cycle outcomes. Vendors receive customer-level access to Epic tools, materials, and other benefits.

The vendors we choose to invite to the Rev Cyclers Program are driven by various criteria including customer feedback, scope of revenue cycle outsourcing work, and more. This is a new program. As such, we are iterating with a couple early adopters and plan to expand our group of vendors at a later time. If you have any questions about the program, please reach out to us at ConsultantInquiries@epic.com.

Please note that vendors can work with our customers regardless of if they participate in the program. For additional information on ways to interoperate with Epic customers, please visit open.epic where you can find specifications for hundreds of FHIR APIs and other resources to exchange data with members of the Epic community. Additionally, you can reach out to our Consultant Relations team if you have any questions on acquiring system access to support our customers.

Designing an Efficient Application

App developers have a responsibility to create apps that perform well at scale and do not create an excessive or unexpected load on a customer's system. Be mindful that high API volumes from your app could have a negative outcome for a customer's users.

Performance issues in a customer's production environment are often caused by web service applications generating either (1) extremely high query volumes or (2) moderate volumes of resource-intensive queries.

To avoid these issues, follow our recommended practices below.

Recommended Practices

Your app should be stable and predictable and should not negatively impact operations for users or healthcare organizations. Follow the best practices below to develop an application that runs efficiently.

For apps launched from Hyperspace, aim for an average initial load response time of under 1.5 seconds.

This is a best practice Epic applications also follow. Delays beyond 1.5 seconds will be noticeable to end users and may result in a poor user experience. If your app reads a large amount of data from Epic upon launching the app, this could result in a slower response time. Where appropriate, consider either requesting data ahead of the app launch or queuing data to be loaded after the app is made responsive to the end user so the user is not waiting for the app to load.

Use the right tool for your use case.

Epic offers many different technologies that can be used to exchange data and integrate products. Depending on your use case, certain types of technologies may be more efficient than others. Consider the examples below and keep in mind when you need the data your app pulls from Epic and how frequently you need to pull data.

Event-driven Interfaces

One of the best tools available to you for keeping systems in sync in real time is event-driven interfaces, most commonly HL7v2 interfaces. HL7v2 interfaces are open industry-standard technology that cover a range of data types and triggers that are built for system-to-system integrations where two systems need to stay in-sync about particular types of data over time. Compared to continuously polling APIs, interfaces significantly reduce load on the operational database as the work is only being done at the time of the event.

Example: Your app needs to maintain a list of currently admitted patients. You may use the Outgoing Patient Administration HL7v2 interface to receive updates when a patient is admitted or discharged.

CDS Hooks

For clinical decision support apps, consider CDS Hooks as a way to get a notification of a user workflow and the ability to respond with CDS content in real time. Similar to event-driven interfaces, CDS Hooks allows your app to only pull data at the time of a particular event (ex. placing a specific order) when your app is able to make an actionable suggestion to the end user.

Note that CDS Hooks should only be used to display an actionable alert to the end user. It is not intended to be used exclusively as a notification framework. If your app only needs a notification, see Event-driven Interfaces above.

Example: Your app evaluates medication orders and recommends more appropriate options and dosages. You may use CDS Hooks to trigger an alert when certain medication orders are placed.

RESTful APIs

RESTful APIs, such as FHIR APIs, are very useful for pulling specific data points about individual patients in real time. Apps that launch from Epic with patient context commonly use FHIR APIs to retrieve various types of information about the current patient, including demographics, diagnoses, current medications, and more.

Example: Your app launches from Epic and needs to retrieve information about the current patient’s wounds. You might get the patient’s FHIR ID through a SMART on FHIR launch and then use Observation.Search (LDA-W) to query for that patient’s active wounds in real time when the app is launched.

Only pull the data you need

You should avoid pulling extraneous data wherever possible, as this can put unnecessary strain on your customer’s system. Use search parameters to filter your search to only the necessary data. For example, if you are using FHIR Search APIs, you can use the date and time parameters to avoid pulling extra data. Use discrete APIs rather than CDA documents since they provide targeted data sets that are more performant for the consuming and producing systems.

Native vs. Post-Filter Search Parameters

R4 FHIR APIs may support both native and post-filter search parameters. Native search parameters narrow down the search performed by Epic, therefore reducing the performance impact on a customer system. Post-filter parameters, however, are applied to search results after the initial search has been performed using the native search parameters. Post-filter parameters reduce the work required by your application, but do not reduce the load on the customer system.

You should generally avoid making Search queries without any native filtering parameters. Many patient charts contain a large amount of data spanning over a long period of time. Consider whether your app will make use of all of the data returned, and if not, filter your search results to a more specific data set. For example, if you only need clinical notes from the patient’s most recent encounter, don’t request all clinical notes for that patient. Instead, filter on the encounter identifier.

Bundle related API calls together

If you’re using FHIR APIs, you can decrease the number of API calls needed while still getting all the data you need by bundling related Search API calls into one API call using the _include parameter. This parameter allows you to ask for related resources to be included in the response bundle. For example, when searching for a patient’s lab results using DiagnosticReport.Search (Results), you can include the associated Observations in the same response, removing the need to perform subsequent Observation.Read API calls.

Avoid API polling

API polling is a practice in which an app constantly or repeatedly queries an API, looking for new data. When apps are designed to poll for information updates, this requires significant resources and degrades performance of the Epic system. There is also the chance that data might be stale by the time it is needed for a workflow and would require additional queries anyway. While use cases for API polling do exist in limited forms, the practice is inherently wasteful. This is because an app does not know when changes have been made to the requested data.

Event-driven technologies can be a way to avoid API polling. Rather than continually calling FHIR APIs to pull the same data points—which may or may not have changed—you can receive updates as they are made to the patient chart.

Cache relatively static data

Your app might require some data that doesn’t change often. For this data, consider taking a snapshot of the data and update or refresh as needed. Many FHIR resources, such as Location and Practitioner, do not change frequently, which means your app can store that data and avoid pulling those resources every time they are referenced. For resources that support date filtering, you may be able to cache older clinical data that is not likely to change, such as encounters that have been closed for an extended period of time. Consider how frequently you want to update each resource in your app’s database to minimize unnecessary API calls.

Set appropriate expectations with customers

Each customer should understand the data you are requesting from their system and at what frequency. Both you and your customer should understand the expected volume of data being exchanged. For example, if your app makes 100 API calls per patient per day, the total number of API calls per day could vary greatly depending on the size of the customer organization. Make sure your customers understand the impact that your app will have on their system so they can evaluate whether any infrastructure changes are required. Customers can work with their Epic Server Systems TS to make this evaluation.

Include programmatic guardrails to limit the number of API calls made

Add programmatic caps on the volume of API calls made in a given time range that would be outside the bounds of expected usage. Alternatively, implement monitoring or alerting mechanisms to proactively identify issues that could impact the customer’s system. Your customers will have different system resources and levels of comfort with different levels of system impact that APIs can cause. Make sure your app has the ability to decrease the frequency of API calls if the customer's system can't handle updates as frequently as you've initially designed.

Stagger API calls

There may be situations where you cannot avoid pulling a very large amount of data. If your app requires a lot of data to be pulled at regular intervals, consider staggering your API calls to minimize your impact on the customer system. For example, rather than making 1,000 API calls at once to pull data for 1,000 different patients, you might split those API calls into groups of 50 or 100 and spread them out over a longer period of time. Coordinate with each customer to schedule your API calls outside of their peak hours.

Test in each customer’s non-production environment

Regardless of the technologies you are using or the amount of data you are requesting, it is important to conduct realistic non-production testing. While test data in a customer’s non-production environments will likely be more limited than production data, you should aim to replicate a production workflow as closely as possible in terms of volume and frequency. This will allow you and your customer to address any issues prior to going live and avoid disrupting end user workflows.

OAuth 2.0 Specification

Per Breaking Change Notification Q-7365177, to enhance security in Epic back-end integrations, all apps that use backend OAuth 2.0 will soon be required to host their public keys at a JWK Set URL (JKU) instead of uploading a static key to each environment. This requirement does not apply to apps that do not have the "Backend Systems" user type. Refer to the following table for Epic versions when this requirement will take effect.

Epic Version

Epic Sandbox JKU Support

Epic Customer JKU Support

February 2025 and prior

Optional

Optional - Each customer can choose to enforce JKU support for backend OAuth apps. This enforcement is off by default.

August 2025

Vendor Services and Epic on FHIR no longer allow new static key uploads for use in the sandbox.

 

November 2025

Static keys no longer supported in the sandbox for backend OAuth.

Vendor Services and Epic on FHIR websites no longer allow static key uploads for new apps. Both sites still support rotating keys for live apps.

February 2026

Static keys are no longer supported in customer environments for backend OAuth.

Using OAuth 2.0

Applications must secure and protect the privacy of patients and their data. To help meet this objective, Epic supports using the OAuth 2.0 framework to authenticate and authorize applications.

Why OAuth 2.0?

OAuth 2.0 enables you to develop your application without having to build a credential management system. Instead of exposing login credentials to your application, your application and an EHR's authorization server exchange a series of authorization codes and access tokens. Your application can access protected patient data stored in an EHR's database after it obtains authorization from the EHR's authorization server.

Before You Get Started

To use OAuth 2.0 to authorize your application's access to patient information, some information needs to be shared between the authorization server and your application:

  1. client_id: The client_id identifies your application to authentication servers within the Epic community and allows you to connect to any organization.
  2. redirect_uri: The redirect_uri confirms your identity and is used to validate and redirect authentication requests that originate from your application. Epic's implementation allows for multiple redirect_uris.
    • Note that a redirect_uri is not needed for backend services using the client_credentials grant type.
    • An https protocol is required for use in a production environment, but http protocol-based redirect_uris are allowed for development and testing.
    • Registered redirect_uris must not contain anything in the fragment (i.e. anything after #).
  3. Credentials: Some apps, sometimes referred to as confidential clients, can use credentials registered for a given EHR system to obtain authorization to access the system without a user or a patient implicitly or explicitly authorizing the app. Examples of this are apps that use refresh tokens to allow users to launch the app outside of an Epic client without needing to log in every time they use the app, and backend services that need to access resources without a specific person launching the app, for example, fetching data on a scheduled basis.

You can register your application for access to both the sandbox and Epic organizations here. You'll provide information to us, including one or more redirect_uris, and Epic will generate a client_id for you.

Apps can be launched from within an existing EHR or patient portal session, which is called an EHR launch. Alternatively, apps can be launched standalone from outside of an existing EHR session. Apps can also be backend services where no user is launching the app.

EHR launch (SMART on FHIR): The app is launched by the EHR calling a launch URL specified in the EHR's configuration. The EHR launches the launch URL and appends a launch token and the FHIR server's endpoint URL (ISS parameter) in the query string. The app exchanges the launch token, along with the client identification parameters to get an authorization code and eventually the access token.

  • See the Embedded Launch section of this guide for more details.

Standalone launch: The app launches directly to the authorize endpoint outside of an EHR session and requests context from the EHR's authorization server.

  • See the Standalone Launch section of this guide for more details.

Backend services: The app is not authorized by a specific person and likely does not have a user interface, and therefore calls EHR web services with system-level authorization.

  • See the Backend Services section of this guide for more details.

Desktop integrations through Subspace: The app requests access to APIs directly available on the EHR's desktop application via a local HTTP server.

  • See the Subspace Communication Framework specification for more details.

Starting in the August 2019 version of Epic, the Epic implementation of OAuth 2.0 supports PKCE and Epic recommends using PKCE for native mobile app integrations. For earlier versions or in cases where PKCE cannot be used, Epic recommends the use of Universal Links/App Links in lieu of custom redirect URL protocol schemes.


The app you build will list both a production Client ID and a non-production Client ID. While testing in the Epic on FHIR sandbox, use the non-production Client ID.

The base URL for the Current Sandbox environment is:

https://fhir.epic.com/interconnect-fhir-oauth/

EHR Launch (SMART on FHIR)

Contents

  • Step 1: Your Application is Launched from the Patient Portal or EHR
  • Step 2: Your Application Retrieves the Conformance Statement or SMART Configuration
    • Additional Header Requirements
  • Step 3: Your Application Requests an Authorization Code
  • Step 4: EHR's Authorization Server Reviews The Request
  • Step 5: Your Application Exchanges the Authorization Code for an Access Token
    • Non-confidential Clients
    • Frontend Confidential Clients
  • OpenID Connect id_tokens
  • Validating the OpenID Connect JSON Web Token
  • Step 6: Your Application Uses FHIR APIs to Access Patient Data
  • Step 7: Use a Refresh Token to Obtain a New Access Token

How It Works

The app is launched by the EHR calling a launch URL specified in the EHR's configuration. The EHR launches the launch URL and appends a launch token and the FHIR server's endpoint URL (ISS parameter) in the query string. The app exchanges the launch token, along with the client identification parameters to get an authorization code and eventually the access token.

Step 1: Your Application is Launched from the Patient Portal or EHR

Your app is launched by the EHR calling the launch URL which is specified in the EHR's configuration. The EHR sends a launch token and the FHIR server's endpoint URL (ISS parameter).

Note: If you are writing a web-based application that you intend to embed in Epic, you may find that your application may require minor modifications. We offer a simple testing harness to help you validate that your web application is compatible with the embeddable web application viewer in Epic. As embedding your application in Hyperspace may not behave the same as presenting it in a browser, ensure that your app works with the testing harness and that you can run multiple instances of the app at the same time within the same session.

  • launch: This parameter is an EHR generated token that signifies that an active EHR session already exists. This token is one-time use and will be exchanged for the authorization code.
    • See the Epic-Issued OAuth 2.0 Tokens appendix section for details on handling launch codes.
  • iss: This parameter contains the EHR's FHIR endpoint URL, which an app can use to find the EHR's authorization server.
    • Your application should create an allowlist of iss values (AKA resource servers) to protect against phishing attacks. Rather than accepting any iss value in your application, only accept those you know belong to the organizations you integrate with.

Step 2: Your Application Retrieves the Conformance Statement or SMART Configuration

To determine which authorize and token endpoints to use in the EHR launch flow, you should make a GET request to the metadata endpoint which is constructed by taking the iss provided and appending /metadata. Alternatively, when communicating with Epic organizations using the August 2021 version or Epic or later, you can make a GET request to the SMART configuration endpoint by taking the iss and appending /.well-known/smart-configuration.

If no Accept header is sent, the metadata response is XML-formatted and the smart-configuration response is JSON-formatted. An Accept header can be sent with a value of application/json, application/xml, and the metadata endpoint additionally supports application/fhir+json and application/json+fhir to specify the format of the response.

Metadata Example

Here's an example of what a full metadata request might look like.

GET https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/DSTU2/metadata HTTP/1.1
Accept: application/fhir+json

Here is an example of what the authorize and token endpoints would look like in the metadata response.

"extension": [
{
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris",
"extension": [
  {
    "url": "authorize",
    "valueUri": "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/authorize"
  },
  {
    "url": "token",
    "valueUri": "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token"
  }
]
}
]

Metadata Example with Dynamic Client Registration (starting in the November 2021 version of Epic)

Here's an example of what a full metadata request might look like. By including an Epic Client ID, clients authorized to perform dynamic client registration can then determine which endpoint to use when performing dynamic client registration. Note that this capability is only supported for STU3 and R4 requests.

GET https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/metadata HTTP/1.1
Accept: application/fhir+json
Epic-Client-ID: 0000-0000-0000-0000-0000

Here is an example of what the authorize, token, and dynamic registration endpoints would look like in the metadata response.

"extension": [
{
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris",
"extension": [
  {
    "url": "authorize",
    "valueUri": "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/authorize"
  },
  {
    "url": "token",
    "valueUri": "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token"
  },
  {
    "url": "register",
    "valueUri": https://fhir.epic.com/interconnect-fhir-oauth/oauth2/register
  }
]
}
]

Here's an example of what a smart-configuration request might look like.

GET https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/.well-known/smart-configuration HTTP/1.1
Accept: application/json

Here is an example of what the authorize and token endpoints would look like in the smart-configuration response.

"authorization_endpoint": "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/authorize",
"token_endpoint": "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token",
"token_endpoint_auth_methods_supported": [
    "client_secret_post",
    "client_secret_basic",
    "private_key_jwt"
]

Retrieving Conformance Statement: Additional Header Requirements

In some cases when making your request to the metadata endpoint, you'll also need to include additional headers to ensure the correct token and authorize endpoints are returned. This is because in Epic, it is possible to configure the system such that the FHIR server's endpoint URL, provided in the iss parameter, is overridden on a per client ID basis. If you do not send the Epic-Client-ID HTTP header with the appropriate production or non-production client ID associated with your application in your call to the metadata endpoint, you can receive the wrong token and authorize endpoints. Additional CORS setup might be necessary on the Epic community member's Interconnect server to allow for this header.

Starting in the November 2021 version of Epic, by including an Epic Client ID, clients authorized to perform dynamic client registration can then determine which endpoint to use when performing dynamic client registration. Note that this capability is supported for only STU3 and R4 requests.

Here's an example of what a full metadata request might look like with the additional Epic-Client-ID header.

GET https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/metadata HTTP/1.1
Accept: application/fhir+json
Epic-Client-ID: 0000-0000-0000-0000-0000

Step 3: Your Application Requests an Authorization Code

Your application now has a launch token obtained from the initial EHR launch as well as the authorize endpoint obtained from the metadata query. To exchange the launch token for an authorization code, your app needs to either make an HTTP GET or POST request to the authorize endpoint that contains the parameters below. POST requests are supported for EHR launches in Epic version November 2020 and later, and are not currently supported for standalone launches.

For POST requests, the parameters should be in the body of the request and should be sent as Content-Type "application/x-www-form-urlencoded". For GET requests, the parameters should be appended in the querystring of the request. It is Epic's recommendation to use HTTP POST for EHR launches to overcome any size limits on the request URL.

In either case, the application must redirect the User-Agent (the user's browser) to the authorization server by either submitting an HTML Form to the authorization server (for HTTP POST) or by redirecting to a specified URL (for HTTP GET). The request should contain the following parameters.

  • response_type: This parameter must contain the value "code".
  • client_id: This parameter contains your web application's client ID issued by Epic.
  • redirect_uri: This parameter contains your application's redirect URI. After the request completes on the Epic server, this URI will be called as a callback. The value of this parameter needs to be URL encoded. This URI must also be registered with the EHR's authorization server by adding it to your app listing.
  • scope: This parameter describes the information for which the web application is requesting access. Starting with the Epic 2018 version, a scope of "launch" is required for EHR launch workflows. Starting with the November 2019 version of Epic, OpenID Connect scopes are supported, specifically the "openid" scope is supported for all apps and the "fhirUser" scope is supported by apps with the R4 (or greater) SMART on FHIR version selected.
  • launch: This parameter is required for EHR launch workflows. The value to use will be passed from the EHR.
  • aud: Starting in the August 2021 version of Epic, health care organizations can optionally configure their system to require the aud parameter for EHR launch workflows if a launch context is included in the scope parameter. Starting in the May 2023 version of Epic, this parameter will be required. The value to use is the FHIR base URL of the resource server the application intends to access, which is typically the FHIR server returned by the iss.
  • state: This optional parameter is generated by your app and is opaque to the EHR. The EHR's authorization server will append it to each subsequent exchange in the workflow for you to validate session integrity. While not required, this parameter is recommended to be included and validated with each exchange in order to increase security. For more information see RFC 6819 Section 3.6.
    • Note: Large state values in combination with launch tokens that are JWTs (see above) may cause the query string for the HTTP GET request to the authorization endpoint to exceed the Epic community member web server's max query string length and cause the request to fail. You can mitigate this risk by:
      • Following the SMART App Launch Framework's recommendation that the state parameter be an unpredictable value ... with at least 122 bits of entropy (e.g., a properly configured random uuid is suitable), and not store any actual application state in the state parameter.
      • Using a POST request. If you use this approach, note that you might still need to account for a large state value in the HTTP GET redirect back to your own server.

Additional parameters for native mobile apps (available starting in the August 2019 version of Epic):

  • code_challenge: This optional parameter is generated by your app and used for PKCE. This is the S256 hashed version of the code_verifier parameter, which will be used in the token request.
  • code_challenge_method: This optional parameter indicates the method used for the code_challenge parameter and is required if using that parameter. Currently, only the S256 method is supported.

Here's an example of an authorization request using HTTP GET. You will replace the [redirect_uri], [client_id], [launch_token], [state], [code_challenge], and [audience] placeholders with your own values.

https://fhir.epic.com/interconnect-fhir-oauth/oauth2/authorize?scope=launch&response_type=code&redirect_uri=[redirect_uri]&client_id=[client_id]&launch=[launch_token]&state=[state]&code_challenge=[code_challenge]&code_challenge_method=S256&aud=[audience]

This is an example HTTP GET from the SMART on FHIR launchpad:

https://fhir.epic.com/interconnect-fhir-oauth/oauth2/authorize?scope=launch&response_type=code&redirect_uri=https%3A%2F%2Ffhir.epic.com%2Ftest%2Fsmart&client_id=d45049c3-3441-40ef-ab4d-b9cd86a17225&launch=GwWCqm4CCTxJaqJiROISsEsOqN7xrdixqTl2uWZhYxMAS7QbFEbtKgi7AN96fKc2kDYfaFrLi8LQivMkD0BY-942hYgGO0_6DfewP2iwH_pe6tR_-fRfiJ2WB_-1uwG0&state=abc123&aud=https%3A%2F%2Ffhir.epic.com%2Finterconnect-fhir-oauth%2Fapi%2Ffhir%2Fdstu2

And here's an example of what the same request would look like as an HTTP Post:

POST https://fhir.epic.com/interconnect-fhir-oauth/oauth2/authorize HTTP/1.1
Content-Type: application/x-www-form-urlencoded

scope=launch&response_type=code&redirect_uri=https%3A%2F%2Ffhir.epic.com%2Ftest%2Fsmart&client_id=d45049c3-3441-40ef-ab4d-b9cd86a17225&launch=GwWCqm4CCTxJaqJiROISsEsOqN7xrdixqTl2uWZhYxMAS7QbFEbtKgi7AN96fKc2kDYfaFrLi8LQivMkD0BY-942hYgGO0_6DfewP2iwH_pe6tR_-fRfiJ2WB_-1uwG0&state=abc123&aud=https%3A%2F%2Ffhir.epic.com%2Finterconnect-fhir-oauth%2Fapi%2Ffhir%2Fdstu2 

Step 4: EHR's Authorization Server Reviews the Request

The EHR's authorization server reviews the request from your application. If approved, the authorization server redirects the browser to the redirect URL supplied in the initial request and appends the following querystring parameter.

  • code: This parameter contains the authorization code generated by Epic, which will be exchanged for the access token in the next step.
    • See the Epic-Issued OAuth 2.0 Tokens appendix section for details on handling authorization codes.
  • state: This parameter will have the same value as the earlier state parameter. For more information, refer to Step 3.

Here's an example of what the redirect will look like if Epic's authorization server accepts the request:

https://fhir.epic.com/test/smart?code=yfNg-rSc1t5O2p6jVAZLyY00uOOte5KM1y3YUxqsJQnBKEMNsYqOPTyVqcCH3YXaPkLztO9Rvf7bhLqQTwALHcHN6raxpTbR1eVgV2QyLA_4K0HrJO92et3qRXiXPkj7&state=abc123

Step 5: Your Application Exchanges the Authorization Code for an Access Token

After receiving the authorization code, your application trades the code for a JSON object containing an access token and contextual information by sending an HTTP POST to the token endpoint using a Content-Type header with value of "application/x-www-form-urlencoded". For more information, see RFC 6749 section 4.1.3.

Access Token Request: Non-confidential Clients

The following parameters are required in the POST body:

  • grant_type: For the EHR launch flow, this should contain the value "authorization_code".
  • code: This parameter contains the authorization code sent from Epic's authorization server to your application as a querystring parameter on the redirect URI as described above.
  • redirect_uri: This parameter must contain the same redirect URI that you provided in the initial access request. The value of this parameter needs to be URL encoded.
  • client_id: This parameter must contain the application's client ID issued by Epic that you provided in the initial request.
  • code_verifier: This optional parameter is used to verify against your code_challenge parameter when using PKCE. This parameter is passed as free text and must match the code_challenge parameter used in your authorization request once it is hashed on the server using the code_challenge_method. This parameter is available starting in the August 2019 version of Epic.

Here's an example of what an HTTP POST request for an access token might look like:

POST https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=yfNg-rSc1t5O2p6jVAZLyY00uOOte5KM1y3YUxqsJQnBKEMNsYqOPTyVqcCH3YXaPkLztO9Rvf7bhLqQTwALHcHN6raxpTbR1eVgV2QyLA_4K0HrJO92et3qRXiXPkj7&redirect_uri=https%3A%2F%2Ffhir.epic.com%2Ftest%2Fsmart&client_id=d45049c3-3441-40ef-ab4d-b9cd86a17225 

The authorization server responds to the HTTP POST request with a JSON object that includes an access token. The response contains the following fields:

  • access_token: This parameter contains the access token issued by Epic to your application and is used in future requests.
    • See the Epic-Issued OAuth 2.0 Tokens appendix section for details on handling access tokens.
  • token_type: In Epic's OAuth 2.0 implementation, this parameter always includes the value bearer.
  • expires_in: This parameter contains the number of seconds for which the access token is valid.
  • scope: This parameter describes the access your application is authorized for.
  • id_token: Returned only for applications that have requested an openid scope. See below for more info on OpenID Connect id_tokens. This parameter follows the guidelines of the OpenID Connect (OIDC) Core 1.0 specification. It is signed but not encrypted.
  • patient: This parameter identifies provides the FHIR ID for the patient, if a patient is in context at time of launch.
  • epic.dstu2.patient: This parameter identifies the DSTU2 FHIR ID for the patient, if a patient is in context at time of launch.
  • encounter: This parameter identifies the FHIR ID for the patient's encounter, if in context at time of launch. The encounter token corresponds to the FHIR Encounter resource.
  • location : This parameter identifies the FHIR ID for the encounter department, if in context at time of launch. The location token corresponds to the FHIR Location resource.
  • appointment: This parameter identifies the FHIR ID for the patient's appointment, if appointment context is available at time of launch. The appointment token corresponds to the FHIR Appointment resource.
  • loginDepartment: This parameter identifies the FHIR ID of the user's login department for launches from Hyperspace. The loginDepartment token corresponds to the FHIR Location resource.
  • state: This parameter will have the same value as the earlier state parameter. For more information, refer to Step 3.

Note that you can include additional fields in the response if needed based on the integration configuration. For more information, refer to the Launching your app topic. Here's an example of what a JSON object including an access token might look like:

{
"access_token": "Nxfve4q3H9TKs5F5vf6kRYAZqzK7j9LHvrg1Bw7fU_07_FdV9aRzLCI1GxOn20LuO2Ahl5RkRnz-p8u1MeYWqA85T8s4Ce3LcgQqIwsTkI7wezBsMduPw_xkVtLzLU2O",
"token_type": "bearer",
"expires_in": 3240,
"scope": "openid Patient.read Patient.search ",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IktleUlEIiwidHlwIjoiSldUIn0.eyJhdWQiOiJDbGllbnRJRCIsImV4cCI6RXhwaXJlc0F0LCJpYXQiOklzc3VlZEF0LCJpc3MiOiJJc3N1ZXJVUkwiLCJzdWIiOiJFbmRVc2VySWRlbnRpZmllciJ9",
"__epic.dstu2.patient": "T1wI5bk8n1YVgvWk9D05BmRV0Pi3ECImNSK8DKyKltsMB",
"patient": "T1wI5bk8n1YVgvWk9D05BmRV0Pi3ECImNSK8DKyKltsMB",
"appointment": "eVa-Ad1SCIenVq8CYQVxfKwGtP-DfG3nIy9-5oPwTg2g3",
"encounter": "eySnzekbpf6uGZz87ndPuRQ3",
"location": "e4W4rmGe9QzuGm2Dy4NBqVc0KDe6yGld6HW95UuN-Qd03",
"loginDepartment": "e4W4rmGe9QzuGm2Dy4NBqVc0KDe6yGld6HW95UuN-Qd03",
"state": "abc123",
}

At this point, authorization is complete and the web application can access the protected patient data it requests using FHIR APIs.

Access Token Request: Frontend Confidential Clients

Consult Standalone Launch: Access Token Request for Frontend Confidential Clients for details on how to obtain an access token if your app is a confidential client.

OpenID Connect id_tokens

Epic started supporting the OpenID Connect id_token in the access token response for apps that request the openid scope since the November 2019 version of Epic.

A decoded OpenID Connect id_token JWT will have these headers:

Header Description
alg The JWT authentication algorithm. Currently only RSA 256 is supported in id_token JWTs so this will always be RS256.
typ This is always set to JWT.
kid The base64 encoded SHA-256 hash of the public key.

A decoded OpenID Connect id_token JWT will have this payload:

Claim Description Remarks
iss Issuer of the JWT. This is set to the token endpoint that should be used by the client. Starting in the May 2020 version of Epic, the iss will be set to the OAuth 2.0 server endpoint, e.g. https://<Interconnect Server URL>/oauth2. For Epic versions prior to May 2020, it is set to the OAuth 2.0 token endpoint, e.g. https://<Interconnect Server URL>/oauth2/token.
sub STU3+ FHIR ID for the resource representing the user launching the app. For Epic integrations, the sub and fhirUser claims reference one of the following resources depending on the type of workflow:
  • Provider/user workflows: Practitioner resource
  • MyChart self access workflows: Patient resource
  • MyChart proxy access workflows: RelatedPerson resource
Note apps with a SMART on FHIR version of DSTU2 will still get a STU3+ FHIR FHIR ID for the user. The STU3+ Practitioner.Read or RelatedPerson.Read APIs would be required to read the Practitioner or RelatedPerson FHIR IDs. Patient FHIR IDs can be used across FHIR versions in Epic's implementation.
fhirUser Absolute reference to the FHIR resource representing the user launching the app. See the HL7 documentation for more details. The fhirUser claim will only be present if app has the R4 (or greater) SMART on FHIR version selected, and requests both the openid and fhirUser scopes in the request to the authorize endpoint. See the remark for the sub claim above for more information about what the resource returned in these claims represents.
aud Audiences that the ID token is intended for. This will be set to the client ID for the application that was just authorized during the SMART on FHIR launch.
iat Time integer for when the JWT was created, expressed in seconds since the "Epoch" (1970-01-01T00:00:00Z UTC).
exp Expiration time integer for this authentication JWT, expressed in seconds since the "Epoch" (1970-01-01T00:00:00Z UTC). This is set to the current time plus 5 minutes.
preferred_username The user's LDAP/AD down-level logon name. This claim is supported in Epic version February 2021 and later. The preferred_username claim will be present only if the app requests both the openid and profile scopes in the request to the authorize endpoint.
nonce The nonce for this client session. The nonce claim will be present only if it was specified in the authorization request. This is an identifier for the client session. For more information about the nonce, refer to the Authentication Request section of the OpenID specification.

Here is an example decoded id_token that could be returned if the app has the R4 (or greater) SMART on FHIR version selected, and requests both the openid and fhirUser scopes in the request to the authorize endpoint:

{
"alg": "RS256",
"kid": "liCulTIaitUzjfUh2AqNiMro47X9HcVcd9XPi8LDJKA=",
"typ": "JWT"
}
{
"aud": "de5dae1a-4317-4c25-86f1-ed558e85529b",
"exp": 1595956317,
"fhirUser": "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/api/FHIR/R4/Practitioner/exfo6E4EXjWsnhA1OGVElgw3",
"iat": 1595956017,
"iss": "https://fhir.epic.com/interconnect-fhir-oauth/oauth2",
"sub": "exfo6E4EXjWsnhA1OGVElgw3"
}

Here is an example decoded id_token that could be returned if the app requests at least the openid scope in the request to the authorize endpoint, but either doesn't request the fhirUser claim or doesn't meet the criteria outlined above for receiving the fhirUser claim:

{
"alg": "RS256",
"kid": "liCulTIaitUzjfUh2AqNiMro47X9HcVcd9XPi8LDJKA=",
"typ": "JWT"
}
{
"aud": "de5dae1a-4317-4c25-86f1-ed558e85529b",
"exp": 1595956317,
"iat": 1595956017,
"iss": "https://fhir.epic.com/interconnect-fhir-oauth/oauth2",
"sub": "exfo6E4EXjWsnhA1OGVElgw3"
}

Validating the OpenID Connect JSON Web Token

When completing the OAuth 2.0 authorization workflow with the openid scope, the JSON Web Token (JWT) returned in the id_token field will be cryptographically signed. The public key to verify the authenticity of the JWT can be found at https://<Interconnect Server URL>/api/epic/2019/Security/Open/PublicKeys/530005/OIDC, for example https://fhir.epic.com/interconnect-fhir-oauth/api/epic/2019/Security/Open/PublicKeys/530005/OIDC.

Starting in the May 2020 version of Epic, metadata about the server's OpenID Connect configuration, including the jwks_uri OIDC public key endpoint, can be found at the OpenID configuration endpoint https://<Interconnect Server URL>/oauth2/.well-known/openid-configuration, for example https://fhir.epic.com/interconnect-fhir-oauth/oauth2/.well-known/openid-configuration.

Starting in the August 2021 version of Epic, metadata about the server's OpenID Connect configuration can also be found at the FHIR endpoint https://<Interconnect Server URL>/api/FHIR/R4/.well-known/openid-configuration, for example https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/.well-known/openid-configuration.

Step 6: Your Application Uses FHIR APIs to Access Patient Data

With a valid access token, your application can now access protected patient data from the EHR database using FHIR APIs. Queries must contain an Authorization header that includes the access token presented as a Bearer token.

Here's an example of what a valid query looks like:

GET https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/DSTU2/Patient/T1wI5bk8n1YVgvWk9D05BmRV0Pi3ECImNSK8DKyKltsMB HTTP/1.1
Authorization: Bearer Nxfve4q3H9TKs5F5vf6kRYAZqzK7j9LHvrg1Bw7fU_07_FdV9aRzLCI1GxOn20LuO2Ahl5RkRnz-p8u1MeYWqA85T8s4Ce3LcgQqIwsTkI7wezBsMduPw_xkVtLzLU2O

Step 7: Use a Refresh Token to Obtain a New Access Token

Refresh tokens are not typically needed for embedded (SMART on FHIR) launches because users are not required to log in to Epic during the SMART on FHIR launch process, and the access token obtained from the launch process is typically valid for longer than the user needs to use the app.

Consult the Standalone Launch: Use a Refresh Token to Obtain a New Access Token for details on how to use a refresh token to get a new an access token if your app uses refresh tokens.

Standalone Launch

Contents

  • How It Works
    • Step 1: Your Application Requests an Authorization Code
    • Step 2: EHR's Authorization Server Authenticates the User and Authorizes Access
    • Step 3: Your Application Exchanges the Authorization Code for an Access Token
      • Non-confidential Clients
      • Frontend Confidential Clients
    • Step 4: Your Application Uses FHIR APIs to Access Patient Data
    • Step 5: Use a Refresh Token to Obtain a New Access Token
  • Offline Access for Native and Browser-Based Applications
    • Step 1: Get the initial access token you'll use to register a dynamic client
    • Step 2: Register the dynamic client
    • Step 3: Get an access token you can use to retrieve FHIR resources
    • Step 3a: Get an access token using the JWT bearer flow
    • Step 3b: Alternate flow with refresh tokens

How It Works

The app launches directly to the authorize endpoint outside of an EHR session and requests context from the EHR's authorization server.

Step 1: Your Application Requests an Authorization Code

Your application would like to authenticate the user using the OAuth 2.0 workflow. To initiate this process, your app needs to link (using HTTP GET) to the authorize endpoint and append the following querystring parameters:

  • response_type: This parameter must contain the value "code".
  • client_id: This parameter contains your web application's client ID issued by Epic.
  • redirect_uri: This parameter contains your application's redirect URI. After the request completes on the Epic server, this URI will be called as a callback. The value of this parameter needs to be URL encoded. This URI must also be registered with the EHR's authorization server by adding it to your app listing.
  • state: This optional parameter is generated by your app and is opaque to the EHR. The EHR's authorization server will append it to each subsequent exchange in the workflow for you to validate session integrity. While not required, this parameter is recommended to be included and validated with each exchange in order to increase security. For more information see RFC 6819 Section 3.6.
  • scope: This parameter describes the information for which the web application is requesting access. Starting with the November 2019 version of Epic, the "openid" and "fhirUser" OpenID Connect scopes are supported. While scope is optional in Epic's SMART on FHIR implementation, it's required for testing in the sandbox. Providing scope=openid is sufficient.
  • aud: Starting in the August 2021 version of Epic, health care organizations can optionally configure their system to require the aud parameter for Standalone and EHR launch workflows if a launch context is included in the scope parameter. Starting in the May 2023 version of Epic, this parameter will be required. The value to use is the base URL of the resource server the application intends to access, which is typically the FHIR server.

Additional parameters for native mobile apps (available starting in the August 2019 version of Epic):

  • code_challenge: This optional parameter is generated by your app and used for PKCE. This is the S256 hashed version of the code_verifier parameter, which will be used in the token request.
  • code_challenge_method: This optional parameter indicates the method used for the code_challenge parameter and is required if using that parameter. Currently, only the S256 method is supported.

Here's an example of an authorization request using HTTP GET. You will replace the [redirect_uri], [client_id], [state], and [audience] placeholders with your own values.

https://fhir.epic.com/interconnect-fhir-oauth/oauth2/authorize?response_type=code&redirect_uri=[redirect_uri]&client_id=[client_id]&state=[state]&aud=[audience]&scope=[scope]

This is an example link:

https://fhir.epic.com/interconnect-fhir-oauth/oauth2/authorize?response_type=code&redirect_uri=https%3A%2F%2Ffhir.epic.com%2Ftest%2Fsmart&client_id=d45049c3-3441-40ef-ab4d-b9cd86a17225&state=abc123&aud=https%3A%2F%2Ffhir.epic.com%2Finterconnect-fhir-oauth%2Fapi%2Ffhir%2Fdstu2&scope=openid

Step 2: EHR's Authorization Server Authenticates the User and Authorizes Access

The EHR's authorization server reviews the request from your application, authenticates the user (sample credentials found here), and authorizes access. If approved, the authorization server redirects the browser to the redirect URL supplied in the initial request and appends the following querystring parameter.

  • code: This parameter contains the authorization code generated by Epic, which will be exchanged for the access token in the next step.
    • See the Epic-Issued OAuth 2.0 Tokens appendix section for details on handling authorization codes.
  • state: This parameter will have the same value as the earlier state parameter. For more information, refer to Step 1.

Here's an example of what the redirect will look like if Epic's authorization server accepts the request:

https://fhir.epic.com/test/smart?code=yfNg-rSc1t5O2p6jVAZLyY00uOOte5KM1y3YUxqsJQnBKEMNsYqOPTyVqcCH3YXaPkLztO9Rvf7bhLqQTwALHcHN6raxpTbR1eVgV2QyLA_4K0HrJO92et3qRXiXPkj7&state=abc123

Step 3: Your Application Exchanges the Authorization Code for an Access Token

After receiving the authorization code, your application trades the code for a JSON object containing an access token and contextual information by sending an HTTP POST to the token endpoint using a Content-Type header with value of "application/x-www-form-urlencoded". For more information, see RFC 6749 section 4.1.3.

Access Token Request: Non-confidential Clients

The following parameters are required in the POST body:

  • grant_type: For the Standalone launch flow, this should contain the value "authorization_code".
  • code: This parameter contains the authorization code sent from Epic's authorization server to your application as a querystring parameter on the redirect URI as described above.
  • redirect_uri: This parameter must contain the same redirect URI that you provided in the initial access request. The value of this parameter needs to be URL encoded.
  • client_id: This parameter must contain the application's client ID issued by Epic that you provided in the initial request.
  • code_verifier: This optional parameter is used to verify against your code_challenge parameter when using PKCE. This parameter is passed as free text and must match the code_challenge parameter used in your authorization request once it is hashed on the server using the code_challenge_method. This parameter is available starting in the August 2019 version of Epic.

Here's an example of what an HTTP POST request for an access token might look like:

POST https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=yfNg-rSc1t5O2p6jVAZLyY00uOOte5KM1y3YUxqsJQnBKEMNsYqOPTyVqcCH3YXaPkLztO9Rvf7bhLqQTwALHcHN6raxpTbR1eVgV2QyLA_4K0HrJO92et3qRXiXPkj7&redirect_uri=https%3A%2F%2Ffhir.epic.com%2Ftest%2Fsmart&client_id=d45049c3-3441-40ef-ab4d-b9cd86a17225 

The authorization server responds to the HTTP POST request with a JSON object that includes an access token. The response contains the following fields:

  • access_token: This parameter contains the access token issued by Epic to your application and is used in future requests.
    • See the Epic-Issued OAuth 2.0 Tokens appendix section for details on handling access tokens.
  • token_type: In Epic's OAuth 2.0 implementation, this parameter always includes the value bearer.
  • expires_in: This parameter contains the number of seconds for which the access token is valid.
  • scope: This parameter describes the access your application is authorized for.
  • id_token: Returned only for applications that have requested an openid scope. See above for more info on OpenID Connect id_tokens. This parameter follows the guidelines described earlier in this document.
  • patient: For patient-facing workflows, this parameter identifies the FHIR ID for the patient on whose behalf authorization to the system was granted.
    • The patient's FHIR ID is not returned for provider-facing standalone launch workflows.
  • epic.dstu2.patient: For patient-facing workflows, this parameter identifies the DSTU2 FHIR ID for the patient on whose behalf authorization to the system was granted.
    • The patient's FHIR ID is not returned for provider-facing standalone launch workflows.

Note that you can pass additional parameters if needed based on the integration configuration. Here's an example of what a JSON object including an access token might look like:

{
"access_token": "Nxfve4q3H9TKs5F5vf6kRYAZqzK7j9LHvrg1Bw7fU_07_FdV9aRzLCI1GxOn20LuO2Ahl5RkRnz-p8u1MeYWqA85T8s4Ce3LcgQqIwsTkI7wezBsMduPw_xkVtLzLU2O",
"token_type": "bearer",
"expires_in": 3240,
"scope": "Patient.read Patient.search ",
"patient": "T1wI5bk8n1YVgvWk9D05BmRV0Pi3ECImNSK8DKyKltsMB"
}

At this point, authorization is complete and the web application can access the protected patient data it requested using FHIR APIs.

Access Token Request: Frontend Confidential Clients

There are two authentication options available for confidential apps that can keep authentication credentials secret:

  • Client Secret Authentication
  • JSON Web Token (JWT) Authentication

Access Token Request: Frontend Confidential Clients with Client Secret Authentication

After receiving the authorization code, your application trades the code for a JSON object containing an access token and contextual information by sending an HTTP POST to the token endpoint using a Content-Type header with value of "application/x-www-form-urlencoded". For more information, see RFC 6749 section 4.1.3.

The Epic on FHIR website can generate a client secret (effectively a password) for your app to use to obtain refresh tokens, and store the hashed secret for you, or Epic community members can upload a client secret hash that you provide them when they activate your app for their system. If you provide community members a client secret hash to upload, you should use a unique client secret per Epic community member and per environment type (non-production and production) for each Epic community member.

The following parameters are required in the POST body:

  • grant_type: This should contain the value authorization_code.
  • code: This parameter contains the authorization code sent from Epic's authorization server to your application as a querystring parameter on the redirect URI as described above.
  • redirect_uri: This parameter must contain the same redirect URI that you provided in the initial access request. The value of this parameter needs to be URL encoded.

Note: The client_id parameter is not passed in the the POST body if you use client secret authentication, which is different from the access token request for apps that do not use refresh tokens. You will instead pass an Authorization HTTP header with client_id and client_secret URL encoded and passed as a username and password. Conceptually the Authorization HTTP header will have this value: base64(client_id:client_secret).

For example, using the following client_id and client_secret:

client_id: d45049c3-3441-40ef-ab4d-b9cd86a17225

URL encoded client_id: d45049c3-3441-40ef-ab4d-b9cd86a17225 Note: base64 encoding Epic's client IDs will have no effect

client_secret: this-is-the-secret-2/7

URL encoded client_secret: this-is-the-secret-2%2F7

Would result in this Authorization header:

Authorization: Basic base64Encode{d45049c3-3441-40ef-ab4d-b9cd86a17225:this-is-the-secret-2%2F7}

or

Authorization: Basic ZDQ1MDQ5YzMtMzQ0MS00MGVmLWFiNGQtYjljZDg2YTE3MjI1OnRoaXMtaXMtdGhlLXNlY3JldC0yJTJGNw==

Here's an example of what a valid HTTP POST might look like:

POST https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded 
Authorization: Basic ZDQ1MDQ5YzMtMzQ0MS00MGVmLWFiNGQtYjljZDg2YTE3MjI1OnRoaXMtaXMtdGhlLXNlY3JldC0yJTJGNw==

grant_type=authorization_code&code=yfNg-rSc1t5O2p6jVAZLyY00uOOte5KM1y3YUxqsJQnBKEMNsYqOPTyVqcCH3YXaPkLztO9Rvf7bhLqQTwALHcHN6raxpTbR1eVgV2QyLA_4K0HrJO92et3qRXiXPkj7&redirect_uri=https%3A%2F%2Ffhir.epic.com%2Ftest%2Fsmart

The authorization server responds to the HTTP POST request with a JSON object that includes an access token and a refresh token. The response contains the following fields:

  • refresh_token: This parameter contains the refresh token issued by Epic to your application and can be used to obtain a new access token. For more information on how this works, see Step 5.
    • See the Epic-Issued OAuth 2.0 Tokens appendix section for details on handling refresh tokens.
  • access_token: This parameter contains the access token issued by Epic to your application and is used in future requests.
    • See the Epic-Issued OAuth 2.0 Tokens appendix section for details on handling access tokens.
  • token_type: In Epic's OAuth 2.0 implementation, this parameter always includes the value bearer.
  • expires_in: This parameter contains the number of seconds for which the access token is valid.
  • scope: This parameter describes the access your application is authorized for.
  • id_token: Returned only for applications that have requested an openid scope. See above for more info on OpenID Connect id_tokens. This parameter follows the guidelines of the OpenID Connect (OIDC) Core 1.0 specification. It is signed but not encrypted.
  • patient: For patient-facing workflows, this parameter identifies the FHIR ID for the patient on whose behalf authorization to the system was granted.
    • The patient's FHIR ID is not returned for provider-facing standalone launch workflows.
  • epic.dstu2.patient: For patient-facing workflows, this parameter identifies the DSTU2 FHIR ID for the patient on whose behalf authorization to the system was granted.
    • The patient's FHIR ID is not returned for provider-facing standalone launch workflows.
  • encounter: This parameter identifies the FHIR ID for the patient's encounter, if in context at time of launch. The encounter token corresponds to the FHIR Encounter resource.
    • The encounter FHIR ID is not returned for standalone launch workflows.
  • location: This parameter identifies the FHIR ID for the ecounter department, if in context at time of launch. The location token corresponds to the FHIR Location resource.
    • The location FHIR ID is not returned for standalone launch workflows.
  • appointment: This parameter identifies the FHIR ID for the patient's appointment, if appointment context is available at time of launch. The appointment token corresponds to the FHIR Appointment resource.
    • The appointment FHIR ID is not returned for standalone launch workflows.
  • loginDepartment: This parameter identifies the FHIR ID of the user's login department for launches from Hyperspace. The loginDepartment token corresponds to the FHIR Location resource.
    • The loginDepartment FHIR ID is not returned for standalone launch workflows.

Note that you can pass additional parameters if needed based on the integration configuration. Here's an example of what a JSON object including an access token and refres token might look like:

{
"access_token": "Nxfve4q3H9TKs5F5vf6kRYAZqzK7j9LHvrg1Bw7fU_07_FdV9aRzLCI1GxOn20LuO2Ahl5RkRnz-p8u1MeYWqA85T8s4Ce3LcgQqIwsTkI7wezBsMduPw_xkVtLzLU2O",
"refresh_token": "H9TKs5F5vf6kRYAZqzK7j9L_07_FdV9aRzLCI1GxOn20LuO2Ahl5R1MeYWqA85T8s4sTkI7wezBsMduPw_xkLzLU2O",
"token_type": "bearer",
"expires_in": 3240,
"scope": "Patient.read Patient.search ",
"patient": "T1wI5bk8n1YVgvWk9D05BmRV0Pi3ECImNSK8DKyKltsMB"
}

At this point, authorization is complete and the web application can access the protected patient data it requested using FHIR APIs.

Access Token Request: Frontend Confidential Clients with JSON Web Token (JWT) Authentication

After receiving the authorization code, your application trades the code for a JSON object containing an access token and contextual information by sending an HTTP POST to the token endpoint using a Content-Type header with value of "application/x-www-form-urlencoded". For more information, see RFC 6749 section 4.1.3.

You can use a one-time use JSON Web Token (JWT) to authenticate your app to the authorization server and obtain an access token and/or refresh token. There are several libraries for creating JWTs. See jwt.io for some examples. You'll pre-register a JSON Web Key Set or JWK Set URL for a given Epic community member environment on the Epic on FHIR website and then use the corresponding private key to create a signed JWT. Frontend apps using JWT authentication must use a JSON Web Key Set or JWK Set URL as static public keys are not supported.

See the Creating a Public Private Key Pair for JWT Signature and the Creating a JWT sections for details on how to create a signed JWT.

The following parameters are required in the POST body:

  • grant_type: This should contain the value authorization_code.
  • code: This parameter contains the authorization code sent from Epic's authorization server to your application as a querystring parameter on the redirect URI as described above.
  • redirect_uri: This parameter must contain the same redirect URI that you provided in the initial access request. The value of this parameter needs to be URL encoded.
  • client_assertion_type: This should be set to urn:ietf:params:oauth:client-assertion-type:jwt-bearer.
  • client_assertion: This will be the one-time use JSON Web Token (JWT) your app generated using the steps mentioned above.

Here's an example of what a valid HTTP POST might look like:

POST https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&code=yfNg-rSc1t5O2p6jVAZLyY00uOOte5KM1y3YUxqsJQnBKEMNsYqOPTyVqcCH3YXaPkLztO9Rvf7bhLqQTwALHcHN6raxpTbR1eVgV2QyLA_4K0HrJO92et3qRXiXPkj7&redirect_uri=https%3A%2F%2Ffhir.epic.com%2Ftest%2Fsmart&client_assertion=eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkNDUwNDljMy0zNDQxLTQwZWYtYWI0ZC1iOWNkODZhMTcyMjUiLCJzdWIiOiJkNDUwNDljMy0zNDQxLTQwZWYtYWI0ZC1iOWNkODZhMTcyMjUiLCJhdWQiOiJodHRwczovL2ZoaXIuZXBpYy5jb20vaW50ZXJjb25uZWN0LWZoaXItb2F1dGgvb2F1dGgyL3Rva2VuIiwianRpIjoiZjllYWFmYmEtMmU0OS0xMWVhLTg4ODAtNWNlMGM1YWVlNjc5IiwiZXhwIjoxNTgzNTI0NDAyLCJuYmYiOjE1ODM1MjQxMDIsImlhdCI6MTU4MzUyNDEwMn0.dztrzHo9RRwNRaB32QxYLaa9CcIMoOePRCbpqsRKgyJmBOGb9acnEZARaCzRDGQrXccAQ9-syuxz5QRMHda0S3VbqM2KX0VRh9GfqG3WJBizp11Lzvc2qiUPr9i9CqjtqiwAbkME40tioIJMC6DKvxxjuS-St5pZbSHR-kjn3ex2iwUJgPbCfv8cJmt19dHctixFR6OG-YB6lFXXpNP8XnL7g85yLOYoQcwofN0k8qK8h4uh8axTPC21fv21mCt50gx59XgKsszysZnMDt8OG_G4gjk_8JnGHwOVkJhqj5oeg_GdmBhQ4UPuxt3YvCOTW9S2vMikNUnxrhdVvn2GVg

The authorization server responds to the HTTP POST request with a JSON object that includes an access token and a refresh token. The response contains the following fields:

  • refresh_token: If your app is configured to receive refresh tokens, this parameter contains the refresh token issued by Epic to your application and can be used to obtain a new access token. For more information on how this works, see Step 5.
    • See the Epic-Issued OAuth 2.0 Tokens appendix section for details on handling refresh tokens.
  • access_token: This parameter contains the access token issued by Epic to your application and is used in future requests.
    • See the Epic-Issued OAuth 2.0 Tokens appendix section for details on handling access tokens.
  • token_type: In Epic's OAuth 2.0 implementation, this parameter always includes the value bearer.
  • expires_in: This parameter contains the number of seconds for which the access token is valid.
  • scope: This parameter describes the access your application is authorized for.
  • id_token: Returned only for applications that have requested an openid scope. See above for more info on OpenID Connect id_tokens. This parameter follows the guidelines of the OpenID Connect (OIDC) Core 1.0 specification. It is signed but not encrypted.
  • patient: For patient-facing workflows, this parameter identifies the FHIR ID for the patient on whose behalf authorization to the system was granted.
    • The patient's FHIR ID is not returned for provider-facing standalone launch workflows.
  • epic.dstu2.patient: For patient-facing workflows, this parameter identifies the DSTU2 FHIR ID for the patient on whose behalf authorization to the system was granted.
    • The patient's FHIR ID is not returned for provider-facing standalone launch workflows.
  • encounter: This parameter identifies the FHIR ID for the patient's encounter, if in context at time of launch. The encounter token corresponds to the FHIR Encounter resource.
    • The encounter FHIR ID is not returned for standalone launch workflows.
  • location: This parameter identifies the FHIR ID for the encounter department, if in context at time of launch. The location token corresponds to the FHIR Location resource.
    • The location FHIR ID is not returned for standalone launch workflows.
  • appointment: This parameter identifies the FHIR ID for the patient's appointment, if appointment context is available at time of launch. The appointment token corresponds to the FHIR Appointment resource.
    • The appointment FHIR ID is not returned for standalone launch workflows.
  • loginDepartment: This parameter identifies the FHIR ID of the user's login department for launches from Hyperspace. The loginDepartment token corresponds to the FHIR Location resource.
    • The loginDepartment FHIR ID is not returned for standalone launch workflows.

Note that you can pass additional parameters if needed based on the integration configuration. Here's an example of what a JSON object including an access token might look like:

{
  "access_token": "Nxfve4q3H9TKs5F5vf6kRYAZqzK7j9LHvrg1Bw7fU_07_FdV9aRzLCI1GxOn20LuO2Ahl5RkRnz-p8u1MeYWqA85T8s4Ce3LcgQqIwsTkI7wezBsMduPw_xkVtLzLU2O",
  "refresh_token": "H9TKs5F5vf6kRYAZqzK7j9L_07_FdV9aRzLCI1GxOn20LuO2Ahl5R1MeYWqA85T8s4sTkI7wezBsMduPw_xkLzLU2O",
  "token_type": "bearer",
  "expires_in": 3240,
  "scope": "Patient.read Patient.search ",
  "patient": "T1wI5bk8n1YVgvWk9D05BmRV0Pi3ECImNSK8DKyKltsMB"
}

At this point, authorization is complete and the web application can access the protected patient data it requested using FHIR APIs.

Step 4: Your Application Uses FHIR APIs to Access Patient Data

With a valid access token, your application can now access protected patient data from the EHR database using FHIR APIs. Queries must contain an Authorization header that includes the access token presented as a Bearer token.

Here's an example of what a valid query looks like:

GET https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/DSTU2/Patient/T1wI5bk8n1YVgvWk9D05BmRV0Pi3ECImNSK8DKyKltsMB HTTP/1.1
Authorization: Bearer Nxfve4q3H9TKs5F5vf6kRYAZqzK7j9LHvrg1Bw7fU_07_FdV9aRzLCI1GxOn20LuO2Ahl5RkRnz-p8u1MeYWqA85T8s4Ce3LcgQqIwsTkI7wezBsMduPw_xkVtLzLU2O

Step 5: Use a Refresh Token to Obtain a New Access Token

If your app uses refresh tokens (i.e. it can securely store credentials), then you can use a refresh token to request a new access token when the current access token expires (determined by the expires_in field from the authorization response from step 3).

There are two authentication options available for confidential apps that can keep authentication credentials secret:

  • Client Secret Authentication
  • JSON Web Token (JWT) Authentication

Using Refresh Tokens: If You Are Using Client Secret Authentication

Your application trades the refresh_token for a JSON object containing a new access token and contextual information by sending an HTTP POST to the token endpoint using a Content-Type HTTP header with value of "application/x-www-form-urlencoded". For more information, see RFC 6749 section 4.1.3.

The Epic on FHIR website can generate a client secret (effectively a password) for your app when using refresh tokens, and store the hashed secret for you, or Epic community members can upload a client secret hash that you provide them when they activate your app for their system. If you provide community members a client secret hash to upload, you should use a unique client secret per Epic community member and per environment type (non-production and production) for each Epic community member.

The following parameters are required in the POST body:

  • grant_type: This parameter always contains the value refresh_token.
  • refresh_token: The refresh token received from a prior authorization request.

An Authorization header using HTTP Basic Authentication is required, where the username is the URL encoded client_id and the password is the URL encoded client_secret.

For example, using the following client_id and client_secret:

client_id: d45049c3-3441-40ef-ab4d-b9cd86a17225

URL encoded client_id: d45049c3-3441-40ef-ab4d-b9cd86a17225 Note: base64 encoding Epic's client IDs will have no effect

client_secret: this-is-the-secret-2/7

URL encoded client_secret: this-is-the-secret-2%2F7

Would result in this Authorization header:

Authorization: Basic base64Encode{d45049c3-3441-40ef-ab4d-b9cd86a17225:this-is-the-secret-2%2F7}

or

Authorization: Basic ZDQ1MDQ5YzMtMzQ0MS00MGVmLWFiNGQtYjljZDg2YTE3MjI1OnRoaXMtaXMtdGhlLXNlY3JldC0yJTJGNw==

Here's an example of what a valid HTTP POST might look like:

POST https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token HTTP/1.1
Authorization: Basic ZDQ1MDQ5YzMtMzQ0MS00MGVmLWFiNGQtYjljZDg2YTE3MjI1OnRoaXMtaXMtdGhlLXNlY3JldC0yJTJGNw==
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=j12xcniournlsdf234bgsd

The authorization server responds to the HTTP POST request with a JSON object that includes the new access token. The response contains the following fields:

  • access_token: This parameter contains the new access token issued.
  • token_type: In Epic's OAuth 2.0 implementation, this parameter always includes the value bearer.
  • expires_in: This parameter contains the number of seconds for which the access token is valid.
  • scope: This parameter describes the access your application is authorized for.

An example response to the previous request may look like the following:

{
"access_token": "57CjhZEdiTcCh1nqIwQUw5rODOLP3bSTnMEGNYbBerSeNn8hIUm6Mlc5ruCTfQawjRAoR8sYr8S_7vNdnJfgRKfD6s8mOqPnvJ8vZOHjEvy7l3Ra9frDaEAUBbN-j86k",
"token_type": "bearer",
"expires_in": 3240,
"scope": "Patient.read Patient.search"
}

Using Refresh Tokens: If You Are Using JSON Web Token Authentication

Your application trades the refresh_token for a JSON object containing a new access token and contextual information by sending an HTTP POST to the token endpoint using a Content-Type HTTP header with value of "application/x-www-form-urlencoded". For more information, see RFC 6749 section 4.1.3.

You can use a one-time use JSON Web Token (JWT) to authenticate your app to the authorization server to exchange the refresh token for a new access token. There are several libraries for creating JWTs. See jwt.io for some examples. You'll pre-register a JSON Web Key Set or JWK Set URL for a given Epic community member environment on the Epic on FHIR website and then use the corresponding private key to create a signed JWT. Frontend apps using JWT authentication must use a JSON Web Key Set or JWK Set URL as static public keys are not supported.

See the Creating a Public Private Key Pair for JWT Signature and the Creating a JWT sections for details on how to create a signed JWT.

The following parameters are required in the POST body:

  • grant_type: This should contain the value refresh_token.
  • refresh_token: The refresh token received from a prior authorization request.
  • client_assertion_type: This should be set to urn:ietf:params:oauth:client-assertion-type:jwt-bearer.
  • client_assertion: This will be the one-time use JSON Web Token (JWT) your app generated using the steps mentioned above.

Here's an example of what a valid HTTP POST might look like:

POST https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkNDUwNDljMy0zNDQxLTQwZWYtYWI0ZC1iOWNkODZhMTcyMjUiLCJzdWIiOiJkNDUwNDljMy0zNDQxLTQwZWYtYWI0ZC1iOWNkODZhMTcyMjUiLCJhdWQiOiJodHRwczovL2ZoaXIuZXBpYy5jb20vaW50ZXJjb25uZWN0LWZoaXItb2F1dGgvb2F1dGgyL3Rva2VuIiwianRpIjoiZjllYWFmYmEtMmU0OS0xMWVhLTg4ODAtNWNlMGM1YWVlNjc5IiwiZXhwIjoxNTgzNTI0NDAyLCJuYmYiOjE1ODM1MjQxMDIsImlhdCI6MTU4MzUyNDEwMn0.dztrzHo9RRwNRaB32QxYLaa9CcIMoOePRCbpqsRKgyJmBOGb9acnEZARaCzRDGQrXccAQ9-syuxz5QRMHda0S3VbqM2KX0VRh9GfqG3WJBizp11Lzvc2qiUPr9i9CqjtqiwAbkME40tioIJMC6DKvxxjuS-St5pZbSHR-kjn3ex2iwUJgPbCfv8cJmt19dHctixFR6OG-YB6lFXXpNP8XnL7g85yLOYoQcwofN0k8qK8h4uh8axTPC21fv21mCt50gx59XgKsszysZnMDt8OG_G4gjk_8JnGHwOVkJhqj5oeg_GdmBhQ4UPuxt3YvCOTW9S2vMikNUnxrhdVvn2GVg

The authorization server responds to the HTTP POST request with a JSON object that includes the new access token. The response contains the following fields:

  • access_token: This parameter contains the new access token issued.
  • token_type: In Epic's OAuth 2.0 implementation, this parameter always includes the value bearer.
  • expires_in: This parameter contains the number of seconds for which the access token is valid.
  • scope: This parameter describes the access your application is authorized for.

An example response to the previous request may look like the following:

{
  "access_token": "57CjhZEdiTcCh1nqIwQUw5rODOLP3bSTnMEGNYbBerSeNn8hIUm6Mlc5ruCTfQawjRAoR8sYr8S_7vNdnJfgRKfD6s8mOqPnvJ8vZOHjEvy7l3Ra9frDaEAUBbN-j86k",
  "token_type": "bearer",
  "expires_in": 3240,
  "scope": "Patient.read Patient.search"
}

Offline Access for Native and Browser-Based Applications

For a native client app (for example, an iOS mobile app or a Windows desktop app) or a browser-based app (for example, a single-page application) to use the confidential app profile in the SMART App Launch framework, that app needs to use "additional technology (such as dynamic client registration and universal redirect_uris) to protect the secret." If you have a native client or browser-based app, you can register a dynamic client to integrate with Epic using the steps below.

Step 1: Get the initial access token you'll use to register a dynamic client

Your app uses a flow like the one described above for a Standalone Launch. Note your application will specifically not be using a client secret, and will use its initial client ID issued by Epic.

The end-result of this flow is your app obtaining an initial access token (defined in RFC 7591) used for registering a client instance (step 2). This token is intentionally one-time-use and should be used for registration immediately.

Step 2: Register the dynamic client

To register the dynamic client, your application needs to:

  1. Generate a public-private key pair on the user’s phone or computer.
    • Native apps can use the same strategies as backend apps to generate key sets
    • Browser-based apps can use the WebCrypto API
  2. Securely store that device-specific key pair on the user’s device.
    • Browser-based apps using WebCrypto should follow key storage recommendations
  3. Use the access token from step 1 to register a dynamic client using the OAuth 2.0 Dynamic Client Registration Protocol.

The client ID and private key should be associated with the Epic environment they are communicating with. Identify the Epic environment with its OAuth 2.0 server URL. If this is a backend integration or a subspace integration, the environment health system identifier (HSI) can be used to identify the environment instead.

This request must have two elements:

  • software_id: This parameter must contain the application’s client ID issued by Epic.
  • jwks: This parameter contains a JSON Web Key Set containing the public key from the key pair your application generated in step 2A.

Here’s an example of what an HTTP POST request to register a dynamic client might look like:


POST https://fhir.epic.com/interconnect-fhir-oauth/oauth2/register HTTP/1.1
Content-Type: application/json
Authorization: Bearer Nxfve4q3H9TKs5F5vf6kRYAZqzK7j9LHvrg1Bw7fU_07_FdV9aRzLCI1GxOn20LuO2Ahl5RkRnz-p8u1MeYWqA85T8s4Ce3LcgQqIwsTkI7wezBsMduPw_xkVtLzLU2O

{
    "software_id": "d45049c3-3441-40ef-ab4d-b9cd86a17225",
    "jwks": { 
        "keys": [{
                "e": "AQAB",
                "kty": "RSA",
                "n": "vGASMnWdI-ManPgJi5XeT15Uf1tgpaNBmxfa-_bKG6G1DDTsYBy2K1uubppWMcl8Ff_2oWe6wKDMx2-bvrQQkR1zcV96yOgNmfDXuSSR1y7xk1Kd-uUhvmIKk81UvKbKOnPetnO1IftpEBm5Llzy-1dN3kkJqFabFSd3ujqi2ZGuvxfouZ-S3lpTU3O6zxNR6oZEbP2BwECoBORL5cOWOu_pYJvALf0njmamRQ2FKKCC-pf0LBtACU9tbPgHorD3iDdis1_cvk16i9a3HE2h4Hei4-nDQRXfVgXLzgr7GdJf1ArR1y65LVWvtuwNf7BaxVkEae1qKVLa2RUeg8imuw",
            }
        ]
    }
}

The EHR stores your app's public key, issues it a dynamic client ID, and returns a response that might look like:


HTTP/1.1 201 Created
Content-Type: application/json

{
    "redirect_uris": [
        " https://fhir.epic.com/test/smart"
    ],
    "token_endpoint_auth_method": "none",
    "grant_types": [
        "urn:ietf:params:oauth:grant-type:jwt-bearer"
    ],
    "software_id": " d45049c3-3441-40ef-ab4d-b9cd86a17225",
    "client_id": "G65DA2AF4-1C91-11EC-9280-0050568B7514",
    "client_id_issued_at": 1632417134,
    "jwks": {
        "keys": [{
                "kty": "RSA",
                "n": "vGASMnWdI-ManPgJi5XeT15Uf1tgpaNBmxfa-_bKG6G1DDTsYBy2K1uubppWMcl8Ff_2oWe6wKDMx2-bvrQQkR1zcV96yOgNmfDXuSSR1y7xk1Kd-uUhvmIKk81UvKbKOnPetnO1IftpEBm5Llzy-1dN3kkJqFabFSd3ujqi2ZGuvxfouZ-S3lpTU3O6zxNR6oZEbP2BwECoBORL5cOWOu_pYJvALf0njmamRQ2FKKCC-pf0LBtACU9tbPgHorD3iDdis1_cvk16i9a3HE2h4Hei4-nDQRXfVgXLzgr7GdJf1ArR1y65LVWvtuwNf7BaxVkEae1qKVLa2RUeg8imuw",
                "e": "AQAB"
            }
        ]
    }
}

Step 3: Get an access token you can use to retrieve FHIR resources

Now that you have registered a dynamic client with its own credentials, there are two ways you can get an access token to make FHIR calls.  We recommend that you use the JWT bearer flow. In the JWT bearer flow, the client issues a JWT with its client ID as the "sub" (subject) claim, indicating the access token should be issued to the users who registered the client. It is also possible to use the SMART standalone launch sequence again to get a refresh token and then use the refresh token flow. Note that both flows involve your app making a JSON Web Token and signing it with its private key.

Generate a JSON Web Token

Your app follows the guidelines below to create a JWT assertion using its new private key.

To get an access token, your confidential app needs to authenticate itself.  Your dynamic client uses the private key it generated in step 2 to sign a JWT.

Here’s an example of the JWT body:


{
    "sub": "G00000000-0129-A6FA-7EDC-55B3FB6E85F8",
    "aud": "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token",
    "jti": "3fca7b08-e5a1-4476-9e4b-4e17f0ad7d1d",
    "nbf": 1639694983,
    "exp": 1639695283,
    "iat": 1639694983,
    "iss": "G00000000-0129-A6FA-7EDC-55B3FB6E85F8"
}

And here’s the final encoded JWT:

eyJhbGciOiJSUzI1NiIsImtpZCI6ImJXbGphR0ZsYkNBOE15QnNZWFZ5WVE9PSIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJHMDAwMDAwMDAtMDEyOS1BNkZBLTdFREMtNTVCM0ZCNkU4NUY4IiwiYXVkIjpbImh0dHBzOi8vdnMtaWN4LmVwaWMuY29tL0ludGVyY29ubmVjdC1DdXJyZW50LUZpbmFsLVByaW1hcnkvIl0sImp0aSI6IjNmY2E3YjA4LWU1YTEtNDQ3Ni05ZTRiLTRlMTdmMGFkN2QxZCIsIm5iZiI6MTYzOTY5NDk4MywiZXhwIjoxNjM5Njk1MjgzLCJpYXQiOjE2Mzk2OTQ5ODMsImlzcyI6IkcwMDAwMDAwMC0wMTI5LUE2RkEtN0VEQy01NUIzRkI2RTg1RjgifQ.UyW_--xpDdfB3EbxQ1KwRRuNDGIb034Y9mpExaaYyoVVkfz3ophzueIFMRrcdqTWmH96Ivx7O-fnjvHkb9iv1ELGVS_UYZy-JNb7r5VHDhdEYoRJzomYQzAyZutK9CJuJQZSvJeQLOXFoFN-fuCHIUBmSoTY5FDGy8gmq_a5fD_L-PnrXCD6SyP663s8kP-cFWh1iuXP0pjg8EMuFFEwgSo-chvv6RO4LIBebqkkv5qkgO_nrcXVJpxu42FDNrP2618q2Rw7sdIaewDw_I5026T4jNSoJXT12dHurqzedvC20exOO2MA6Vh1o2iVyzIfPE1EnEf-hk4WRFtQTUlqCQ

Step 3a: Get an access token using the JWT bearer flow

For the duration the user selected when approving your app to get an authorization code in step 1, your app can get an access token whenever it needs using the JWT bearer authorization grant described in RFC 7523 Section 2.1.

Recall that in the JWT bearer flow, the client issues a JWT to itself with a "sub" (subject) claim that indicates the user on whose behalf the client acts.  Because the dynamic client your app registered in step 2 is bound to the user who logged in during the SMART standalone launch sequence in step 1, the server only accepts a JWT where the subject is the user bound to that client.

Your app posts the JWT it issued itself in the Generate a JSON Web Token step to the server to get an access token. The following parameters are required in the POST body:

  • grant_type: This parameter contains the static value urn:ietf:params:oauth:grant-type:jwt-bearer.
  • assertion: This parameter contains the JWT your app generated above.
  • client_id: This parameter contains the dynamic client ID assigned in step 2.

Here’s an example of an HTTP POST request for an access token. You will replace the [assertion] and [client_id] placeholders with your own values.


POST https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=[assertion]&client_id=[client_id]

The authorization server responds to the HTTP POST request with a JSON object that includes an access token. The response contains the following fields:

  • access_token: This parameter contains the access token issued by Epic to your application and is used in future requests.
    • See the Epic-Issued OAuth 2.0 Tokens appendix section for details on handling access tokens.
  • token_type: In Epic's OAuth 2.0 implementation, this parameter always includes the value bearer.
  • expires_in: This parameter contains the number of seconds for which the access token is valid.
  • epic.dstu2.patient: This parameter identifies the DSTU2 FHIR ID for the patient, if a patient is in context at time of launch.
  • scope: This parameter describes the access your application is authorized for.
  • patient: This parameter identifies provides the FHIR ID for the patient.

Note that you can include additional fields in the response if needed based on the integration configuration. For more information, refer to Launching your apptopic. Here's an example of what a JSON object including an access token might look like:


{
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ1cm46R1RNUUFDVVI6Y2UuZ3RtcWFjdXIiLCJjbGllbnRfaWQiOiJHMDAwMDAwMDAtMDEyOS1BNkZBLTdFREMtNTVCM0ZCNkU4NUY4IiwiZXBpYy5lY2kiOiJ1cm46ZXBpYzpzMmN1cmd0IiwiZXBpYy5tZXRhZGF0YSI6ImVTMUF1dng4NlhRanRmVExub2loeks4QVRjdU5OUDFnRHhCRklvR0EyVUhHdlpDRE5PYjVZQ3NFZVhlUjExa1UyTjBHaHIwZjIwcE5yQnJ0V1JUa0l4d3VoVnlYeEVYUjQwVGlkdXNKdTk5ZmZRLWsybjFJWEY4X2I0SS11a054IiwiZXBpYy50b2tlbnR5cGUiOiJhY2Nlc3MiLCJleHAiOjE2Mzk2OTg1ODgsImlhdCI6MTYzOTY5NDk4OCwiaXNzIjoidXJuOkdUTVFBQ1VSOmNlLmd0bXFhY3VyIiwianRpIjoiYzQ1YzU4NjUtY2E3Ny00YWVlLTk3NDEtY2YzNzFiNDlhY2FkIiwibmJmIjoxNjM5Njk0OTg4LCJzdWIiOiJlRmdDY3E4cW1PeXY1b3FEcUFyUS5MQTMifQ.G0Z3PBv4ldpTqkjBjNUnvRYeVnNzT8qvLsGAVN8S9YibJGGCH_Txd6Ph1c9yB2hlQW3dw9IkaAvxxlUuclGMzmtyPXeo8wcWC07t_0vVasS-Ya9VjeDtR1hO8rcqEgV1DhKZ1jsEbzlRvKuZvONew0gL25ug6dOolNXcPcluzK6sxEyf2UoosX-W3nsU0iYZPJI-mf7lMEbsMUOSY8CR-77uBap3suxxHy03BwtkAXP0GwW0KSjOVe7_bsxX9k4DEhWyZuEOgDjEhONQFe2TeuWgUcI2KQeK5HjzmxN3dp56rCZ8zlhlukgw-C0F2IDbkZ5on7g7rl8lm29I7_kq9g",
    "token_type": "Bearer",
    "expires_in": 3600,
    "scope": "patient/Immunization.Read patient/Immunization.read patient/Patient.read patient/Practitioner.read patient/PractitionerRole.read launch/patient offline_access",
    "state": "oYK9nyFdUgTb1n_hMHZESDea",
    "patient": "eUYSU3eH0lceii-4SYNvpPw3",
    "__epic.dstu2.patient": "TJR7HfYCw58VnihLrp.axehccg-4IcjmZd3lw7Spm1F8B"
}

Step 3b: Alternate flow with refresh tokens

If you completed Step 3A, you do not need to complete this step. This is an alternate workflow to the JWT bearer flow. We strongly recommend that you use the JWT bearer flow above so you can provide a smoother user experience in which the user only needs to authenticate once. To get a refresh token, your app would need to go through the Standalone Launch sequence a second time. The first time (see Step 1: Get the access token you'll use to register a dynamic client) you were using the public app profile and weren't issued a refresh token.  Now that you have registered a dynamic client with its own credentials, you can use the confidential-asymmetric app profile.

Get an authorization code

This is the same as the Get an authorization code step from Step 1 except:

  • The client_id should be that of your dynamic client
  • Your token request will resemble a backend services grant, using the following parameters:
    • client_assertion – set to a JWT signed with your dynamic client’s private key
    • client_assertion_type – set to urn:ietf:params:oauth:client-assertion-type:jwt-bearer
    • An additional parameter per grant_type (code or refresh_token) described below

Note that the user will once again need to log in.

Exchange the authorization code for an access token

When your app makes this request to the token, it needs to authenticate itself to be issued a refresh token.  It uses the JWT it generated to authenticate itself per RFC 7523 section 2.2. This request will look like the JWT bearer request except the grant type is authorization_code instead of urn:ietf:params:oauth:grant-type:jwt-bearer and it includes the PKCE code verifier like the access token request you made before registering the dynamic client.

In response to this request, you’ll get an access token and a refresh token.

Use the refresh token to get subsequent access tokens

When the access token expires, your app can generate a new JWT and use the refresh token flow to get a new access token.  This request will look like the JWT bearer request except the grant type is refresh_token instead of urn:ietf:params:oauth:grant-type:jwt-bearer.

SMART Backend Services (Backend OAuth 2.0)

Contents

  • Building a Backend OAuth 2.0 App
  • Complete Required Epic Community Member Setup to Audit Access from Your Backend Application
  • Using a JWT to Obtain an Access Token for a Backend Service
    • Step 1: Creating the JWT
    • Step 2: POSTing the JWT to Token Endpoint to Obtain Access Token

Overview

Backend apps (i.e. apps without direct end user or patient interaction) can also use OAuth 2.0 authentication through the client_credentials OAuth 2.0 grant type. Epic's OAuth 2.0 implementation for backend services follows the SMART Backend Services: Authorization Guide, though it currently differs from that profile in some respects. Application vendors pre-register a public key for a given Epic community member on the Epic on FHIR website and then use the corresponding private key to sign a JSON Web Token (JWT) which is presented to the authorization server to obtain an access token.

Building a Backend OAuth 2.0 App

To use the client_credentials OAuth 2.0 grant type to authorize your backend application's access to patient information, two pieces of information need to be shared between the authorization server and your application:

  1. Client ID: The client ID identifies your application to authentication servers within the Epic community and allows you to connect to any organization.
  2. Public key: The public key is used to validate your signed JSON Web Token to confirm your identity.

You can register your application for access to both the sandbox and Epic organizations here. You'll provide information, including a JSON Web Key set URL (JWK Set URL or JKU) that hosts a public key used to verify the JWT signature. Epic will generate a client ID for you. Your app will need to have both the Backend Systems radio button selected and the Use OAuth 2.0 box checked in order to register a JKU for backend OAuth 2.0.

Complete Required Epic Community Member Setup to Audit Access from Your Backend Application

Note: This build is not needed in the sandbox. The sandbox automatically maps your client to a user, removing the need for this setup.

When performing setup with an Epic community member, their Epic Client Systems Administrator (ECSA) will need to map your client ID to an Epic user account that will be used for auditing web service calls made by your backend application. Your app will not be able to obtain an access token until this build is completed.

Using a JWT to Obtain an Access Token for a Backend Service

You will generate a one-time use JSON Web Token (JWT) to authenticate your app to the authorization server and obtain an access token that can be used to authenticate your app's web service calls. There are several libraries for creating JWTs. See jwt.io for some examples.

Step 1: Creating the JWT

See the Creating a Public Private Key Pair for JWT Signature and the Creating a JWT sections for details on how to create a signed JWT.

Step 2: POSTing the JWT to Token Endpoint to Obtain Access Token

Your application makes a HTTP POST request to the authorization server's OAuth 2.0 token endpoint to obtain access token. The following form-urlencoded parameters are required in the POST body:

  • grant_type: This should be set to client_credentials.
  • client_assertion_type: This should be set to urn:ietf:params:oauth:client-assertion-type:jwt-bearer.
  • client_assertion: This should be set to the JWT you created above.

Here is an example request:

POST https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkNDUwNDljMy0zNDQxLTQwZWYtYWI0ZC1iOWNkODZhMTcyMjUiLCJzdWIiOiJkNDUwNDljMy0zNDQxLTQwZWYtYWI0ZC1iOWNkODZhMTcyMjUiLCJhdWQiOiJodHRwczovL2ZoaXIuZXBpYy5jb20vaW50ZXJjb25uZWN0LWZoaXItb2F1dGgvb2F1dGgyL3Rva2VuIiwianRpIjoiZjllYWFmYmEtMmU0OS0xMWVhLTg4ODAtNWNlMGM1YWVlNjc5IiwiZXhwIjoxNTgzNTI0NDAyLCJuYmYiOjE1ODM1MjQxMDIsImlhdCI6MTU4MzUyNDEwMn0.dztrzHo9RRwNRaB32QxYLaa9CcIMoOePRCbpqsRKgyJmBOGb9acnEZARaCzRDGQrXccAQ9-syuxz5QRMHda0S3VbqM2KX0VRh9GfqG3WJBizp11Lzvc2qiUPr9i9CqjtqiwAbkME40tioIJMC6DKvxxjuS-St5pZbSHR-kjn3ex2iwUJgPbCfv8cJmt19dHctixFR6OG-YB6lFXXpNP8XnL7g85yLOYoQcwofN0k8qK8h4uh8axTPC21fv21mCt50gx59XgKsszysZnMDt8OG_G4gjk_8JnGHwOVkJhqj5oeg_GdmBhQ4UPuxt3YvCOTW9S2vMikNUnxrhdVvn2GVg

And here is an example response body assuming the authorization server approves the request:

{
"access_token": "i82fGhXNxmidCt0OdjYttm2x0cOKU1ZbN6Y_-zBvt2kw3xn-MY3gY4lOXPee6iKPw3JncYBT1Y-kdPpBYl-lsmUlA4x5dUVC1qbjEi1OHfe_Oa-VRUAeabnMLjYgKI7b",
"token_type": "bearer",
"expires_in": 3600,
"scope": "Patient.read Patient.search"
}
  • Note: See the Epic-Issued OAuth 2.0 Tokens appendix section for details on handling access tokens.

JSON Web Tokens (JWTs)

Contents

  • Creating a Public Private Key Pair for JWT Signature
    • OpenSSL
    • Windows PowerShell
    • Finding the Public Key Certificate Fingerprint (Also Called Thumbprint)
  • Creating a JWT
  • JSON Web Key Sets
    • Key Identifier Requirements
  • Hosting a JWK Set URL
    • App-Level vs Licensing Specific JWK Set URLs
    • Registering a JWK Set URL
    • Key Rotation
  • Providing Multiple Public Keys
    • How
    • Why Not?
    • Use Cases

Background

JSON Web Tokens (JWTs) can be used for some OAuth 2.0 workflows. JWTs are required to be used by the client credentials flow used by backend services, and can be used, as an alternative to client secrets, for apps that use the confidential client profile or refresh tokens. You will generate a one-time use JWT to authenticate your app to the authorization server and obtain an access token that can be used to authenticate your app's web service calls, and potentially a refresh token that can be used to get another access token without a user needing to authorize the request. There are several libraries for creating JWTs. See jwt.io for some examples.

Creating a Public Private Key Pair for JWT Signature

There are several tools you can use to create a key pair. As long as you can export your public key to a base64 encoded X.509 certificate (backend clients) or JSON Web Key Set (frontend confidential clients) for registration on the Epic on FHIR website, or you can host it on a properly formatted JWK Set URL, the tool you use to create the key pair and file format used to store the keys is not important. If your app is hosted by Epic community members ("on-prem" hosting) instead of cloud hosted, you should have unique key pairs for each Epic community member you integrate with. In addition, you should always use unique key pairs for non-production and production systems.

Here are examples of two tools commonly used to generate key pairs:

Creating a Public Private Key Pair: OpenSSL

You can create a new private key named privatekey.pem using OpenSSL with the following command:

openssl genrsa -out /path_to_key/privatekey.pem 2048

Make sure the key length is at least 2048 bits.

For backend apps, you can export the public key to a base64 encoded X.509 certificate named publickey509.pem using this command:

openssl req -new -x509 -key /path_to_key/privatekey.pem -out /path_to_key/publickey509.pem -subj '/CN=myapp'

Where '/CN=myapp' is the subject name (for example the app name) the key pair is for. The subject name does not have a functional impact in this case but it is required for creating an X.509 certificate.

For frontend confidential clients, refer to JSON Web Key Sets.

Creating a Public Private Key Pair: Windows PowerShell

You can create a key pair using Windows PowerShell with this command, making sure to run PowerShell as an administrator:

New-SelfSignedCertificate -Subject "MyApp"

Note that the Epic on FHIR website is extracting the public key from the certificate and discarding the rest of the certificate information, so it's not 'trusting' the self-signed certificate per se.

The subject name does not have a functional impact in this case, but it is required for creating an X.509 certificate. Note that this is only applicable to backend apps. For frontend confidential clients, refer to JSON Web Key Sets.

You can export the public key using either PowerShell or Microsoft Management Console.

If you want to export the public key using PowerShell, take note of the certificate thumbprint and storage location printed when you executed the New-SelfSignedCertificate command.

PS C:\dir> New-SelfSignedCertificate -Subject "MyApp"

PSParentPath: Microsoft.PowerShell.Security\Certificate::LocalMachine\MY

Thumbprint                                Subject
----------                                -------
3C4A558635D67F631E5E4BFDF28AE59B2E4421BA  CN=MyApp

Then export the public key to a X.509 certificate using the following commands (using the thumbprint and certificate location from the above example):

PS C:\dir> $cert = Get-ChildItem -Path Cert:\LocalMachine\My\3C4A558635D67F631E5E4BFDF28AE59B2E4421BA

PS C:\dir> Export-Certificate -Cert $cert -FilePath newpubkeypowerShell.cer

Export the binary certificate to a base64 encoded certificate:

PS C:\dir> certutil.exe -encode newpubkeypowerShell.cer newpubkeybase64.cer

If you want to export the public key using Microsoft Management Console, follow these steps:

  1. Run (Windows + R) > mmc (Microsoft Management Console).
  2. Go to File > Add/Remove SnapIn.
  3. Choose Certificates > Computer Account > Local Computer. The exact location here depends on the certificate store that was used to create and store the keys when you ran the New-SelfSignedCertificate PowerShell command above. The store used is displayed in the printout after the command completes.
  4. Then back in the main program screen, go to Personal > Certificates and find the key pair you just created in step 1. The "Issue To" column will equal the value passed to -Subject during key creation (e.g. "MyApp" above).
  5. Right click on the certificate > All Tasks > Export.
  6. Choose option to not export the private key.
  7. Choose to export in base-64 encoded X.509 (.CER) format.
  8. Choose a file location.

Finding the Public Key Certificate Fingerprint (Also Called Thumbprint)

The public key certificate fingerprint (also known as thumbprint in Windows software) is displayed for JWT signing public key certificates that are uploaded to the Epic on FHIR website. There are a few ways you can find the fingerprint for a public key certificate:

  • If you created the public key certificate using Windows PowerShell using the steps above, the thumbprint was displayed after completing the command. You can also print public keys and their thumbprints for a given certificate storage location using the Get-ChildItem PowerShell command.

    For example, run Get-ChildItem -Path Cert:\LocalMachine\My to find all certificate thumbprints in the local machine storage.

  • You can follow the steps here to find the thumbprint of a certificate in Microsoft Management Console.
  • You can run this OpenSSL command to print the public key certificate fingerprint that would be displayed on the Epic on FHIR website, replacing openssl_publickey.cert with the name of your public key certificate:

    $ openssl x509 -noout -fingerprint -sha1 -inform pem -in openssl_publickey.cert

    Note that the output from this command includes colons between bytes which are not shown on the Epic on FHIR website.

Creating a JWT

The JWT should have these headers:

Header Description
alg The JWT authentication algorithm. Epic supports the following RSA signing algorithms for all confidential clients: RS256, RS384, and RS512. For clients that register a JSON Web Key Set URL, Epic also supports the following Elliptic Curve algorithms: ES256, ES384. Epic does not support manual upload of Elliptic Curve public keys, and we recommend new apps use a JWK Set URL and Elliptic Curve keys.
typ If provided, this should always be set to JWT.
kid For apps using JSON Web Key Sets (including dynamically registed clients), set this value to the kid of the target public key from your key set
jku For apps using JSON Web Key Set URLs, optionally set this value to the URL you registered on your application

The JWT header should be formatted as follows:

{
"alg": "RS384",
"typ": "JWT"
}

The JWT should have these claims in the payload:

Claim Description Remarks
iss Issuer of the JWT. This is the app's client_id, as determined during registration on the Epic on FHIR website, or the client_id returned during a dynamic registration This is the same as the value for the sub claim.
sub Issuer of the JWT. This is the app's client_id, as determined during registration on the Epic on FHIR website, or the client_id returned during a dynamic registration This is the same as the value for the iss claim.
aud The FHIR authorization server's token endpoint URL. This is the same URL to which this authentication JWT will be posted. See below for an example POST. It's possible that Epic community member systems will route web service traffic through a proxy server, in which case the URL the JWT is posted to is not known to the authorization server, and the JWT will be rejected. For such cases, Epic community member administrators can add additional audience URLs to the allowlist, in addition to the FHIR server token URL if needed.
jti A unique identifier for the JWT. The jti must be no longer than 151 characters and cannot be reused during the JWT's validity period, i.e., before the exp time is reached. In the November 2024 version of Epic and later, this value must be a string.
exp Expiration time integer for this authentication JWT, expressed in seconds since the "Epoch" (1970-01-01T00:00:00Z UTC). The exp value must be in the future and can be no more than 5 minutes in the future at the time the access token request is received.
nbf Time integer before which the JWT must not be accepted for processing, expressed in seconds since the "Epoch" (1970-01-01T00:00:00Z UTC). The nbf value cannot be in the future, cannot be more recent than the exp value, and the exp - nbf difference cannot be greater than 5 minutes.
iat Time integer for when the JWT was created, expressed in seconds since the "Epoch" (1970-01-01T00:00:00Z UTC). The iat value cannot be in the future, and the exp - iat difference cannot be greater than 5 minutes.

Here's an example JWT payload:

{
"iss": "d45049c3-3441-40ef-ab4d-b9cd86a17225",
"sub": "d45049c3-3441-40ef-ab4d-b9cd86a17225",
"aud": "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token",
"jti": "f9eaafba-2e49-11ea-8880-5ce0c5aee679",
"exp": 1583524402,
"nbf": 1583524102,
"iat": 1583524102
}

The header and payload are then base64 URL encoded, combined with a period separating them, and cryptographically signed using the private key to generate a signature. Conceptually:

signature = RSA-SHA384(base64urlEncoding(header) + '.' + base64urlEncoding(payload), privatekey)

The full JWT is constructed by combining the header, body and signature as follows:

base64urlEncoding(header) + '.' + base64urlEncoding(payload) + '.' + base64urlEncoding(signature)

A fully constructed JWT looks something like this:

eyJhbGciOiJSUzM4NCJ9.eyJpYXQiOjE3MDc0MjQzMzYsImlzcyI6IjAxZjBlMzllLTIyY2MtNGFiNS05NTE0LWMyN2Y5Mzg0NzFjOCIsInN1YiI6IjAxZjBlMzllLTIyY2MtNGFiNS05NTE0LWMyN2Y5Mzg0NzFjOCIsImF1ZCI6Imh0dHBzOi8vdmVuZG9yc2VydmljZXMuZXBpYy5jb20vaW50ZXJjb25uZWN0LWFtY3VycHJkLW9hdXRoL29hdXRoMi90b2tlbiIsImV4cCI6MTcwNzQyNDYzNiwianRpIjoiMGIyZjZmMTQtMThkZS01NTU1LWJkMjUtZTNkMzg0ZmEwZTVkIn0.hE8DAq72XjJWNci5JFEuN5TrLh9W1UZP4H904GTIhggZNnNxtJLvUPKPrpf9PYgMZmgHb6peVR5MMUnQRtFHLZsH_C2v9ridfIA8BgWBL5eNiXcriUM9rIEvHwImgbKyPQUY_a0D7BCXiLqdjMAdEZe_afxCQBW0I7EsGqbpA0agayhLIDrlhcfCtdBrGyBlwW2zlUjTUo8Rj2o59tL90w6mVykeZIsJfcLQF4jeSfNOko-908mha_Gga6szWCqxlCr5ed9i6mjRSUQByG-GAFnCIhPULeedCHEgb9WF2NC0cImjK92jGSM6i43Y19uHvCFYMEkBMDaDK7WQigm3yg

JSON Web Key Sets

Applications using JSON Web Token (JWT) authentication can provide their public keys to Epic as a JSON Web Key (JWK) Set. Epic accepts keys of type RSA or EC. EC is only support for JWKS returned from a JWKS URL and is not supported for static JWKS uploaded directly. When using a JWKS URL we recommend including the kid values in your keys and in the headers of your assertions. Note that when uploading a static JWKS directly, only the "kty", "e" and "n" properties will be saved from the uploaded keys. Other fields typically present in a JWKS like "kid" or "alg" can exist in the uploaded JWKS without an error but they will be discarded upon save.

In the example key set below, the "RSA" key can be used with the RS256, RS384 or RS512 signing algorithms. The "EC" key can only be used with the "ES384" algorithm because it specifies a curve of P-384. The algorithm is determined when you first generate the key, like in the example above.

        {
            "keys": [
                { "kty":"RSA", "e": "AQAB", "kid": "d1fc1715-3299-43ec-b5de-f943803314c2", "n": "uPkpNCkqbbismKNwKguhL0P4Q40sbyUkUFcmDAACqBntWerfjv9VzC3cAQjwh3NpJyRKf7JvwxrbELvPRMRsXefuEpafHfNAwj3acTE8xDRSXcwzQwd7YIHmyXzwHDfmOSYW7baAJt-g_FiqCV0809M9ePkTwNvjpb6tlJu6AvrNHq8rVn1cwvZLIG6KLCTY-EHxNzsBblJYrZ5YgR9sfBDo7R-YjE8c761PSrBmUM4CAQHtQu_w2qa7QVaowFwcOkeqlSxZcqqj8evsmRfqJWoCgAAYeRIsgKClZaY5KC1sYHIlLs2cp2QXgi7rb5yLUVBwpSWM4AWJ_J5ziGZBSQYB4sWWn8bjc5-k1JpUnf88-UVZv9vrrkMJjNam32Z6FNm4g49gCVu_TH5M83_pkrsNWwCu1JquY9Z-eVNCsU_AWPgHeVZyXT6giHXZv_ogMWSh-3opMt9dzPwYseG9gTPXqDeKRNWFEm46X1zpcjh-sD-8WcAlgaEES6ys_O8Z" },
                { "kty":"EC", "kid": "iTqXXI0zbAnJCKDaobfhkM1f-6rMSpTfyZMRp_2tKI8", "crv": "P-384", "x": "C1uWSXj2czCDwMTLWV5BFmwxdM6PX9p-Pk9Yf9rIf374m5XP1U8q79dBhLSIuaoj", "y": "svOT39UUcPJROSD1FqYLued0rXiooIii1D3jaW6pmGVJFhodzC31cy5sfOYotrzF" }
            ]
        }	
    

Key Identifier Requirements

Epic recommends your application provide the kid field in JWK Sets and in your JWT Headers when using a JWKS URL, since Epic can use the identifier to find your intended key. Epic does not require your key identifiers to be thumbprints, and will accept any value that is unique within your key set.

Hosting a JWK Set URL

Starting in the February 2024 version of Epic, customers can require back-end apps using JWT authentication to use a JSON Web Key Set URL to provide public keys to Epic for JWT verification. JWK Set URLs streamline implementation and make key rotation feasible by providing a centralized and trusted place where Epic community members can retrieve your public keys.

App-Level vs Licensing Specific JWK Set URLs

By default, every community member will use the app-level JWK Set URLs when validating your application's JWTs. These are the URLs you provide when building your application. Re-using these default URLs for all your integrations is appropriate for cloud-based apps that can re-use a single private key (without making a copy).

However, if your application is hosted by Epic community members ("on-prem", applications), then you should instead provide different JWK Set URLs for every licensed community member. There are 2 reasons on-prem applications should use distinct URLs:

  1. These applications require physically separate servers, and cannot share a private key across installations without copying and transporting the key (a security risk)
  2. Attempting to aggregate multiple public keys behind one URL is a security risk if those keys are in physically different places. If any one of your server environments is compromised, it can be used to impersonate any other instance of your application

Registering a JWK Set URL

When building or licensing an application that will authenticate against Epic, you are prompted to optionally upload non-production and production JWK Set URLs to your app. When registering these URLs, be sure of the following:

  1. Are secured with TLS
  2. Are publicly accessible
  3. Do not require authentication
  4. Will not change over time
  5. Are responsive

Epic will verify points 1-3 whenever you provide a JWK Set URL

Note that there are separate non-production and production URLs because we recommend you use separate key sets for each.

Key Rotation

Before rotating your keys, remove any static public keys from your existing downloads

One benefit of JWK Set URLs is that your application can rotate its key as needed, and Epic can dynamically fetch the updated keys. It is a best practice to periodically rotate your private keys, and if you choose to do so, we recommend the following strategy:

  1. Create your new public-private key pair ahead of time
  2. Add your new public key to your JWK Set in addition to the existing one
  3. Start using your new private key for signing JWTs
  4. Once you are certain your system is no longer using the old private key, wait 5 minutes (the maximum JWT expiration time) remove the old public key from your JWK Set

In between steps 3 and 4, Epic will notice you are using a new private key and will automatically fetch the new key set. Every time you present a JWT to Epic we will check your JWK Set URL at most once, and only if the cached keys are expired or did not verify your JWT

Epic's default and maximum cache time is 29 hours, but your endpoint can override the default by using the Cache-Control HTTP response header. This is most useful during development and testing when you may want to update your non-production public keys ad-hoc, or to test the availability of your server from Epic. You could for instance respond with "Cache-Control": "no-store" to stop Epic from storing your keys at all. This is not recommended for production usage since you lose the performance benefits of caching.

Providing Multiple Public Keys

How

Apps can provide multiple public keys to an Epic community member by providing a JWK Set URL that contains multiple keys.

Why Not?

In general, the only reason to provide multiple public keys is if your system spans multiple separate servers or cloud environments, and so you cannot share a private key between them. If you can re-use a key without making copies of it, then you should re-use that key and focus on protecting it with the best policy available (For example, using your hosting provider's key store or using a Trusted Platform Module).

Use Cases

Example use cases:

  • Having multiple isolated testing environments, where you may provide multiple non-production public keys (one for each environment)
  • On-premises applications with multiple servers hosted by a given community member. In this case you would provide multiple production public keys (one for each server)

Appendix

Epic-Issued OAuth 2.0 Tokens

Starting in the May 2020 version of Epic, Epic community members can enable a feature that makes all OAuth 2.0 tokens and codes into JSON Web Tokens (JWTs) instead of opaque tokens. This feature increases the length of these tokens significantly and is enabled in the fhir.epic.com sandbox. Developers should ensure that app URL handling does not truncate OAuth 2.0 tokens and codes.

The format of these tokens is helpful for troubleshooting, but Epic strongly recommends against relying on these tokens being JWTs. This same feature can be turned off by Epic community members, and Epic may need to change the format of these tokens in the future.

In general, if the "aud" parameter of a JWT is not your app's client ID, you should not be decoding it programmatically. For the 5 types of JWTs mentioned in this tutorial, here's a breakdown of their audience (aud) and issuers (iss):

JWT Scenario Audience (aud) Issuer (iss)
Launch Token Authorization server Authorization server
Authorization Code Authorization server Authorization server
Access Token Authorization server Authorization server
Openid Connect ID Token The app's client_id Authorization server
Client Assertion Authorization server The app's client_id

Trusting an OAuth 2.0 Token

A common question that developers ask is how an app can "trust" an OAuth 2.0 token without cryptographically verifying it. Our answer is that your app should never trust a token by itself and should rely on the authorization server to verify a given token. When redeeming an authorization code for an access token, your app can trust that if the request succeeded that the authorization code was valid, and you can then create a session in your application. The only exception is an openid connect id_token, which is designed to be verified by the client application.

Epic-Issued OAuth 2.0 Tokens

Starting in the May 2020 Epic version, Epic community members can enable a feature that makes all OAuth 2.0 tokens and codes into JSON Web Tokens (JWTs) instead of opaque tokens. This feature increases the length of these tokens significantly and is enabled in the vendorservices.epic.com sandbox. Developers should ensure that app URL handling does not truncate OAuth 2.0 tokens and codes.

The format of these tokens is helpful for troubleshooting, but Epic strongly recommends against relying on these tokens being JWTs. This same feature can be turned off by Epic community members, and Epic may need to change the format of these tokens in the future.

In general, if the "aud" parameter of a JWT is not your app's client ID, you should not be decoding it programmatically. For the 5 types of JWTs mentioned in this tutorial, here's a breakdown of their audience (aud) and issuers (iss):

JWT Scenario Audience (aud) Issuer (iss)
Launch Token Authorization server Authorization server
Authorization Code Authorization server Authorization server
Access Token Authorization server Authorization server
Openid Connect ID Token The app's client_id Authorization server
Client Assertion Authorization server The app's client_id

Trusting an OAuth 2.0 Token

A common question that developers ask is how an app can "trust" an OAuth 2.0 token without cryptographically verifying it. Our answer is that your app should never trust a token by itself and should rely on the authorization server to verify a given token. When redeeming an authorization code for an access token, your app can trust that if the request succeeded that the authorization code was valid, and you can then create a session in your application. The only exception is an openid connect id_token, which is designed to be verified by the client application.

How you handle the authorization code is the most important step in an OAuth 2.0 flow. See our best practice for using this step to secure your application.

Refresh Tokens and Persistent Access Periods

The time during which a client can use refresh tokens to get new access is known as the persistent access period. In patient-facing contexts, it can be set by the patient through MyChart. In non-patient-facing contexts, the duration will be set by the refresh token itself. Specifically, the start of the period is set to the time the token is provisioned and the end of the period is set to the token's expiration date-time.

In many situations, only one access token will want to be requested with the original refresh token and the persistent access period just defines when the that refresh token can do so. However, a client will often need access over a longer period of time. In these situations, a single refresh token won't be an efficient solution as a new authorization request will have to be made after the token is used or the period ends. There are two workflows for getting an extended persistent access period by utilizing multiple refresh tokens: rolling refresh tokens and indefinite access.

Rolling Refresh Persistent Access Periods

In this workflow, multiple refresh tokens are used to have the persistent access period last as long as a single refresh token could. A first refresh token is provisioned and the persistent access period is set as normal (i.e., start of period = time of provisioning and end of period = token expiration date-time). This first refresh token can then be used in another access token request that specifies to return a refresh token. The refresh token granted from this access token request will be generated normally except for the fact that its expiration date-time will be set to the same as the first refresh token. All subsequent refresh tokens in the workflow will also have this same expiration date-time.

This means that the persistent access period of a rolling refresh workflow is defined by the first refresh token and all subsequent refresh tokens 'fall within' that period. The below diagram gives a visual representation of the persistent access period during a rolling refresh token workflow.

For patient-facing apps, the length of the persistent access period for a rolling refresh will be set by each patient during the OAuth 2.0 flow. They can choose from a variety of options (e.g., 1 hours, 1 day, 1 week, …) configured by the customer.

To enable rolling refresh access for non-patient-facing apps, check that your customers have properly configured their external client record.

Indefinite Persistent Access Periods

In this workflow, the persistent access period lasts indefinitely. It does so by using consecutive refresh tokens like in rolling refresh workflows. The notable difference is that, here, new refresh tokens have an expiration date-time greater than the one before it. In the rolling refresh workflow, this is always set to that of the first token. Since the persistent access period end date-time is set by the token's expiration, continually setting new refresh tokens to expire in the future will make the persistent access period last until no more refresh tokens are requested.

To enable indefinite access for patient-facing apps, instruct your customers to follow these configuration steps. Note that this only gives patients the option of indefinite access. Each patient still chooses their max access period.

  1. Navigate to Login and Access Configuration in MyChart.
  2. Navigate to OAuth Access Duration Configuration.
  3. Specify 'Indefinite' in the Global Max OAuth Access Duration field.

To enable indefinite access for non-patient-facing apps, check that your customers have properly configured their external client records.

Limit Use of Localhost URIs

Launched applications must register a list of URLs that Epic will use as an allowlist during OAuth 2.0. Epic will only deliver authorization codes to a URL that is listed on the corresponding app.

As a security best practice, apps should not include localhost/loopback URIs in this list. An exception is for Native applications which are designed to use localhost for the redirect, such as certain libraries available from the AppAuth group. An alternative available for native apps are platform specific link associations, discussed in our Considerations for Native Apps below.

If your developers wish to use localhost URIs for testing in our sandboxes, we recommend creating a second test application that will never be activated in customer environments and listing your localhost URLs there.

Considerations for Native Apps

Additional security considerations must be taken into account when launching a native app to sufficiently protect the authorization code from malicious apps running on the same device. There are a few options for how to implement this additional layer of security:

  • Platform-specific link association: Android, iOS, Windows, and other platforms have introduced a method by which a native app can claim an HTTPS redirect URL, demonstrate ownership of that URL, and instruct the OS to invoke the app when that specific HTTPS URL is navigated to. Apple's iOS calls this feature "Universal Links"; Android, "App Links"; Windows, "App URI Handlers". This association of an HTTPS URL to native app is platform-specific and only available on some platforms.
  • Proof Key for Code Exchange (PKCE): This is a standardized, cross-platform technique for public clients to mitigate the threat of authorization code interception. However, it requires additional support from the authorization server and app. PKCE is described in IETF RFC 7636 and supported starting in the August 2019 version of Epic. Note that the Epic implementation of this standard uses the S256 code_challenge_method. The "plain" method is not supported.

Starting in the August 2019 version of Epic, the Epic implementation of OAuth 2.0 supports PKCE and Epic recommends using PKCE for native mobile app integrations. For earlier versions or in cases where PKCE cannot be used, Epic recommends the use of Universal Links/App Links in lieu of custom redirect URL protocol schemes.

Subspace Security Considerations

This section applies to certain applications with a user type of "Backend Systems". If your application does not fit this criterion, you can skip this section. You can also skip this section if your Backend application does not use Subspace APIs; in which case you should remove the "Subspace" functionality and un-check the "Can Register Dynamic Clients" checkbox.

Beginning March 2, 2023, Epic no longer supports registering new client IDs with the following combination of settings:

  1. User type of Backend Systems

  2. Uses OAuth 2.0

  3. Functionality: Subspace

  4. Registers Dynamic Clients

  5. Functionality: Incoming Web Services

The combination of settings 1-4 has historically been used for integration with Epic's Subspace Communication Framework. In this approach your application registers workstation-specific private keys to authorize Subspace API calls.

As a security best practice, for integrations using the above settings, instead it is recommended to authenticate the Incoming Web Services (Setting 5) using the OAuth 2.0 token obtained from a SMART on FHIR app launch or configuring your integration to use a multi-context application.

When a workstation-specific private key is allowed to authorize web service calls, a user may be able to bypass user-specific authentication and authorization checks within the web services themselves. This risk is mitigated in virtualized environments where the workstation (and the private key) is controlled by administrators. These security implications do not apply to Subspace APIs as Subspace APIs provide access to the Epic database only after the user has authenticated against Epic.

As a security best practice, rather than using the combination of settings above, an application that needs all these features (particularly web services) can do one of the following:

SMART App Launch

We recommend following the FHIRcast specification's guidance, which says to use the SMART App Launch to secure communication. One reason to use the SMART App Launch for Subspace integrations is that the launch provides user-specific security. The token provided by the launch is designed for end-users to use directly and provides the flexibility for you to call web services from any portion of your infrastructure.

Per the FHIRcast specification, the hub needs to launch your application before you can use Subspace APIs. Practically, this means your end users need to launch your application from Epic to begin their workflow. Epic's "User Toolbar" launch approach provides the ability for the user to launch your application before opening any patient and provides your application with authorization to view multiple patients' charts without re-launching. This requires coordination with the Epic Community Member to show the launch button to end users and requires instructing end users to start their workflow in this way.

By default, launched Subspace applications will receive 1 hour of API authorization per launch. If your application requires longer workflows, we offer the following options:

  1. Epic offers a Dynamic Client Registration (DCR) workflow where you can register a temporary and in-memory private key to extend your application's access. Epic will revoke the key's access after a period of inactivity, so you should request a new access token before the most recent one expires. After the end user closes your application, you should delete the private key and Epic will revoke its access due to inactivity.

    Note this workflow differs from the "Backend System" DCR workflow above, in that tokens issued to the client are tied to the user who launched the application. In this way, Epic can conduct user-specific security checks when your application calls web services.

  2. (Not recommended) Epic Community Members can extend the default duration of your access tokens to any value, for example 4 hours.

Multi-Context Application

In this approach, your application handles the burden of authenticating the end-user and mediating their access to the Epic database. This can only be done securely by using server-side controls; safeguards put in place in a client-application (example: a native desktop app) are not sufficient, as they could potentially be bypassed by an end user.

An existing "Backend System" Subspace integration can achieve this with the following:

  1. Created a new client ID with the following settings:

    1. User type of Backend Systems

    2. Uses OAuth 2.0

    3. Feature: Incoming API

    4. Select your required Web Services

  2. Conduct Backend OAuth 2.0 as you did with your existing app, but skip the DCR step

  3. Follow our security best practices for Backend applications, which solve the user-authentication issues above

SMART Scopes

SMART scopes are returned in the scope parameter of the OAuth 2.0 token endpoint response and determine which resources an application has permission to access and what actions the application is permitted to take. The scopes provided are based on the scopes that your app requested in your authorize request as well as the incoming APIs you have selected on your app page.

As of the August 2024 version of Epic, both SMART v1 and SMART v2 scope formatting are supported. Prior to the August 2024 version, only SMART v1 scopes are supported.

The actions defined by SMART v1 scopes are .read and .write. SMART v2 scopes use CRUDS syntax and actions include: .c – create, .r – read, .u – update, .d – delete, and .s – search.

For example, the Observation.Create resource corresponds to either:

  • SMART v1: {user-type}/Observation.write
  • SMART v2: {user-type}/Observation.c

Which version should you select?

If your app uses SMART v1 scopes or if your app does not use SMART scopes at all, you should select SMART v1.

If your app uses SMART v2 scopes, you should select SMART v2.

Note: Prior to the August 2024 version of Epic, SMART v1 scope formatting will be returned regardless of the SMART Scope Version selected on your app page. If your app uses SMART scopes and your customer is on a version of Epic earlier than August 2024, you must support SMART v1 scopes.

Non-OAuth 2.0 Authentication

Epic supports forms of authentication in addition to OAuth 2.0. Only use these forms of authentication if OAuth 2.0 is not an option.

HTTP Basic Authentication

HTTP Basic Authentication requires that Epic community members provision a username and password that your application will provide to the web server to authenticate every request.

Epic supports HTTP Basic Authentication via a system-level user record created within Epic's database. You may hear this referred to as an EMP record. You will need to work with an organization's Epic user security team to have a username and password provided to you.

When calling Epic's web services with HTTP Basic Authentication the username must be provided as follows: emp$<username>. Replace <username> with the username provided by the organization during implementation of your application.

Base64 encode your application's credentials and pass them in the HTTP Authorization header. For example, if you've been given a system-level username/password combination that is username/Pa$$w0rd1, the Authorization header would have a value of ZW1wJHVzZXJuYW1lOlBhJCR3MHJkMQ== (the base64 encoded version of emp$username:Pa$$w0rd1):

GET http://localhost:8888/website HTTP/1.1
Host: localhost:8888
Proxy-Connection: keep-alive
Authorization: Basic ZW1wJHVzZXJuYW1lOlBhJCR3MHJkMQ==

Storing HTTP Basic Authentication Values

The username and password you use to authenticate your web service requests are extremely sensitive since they can be used to pull PHI out of an Epic organization's system. The credentials should always be encrypted and should not be stored directly within your client's code. You should make sure that access to decrypt the credentials should be limited to only the users that need access to it. For example, if a service account submits the web service requests, only that service account should be able to decrypt the credentials.

Client Certificates and SAML tokens

Epic also supports the use of Client Certificates and SAML tokens as an authentication mechanism for server-to-server web service requests. We do not prefer these methods because both require web server administrators to maintain a trusted certificate for authentication. As certificates expire or servers need to be moved around, this puts an additional burden on system administrators.

Additional Required Headers

When you use OAuth 2.0 authentication, Epic can automatically gather important information about the client making a web service request. When you use a non-OAuth 2.0 authentication mechanism, we require that this additional information be passed in an HTTP header.

Epic-Client-ID

This is the client ID you were given upon your app's creation on the Epic on FHIR site. This is always required when calling Epic's web services if your app doesn't use OAuth 2.0 authentication.

GET http://localhost:8888/website HTTP/1.1
Host: localhost:8888
Proxy-Connection: keep-alive
Authorization: Basic ZW1wJHVzZXJuYW1lOlBhJCR3MHJkMQ==
Epic-Client-ID: 0000-0000-0000-0000-0000

Epic-User-ID and Epic-User-IDType

This is required for auditing requests to FHIR resources. The Epic-User-ID corresponds to an EMP (Epic User) account that an organization creates for your application. This might be required depending on the web services you are calling. The Epic-User-IDType is almost always EXTERNAL.

GET http://localhost:8888/website HTTP/1.1
Host: localhost:8888
Proxy-Connection: keep-alive
Authorization: Basic ZW1wJHVzZXJuYW1lOlBhJCR3MHJkMQ==
Epic-Client-ID: 0000-0000-0000-0000-0000					 	
Epic-User-ID: username					 	
Epic-User-IDType: EXTERNAL

FHIR Tutorial

FHIR makes it easy to quickly get patient data from a clinical system, and to connect together queries to obtain a wide variety of related healthcare information. The tutorial below includes a walkthrough introduction to FHIR data and sample patient and clinical information - everything you need to turn up the heat on your first FHIR app!

Let's begin with a few examples using jQuery — in a larger application, you might build your own framework, or use an existing one, to interact with FHIR, but FHIR is easy enough to use without any special libraries. This tutorial assumes you are passing a form of authorization covered in one of our authentication guides. It is generally assumed that in production environments FHIR APIs will use OAuth 2.0; however, it's important to note FHIR APIs do support HTTP Basic Authentication.

Interacting with the API

To get started, let's define the FHIR server base URL. This is a constant address for a server where all FHIR API endpoints for a given FHIR version live. In a SMART on FHIR launch, this is specified with the iss parameter. We'll store this variable as a separate entity, so we can reference it across our queries:

    
var baseUrl = "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4"
    

You can expect the bold part of the above URL to be different for each unique instance of Epic you integrate with.

Finding Patients via Demographic Data

With the base URL defined, we can start building queries. Because we're interacting with patient data, there's an obvious place to get started: finding the patient whose data we are interested in. Epic's FHIR support provides two methods for applications to find and interact with patients:

  1. The Epic application can provide a patient to you as part of the application launch context, determined from the Epic workflow or the patient selection activity. This method can apply to both clinician and patient-facing workflows.
  2. Our Patient API provides a way to match on a single high-confidence patient using demographic information such as patient name, birthdate, address, and other identifying information. We use this method when your application is independent from Epic.
    
var patientMatchString = "/Patient/$match"
    

We can use jQuery's $.ajax() function to send patient demographic data to the server using an HTTP POST request. It takes a parameter describing the URL, the data to send to the server, and a callback for doing something with the data once the API responds. In our case, let's just log the data to the console so we can inspect it before developing code that works with it:

    
$.ajax({
  url: baseUrl + patientMatchString,
  type: "POST",
  contentType:"application/json; charset=utf-8",
  dataType: "json",
  data: dataToSend,
  success: function(data) { console.log(data); } 
});
    

The complete request URL at this point will read:

    
"https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/Patient/$match"
    

The dataToSend will be a FHIR Parameters resource. In this case, there are two parameters:

  1. The “resource” parameter, which is a partial Patient resource with demographic information about the patient you want to find.
  2. The “onlyCertainMatches” parameter, which is set to true, indicating that the server should only return single high-confidence matches based on your demographic information.
{
  "resourceType": "Parameters",
  "id": "example",
  "parameter": [
    {
      "name": "resource",
      "resource": {
        "resourceType": "Patient",
        "identifier": [
            {
                "use": "usual",
                "type": {
                    "text": "EPI"
                },
                "system": "urn:oid:1.2.840.114350.1.1",
                "value": "27475"
            }
        ],
        "name": [
          {
            "family": "Smith",
            "given": [
              "John"
            ]
          }
        ],
        "birthDate": "1906-03-04"
      }
    },
    {
      "name": "count",
      "valueInteger": "1"
    },
    {
      "name": "onlyCertainMatches",
      "valueBoolean": "true"
    }
  ]
}

If you execute the above request, you'll see the following full Patient resource response in your console:

{
    "resourceType": "Bundle",
    "type": "searchset",
    "total": 1,
    "link": [
        {
            "relation": "self",
            "url": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/Patient/$match"
        }
    ],
    "entry": [
        {
            "link": [
                {
                    "relation": "self",
                    "url": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/Patient/ecBjL8PUhljoHWMtwx63UhA3"
                }
            ],
            "fullUrl": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/Patient/ecBjL8PUhljoHWMtwx63UhA3",
            "resource": {
                "resourceType": "Patient",
                "id": "ecBjL8PUhljoHWMtwx63UhA3",
                "extension": [
                    {
                        "valueCodeableConcept": {
                            "coding": [
                                {
                                    "system": "urn:oid:1.2.840.114350.1.13.5325.1.7.10.698084.130.768080.39128",
                                    "code": "male",
                                    "display": "male"
                                }
                            ]
                        },
                        "url": "http://open.epic.com/FHIR/StructureDefinition/extension/legal-sex"
                    },
                    {
                        "valueCodeableConcept": {
                            "coding": [
                                {
                                    "system": "urn:oid:1.2.840.114350.1.13.5325.1.7.10.698084.130.768080.35144",
                                    "code": "male",
                                    "display": "male"
                                }
                            ]
                        },
                        "url": "http://open.epic.com/FHIR/StructureDefinition/extension/sex-for-clinical-use"
                    },
                    {
                        "extension": [
                            {
                                "valueCoding": {
                                    "system": "http://terminology.hl7.org/CodeSystem/v3-NullFlavor",
                                    "code": "UNK",
                                    "display": "Unknown"
                                },
                                "url": "ombCategory"
                            },
                            {
                                "valueString": "Unknown",
                                "url": "text"
                            }
                        ],
                        "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race"
                    },
                    {
                        "extension": [
                            {
                                "valueString": "Unknown",
                                "url": "text"
                            }
                        ],
                        "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity"
                    },
                    {
                        "valueCode": "248153007",
                        "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-sex"
                    },
                    {
                        "valueCodeableConcept": {
                            "coding": [
                                {
                                    "system": "http://loinc.org",
                                    "code": "LA29518-0",
                                    "display": "he/him/his/his/himself"
                                }
                            ]
                        },
                        "url": "http://open.epic.com/FHIR/StructureDefinition/extension/calculated-pronouns-to-use-for-text"
                    }
                ],
                "identifier": [
                    {
                        "use": "usual",
                        "type": {
                            "text": "CEID"
                        },
                        "system": "urn:oid:1.2.840.114350.1.13.5325.1.7.3.688884.100",
                        "value": "YA5WT9X7ZMB69TB"
                    },
                    {
                        "use": "usual",
                        "type": {
                            "text": "EPI"
                        },
                        "system": "urn:oid:1.2.840.114350.1.1",
                        "value": "27475"
                    },
                    {
                        "use": "usual",
                        "system": "urn:oid:2.16.840.1.113883.4.1",
                        "_value": {
                            "extension": [
                                {
                                    "valueString": "xxx-xx-3745",
                                    "url": "http://hl7.org/fhir/StructureDefinition/rendered-value"
                                }
                            ]
                        }
                    }
                ],
                "active": true,
                "name": [
                    {
                        "use": "official",
                        "text": "John Smith",
                        "family": "Smith",
                        "given": [
                            "John"
                        ]
                    },
                    {
                        "use": "usual",
                        "text": "John Smith",
                        "family": "Smith",
                        "given": [
                            "John"
                        ]
                    }
                ],
                "gender": "male",
                "birthDate": "1906-03-04",
                "deceasedBoolean": false,
                "managingOrganization": {
                    "reference": "Organization/ePUQySFaW.ofXm2GhaXRMDA3",
                    "display": "Epic Facility"
                }
            },
            "search": {
                "extension": [
                    {
                        "valueCode": "certain",
                        "url": "http://hl7.org/fhir/StructureDefinition/match-grade"
                    }
                ],
                "mode": "match",
                "score": 1
            }
        }
    ]
}

Let's pick out a couple of key elements to learn more about:

  1. The entry[n].fullUrl element points to the permanent location of a unique patient. If we make a request to this absolute URL, we can directly access the Patient resource for that patient.
  2. The entry[n].resource.id contains the FHIR ID of the resource. This ID is assigned by the server responsible for storing the resource. Storing this FHIR ID can be useful if you want to save off a list of recently used patients, for example.
  3. The entry[0].resource element contains all of the actual data about John Smith. Within this object, you'll find information about John such as his address, phone number, and birth date.

Ideally, you provide enough demographic information in your initial request for the API to return the correct patient. If we were building a more complex app, we might need to handle other scenarios gracefully, such as when no patients are found.

The following response is an example of what to expect if a single high-confidence patient match is not found. Note the text contained within the OperationOutcome resource:

{
    "resourceType": "OperationOutcome",
    "issue": [
        {
            "severity": "fatal",
            "code": "processing",
            "details": {
                "coding": [
                    {
                        "system": "urn:oid:1.2.840.114350.1.13.5325.1.7.2.657369",
                        "code": "59013",
                        "display": "The matching operation found one or more possible matches, but did not find a certain match."
                    }
                ],
                "text": "The matching operation found one or more possible matches, but did not find a certain match."
            },
            "location": [
                "/f:patient"
            ],
            "expression": [
                "patient"
            ]
        }
    ]
}


That's a quick overview of the Patient $match endpoint, but if you're interested in learning more about our Patient.$match API, you can read more here. Let's continue by saving off John's FHIR ID so we can refer to it later:

    
var patientId = data.entry[0].resource.id;
    

Interlude: Relative Resource URLs (R4 and Later)

There are several versions of the FHIR standard. We generally recommend using Epic’s latest R4 FHIR resources, as we did in the example above, but you might also need to use DSTU2 resources or STU3 resources in some cases.

For R4 and later versions of FHIR, Epic uses relative URLs for all referenced FHIR resources included in a query response. STU3 and earlier versions use absolute URLs. For example, if we look back at the sample response from our Patient query above, if that query was an STU3 version Patient query, instead of returning a relative reference URL for the managingOrganization:

"managingOrganization": {
    "reference": "Organization/ePUQySFaW.ofXm2GhaXRMDA3",
    "display": "Epic Facility"
}

An STU3 resource would return an absolute URL instead:

"managingOrganization": {
    "reference": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/STU3/Organization/ePUQySFaW.ofXm2GhaXRMDA3",
    "display": "Epic Facility"
}

This distinction is important to keep in mind, particularly if you’re working with some FHIR resources that use the DSTU2 or STU3 standard, and other resources that use the R4 standard. Make sure to append the baseURL to the front of the relative URL when querying referenced FHIR resources.

Searching for Clinical Data by Patient ID

We've found our patient, and we have some demographics that we could parse and display to an end user. But how do we get access to clinical information about that patient? To retrieve this data, we need to use another endpoint within our API, along with the patient ID we saved off above.

Let's start with a simple query — retrieving a patient's allergies. The AllergyIntolerance endpoint exposes exactly that, and provides a search endpoint to find allergies by Patient ID. We can construct a query that provides John's FHIR ID to grab all the active allergies recorded in our system:

    
var allergySearchString = "/AllergyIntolerance?patient=" + patientId;
    

We can use jQuery’s $.getJSON() function to call into the URL and retrieve our data in JSON format. We will also log the response bode to the console as a simple means to view the data.

    
$.getJSON(baseUrl + allergySearchString, function(data,error) { console.log(data); });
    

Executing the above request returns the following:

{
    "resourceType": "Bundle",
    "type": "searchset",
    "total": 1,
    "link": [
        {
            "relation": "self",
            "url": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/AllergyIntolerance?patient=ecBjL8PUhljoHWMtwx63UhA3"
        }
    ],
    "entry": [
        {
            "link": [
                {
                    "relation": "self",
                    "url": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/AllergyIntolerance/efnBJhT6lf969qC4prH5jVqor3vnmeJyvRfMzJX9-yZ43"
                }
            ],
            "fullUrl": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/AllergyIntolerance/efnBJhT6lf969qC4prH5jVqor3vnmeJyvRfMzJX9-yZ43",
            "resource": {
                "resourceType": "AllergyIntolerance",
                "id": "efnBJhT6lf969qC4prH5jVqor3vnmeJyvRfMzJX9-yZ43",
                "clinicalStatus": {
                    "coding": [
                        {
                            "system": "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical",
                            "version": "4.0.0",
                            "code": "active",
                            "display": "Active"
                        }
                    ],
                    "text": "Active"
                },
                "verificationStatus": {
                    "coding": [
                        {
                            "system": "http://terminology.hl7.org/CodeSystem/allergyintolerance-verification",
                            "version": "4.0.0",
                            "code": "confirmed",
                            "display": "Confirmed"
                        }
                    ],
                    "text": "Confirmed"
                },
                "category": [
                    "medication"
                ],
                "criticality": "high",
                "code": {
                    "coding": [
                        {
                            "system": "urn:oid:2.16.840.1.113883.3.26.1.5",
                            "code": "N0000005840",
                            "display": "PENICILLINS"
                        }
                    ],
                    "text": "PENICILLINS"
                },
                "patient": {
                    "reference": "Patient/ecBjL8PUhljoHWMtwx63UhA3",
                    "display": "Smith, John"
                },
                "onsetDateTime": "2024-08-27",
                "recordedDate": "2024-08-27T20:34:34Z",
                "reaction": [
                    {
                        "manifestation": [
                            {
                                "coding": [
                                    {
                                        "system": "http://snomed.info/sct",
                                        "code": "201272008",
                                        "display": "(URTICARIA NOS) OR (HIVES)"
                                    }
                                ],
                                "text": "Hives"
                            }
                        ],
                        "description": "Hives"
                    }
                ]
            },
            "search": {
                "mode": "match"
            }
        }
    ]
}

Looking at the response, there are a couple of key elements to highlight to help us understand the allergy information:

  1. The total element lets you know the number of resources returned for a query. In this case, it lets us know how many allergies our system has documented for John Smith.
  2. The entry[n].fullUrl element points to the permanent location of a unique AllergyIntolerance resource. If we make a request to this URL, we will always get back data for this specific AllergyIntolerance for the patient. The FHIR ID for this resource can also be retrieved directly from entry[n].resource.id. This might be useful for future reference to check whether the patient has developed additional reactions to this allergy.
  3. The entry[n].resource.patient element describes the patient the allergy relates to. This allows us to confirm the association between allergy and patient and shows how FHIR works by linking these smaller resources to paint a larger picture. In this case, the element in each allergy entry points to our patient, John.
  4. The entry[n].resource.code element describes the allergen that causes the allergy. This element has a text element, to make it easy to read, as well as coded values, to associate and develop clinical rules against. In this example response, we can see that John is allergic to Penicillin.
  5. The entry[n].resource.reaction element describes the allergic reactions of the patient documented in the system.
  6. The entry[n].resource.onsetDateTime element tells you when the allergy first started happening.
  7. The entry[n].resource.reaction[n].manifestation element tells you what kind of reaction occurred. For example, John's allergy to Penicillin gives him hives.

That's a lot of information with a simple query, and it demonstrates how quickly we can build up and associate the different endpoints available on our API.

Wrap Up

Nice work! You made it to the end of our tutorial, and you understand how to find patients, and start working with their data. There's even more to discover in our documentation, like additional data types, and more complex searches.

Advanced Topics

In this section we'll cover a few advanced topics that may come up in your implementation of FHIR.

Searching from Patient Context in Coordination with OAuth 2.0

For patient-facing applications using OAuth 2.0 authorization, including the patient or subject search parameter(s) is optional when making requests for the patient on whose behalf authorization to the system was granted. This is true for STU3 and R4 versions of FHIR resources.

Including the patient or subject ID in the request is still recommended and encouraged. Requests can be made with these parameters so long as the FHIR ID matches the patient whose data is being requested with the access token.

Content negotiation in FHIR

Epic’s FHIR server defaults to using XML for FHIR resources, but many apps may prefer sending and receiving JSON instead. FHIR allows both XML and JSON MIME-types and specifies how a client can request one or the other. Per the FHIR spec and in Epic’s FHIR server, a client can specify XML or JSON through either the _format query parameter or by specifying the MIME-type in an HTTP header. A client should use the Accept HTTP header to specify the MIME-type of the content that it wishes to receive from the server and the Content-Type HTTP header to specify the MIME-type of the content that it is sending in the body of its request, for example, as part of a create or update; the Content-Type HTTP header will also be considered for requests in which another method (_format query parameter, or Accept HTTP header) is not provided.

Epic's FHIR server processes the the available options in the following hierarchy:

  • _format query parameter
  • Accept HTTP Header
  • Content-Type HTTP Header
  • Server Default: XML

Epic supports all of the following MIME-types for FHIR interactions:

  • application/fhir+json
  • application/json+fhir
  • application/xml+fhir
  • application/fhir+xml
  • application/json
  • application/xml
  • text/xml

In addition to the MIME-types listed above, the _format query parameter supports:

  • xml
  • json

Retrieving Additional ID Types with Patient and Practitioner Resources

You can also use FHIR search APIs such as Patient.Search and Practitioner.Search to get FHIR IDs for other FHIR versions and a list of other identifiers given one type of identifier. For Patient.Search, use the following format:

<base url>/R4/Patient?identifier=<ID Type>|<ID>

Where ID Type can be one of the following: CID, CSN, External, ExternalKey, FHIR, FHIR STU3, Internal, Name, NationalID, CEID, MyChartLogin, <any IIT identifier>

The resulting identifiers are identical to those returned by GetPatientIdentifiers.

For practitioners, you can use the Practitioner.Search endpoint to retrieve a list of practitioner identifiers. Use the following format:

<base url>/R4/Practitioner?identifier=<ID Type>|<ID>

Where ID Type can be one of the following: CID, External, FHIR, Internal, NPI, CSN.

Constraining ID Length to 64 Characters in the R4 Version of FHIR

For historical reasons, some of Epic’s FHIR resources have identifiers that are longer than 64 characters. For example, Medication resource IDs sometimes exceed this length. New clients can be configured to respect the 64-character ID-length limit described in the HL7 FHIR standard for all STU3 and R4 version FHIR resources that are part of the USCDI data classes. To avoid disrupting existing integrations or new integrations that rely on historical data, this configuration is not enabled by default. You can set your client to observe the 64-character identifier length limit for USCDI resources by changing the FHIR ID Generation Scheme setting on the Build Apps page. To change this setting on an active app, you will need to contact your Epic representative. See the App Default FHIR Version Tutorial for more information.

This document explains the ways in which the date and datetime parameters can be configured during a search with a FHIR resource. The general format for dates and times is yyyy-mm-ddThh:mm:ss[Z|(+|-)hh:mm], an example being 2019-05-20T08:00:00Z which corresponds to May 20th 2019 at 8:00 AM UTC.

Date/Time Information

Prefixes

Epic supports a subset of HL7-defined prefixes that can be added to a date or datetime parameter to modify the way in which searching occurs. If you choose to omit a prefix, the default behavior will be the same as that of an "equal" prefix, such that the search will include a results for which the specified value is within range.

Refer to the Prefix Considerations section below for additional caveats about Epic's date searching behavior.

Prefix Prefix Meaning Example Search Example Comparisons
eq equal =eq2013-01-14 2013-01-14T00:00 matches
2013-01-14T10:00 matches
2013-01-15T00:00 does not match - it's not in the range
lt less than =lt2013-01-14T10:00 2013-01-14 matches, because it includes the part of 14-Jan 2013 before 10am
le less than or equal to =le2013-03-14 "from 21-Jan 2013 onwards" is included because that period may include times before 14-Mar 2013
gt greater than =gt2013-01-14T10:00 2013-01-14 matches, because it includes the part of 14-Jan 2013 after 10am
ge greater than or equal to =ge2013-03-14 "from 21-Jan 2013 onwards" is included because that period may include times after 14-Mar 2013
sa starts after =sa2013-03-14 "from 15-Mar 2013 onwards" is included because that period starts after 14-Mar 2013
"from 21-Jan 2013 onwards" is not included because that period starts before 14-Mar 2013
"before and including 21-Jan 2013" is not included because that period starts (and ends) before 14-Mar 2013
eb ends before =eb2013-03-14 "from 15-Mar 2013 onwards" is not included because that period starts after 14-Mar 2013
"from 21-Jan 2013 onwards" is not included because that period starts before 14-Mar 2013, but does not end before it
"before and including 21-Jan 2013" is included because that period ends before 14-Mar 2013
Prefix Considerations
There are a few unique things to note about Epic's implementation of date search prefixes:
  • In some FHIR APIs, the date parameter supports fuzzy date searching, meaning that the search will also return values with dates close to but outside of the specified date or date range. If you need strict date searching, we suggest that you do additional validation on the date values returned in the response.
  • The sa and eb prefixes behave differently than defined by HL7:
    • sa behaves identically to gt in Epic. In other words, sa will match a period even if only part of that period occurs after the specified date or datetime.
    • eb behaves identically to lt in Epic. In other words, eb will match a period even if only part of that period occurs before the specified date or datetime.
  • Epic does not support the ne or ap date search prefixes.

Date Parameters

Level of Precision

Searching by a date or time will do a search based on the level of precision you enter.

Search by year:

date=yyyy

Ex: date=2019

This will search for values with a date that occurred in 2019. This encompasses a range from January 1st, 2019 00:00:00 to December 31st, 2019 23:59:59.

Search by year and month:

date=yyyy-mm

Ex: date=2019-05

This will search for values with a date that occurred in May 2019. This encompasses a range from May 1st, 2019 00:00:00 to May 31st, 2019 23:59:59.

Search by year, month, and day:

date=yyyy-mm-dd

Ex: date=2019-05-15

This will search for values with a date of May 15th, 2019.

Date Ranges

Search on or near a specific date:

date=yyyy-mm-dd

Ex: date=2019-05-15

This will search for values with a date of May 15th, 2019.

Search starting at a specific date:

date=ge yyyy-mm-dd

Ex: date=ge2019-05-10

This will search for values with a date on or after May 10th, 2019.

Search ending at a specific date:

date=le yyyy-mm-dd

Ex: date=le2019-05-20

This will search for values with a date on or before May 20th, 2019.

Search starting and ending at a specific date:

date=ge yyyy-mm-dd&date=le yyyy-mm-dd

Ex: date=ge2019-05-10&date=le2019-05-20

This will search for values with a date on or after May 10th, 2019 and on or before May 20th, 2019.

DateTime (Instant)

Currently in FHIR, most searching is done by Date only, while the response can return a DateTime value which will be in the same format as would be used in a search request.
Level of Precision

Search by Hour:

parameter=yyyy-mm-ddThh:mm

Ex: parameter=2019-05-15T12:00

This will search for values with a date of May 15th, 2019 and a time of 12:00 pm local time. If you include an hour value, you must include a minute value.

Search by Hour and Minute:

parameter=yyyy-mm-ddThh:mm

Ex: parameter=2019-05-15T12:30

This will search for values with a date of May 15th, 2019 and a time of 12:30 pm local time.

Search by Hour, Minute, and Second:

parameter=yyyy-mm-ddThh:mm:ss

Ex: parameter=2019-05-15T12:00:30

This will search for values with a date of May 15th, 2019 and a time of 12:00:30 pm local time.

Time Zones

When a time and time offset are specified, the specified timezone will be used. If only a date with no time is specified or a time is specified without a timezone, the timezone defaults to the local timezone.

Specify UTC Time Zone:

parameter=yyyy-mm-ddThh:mm:ssZ

Ex: parameter=2019-05-15T12:30:00Z

This will search for values with a date of May 15th, 2019 and a time of 12:30 pm UTC. This also works with an offset of +00:00 or -00:00.

Specify a non-UTC Time Zone:

parameter=yyyy-mm-ddThh:mm:ss(+/-)hh:mm

Ex: parameter=2019-05-15T12:30:00-05:00

This will search for values with a date of May 15th, 2019 and a time of 12:30 pm EST.

App Default FHIR Version

An app’s FHIR version is selected during the app creation process and can be changed any time prior to activating an app. The FHIR version controls the behavior of different interoperability technologies, including:

  • SMART on FHIR: Multiple pieces of the SMART on FHIR authentication workflow
  • Authentication: OpenID Connect or Introspect for token inspection and user authentication
  • CDS Hooks: Supported hooks and other features
  • FHIR ID Generation Scheme: The control of FHIR ID length used in the app

Generally, an app FHIR version of STU3+ is recommended as those versions support functionality that the DSTU2 app FHIR version does not. Regardless of the version you choose though, you should test all relevant behaviors to ensure app compatibility with the selected FHIR version. Note that selecting an app FHIR version does not prevent you from using web services of other FHIR versions.

SMART on FHIR

The app’s FHIR version impacts multiple different behaviors during a SMART on FHIR launch, including:

  • iss element
  • JSON payload

iss Element

The value of the iss element sent during Step 1 of a SMART on FHIR launch is determined by the app's FHIR version, where the iss endpoint returned corresponds to the specific endpoint for that FHIR version on the server.

For example, if your app has a FHIR version of STU3 selected, then the server's STU3 FHIR endpoint will be returned as the iss value. In the sandbox, this would correspond to /interconnect-aocurprd-oauth/api/fhir/STU3.

JSON Payload

After successfully exchanging an authorization code for an access token, a JSON payload containing context about the app launch is returned. The JSON payload is impacted by the app FHIR version in the following ways:

  • Encounter context is supported only in STU3+ app FHIR versions.
  • Patient context returns a Patient FHIR ID of the version that matches the app's FHIR version.

Authentication

Epic offers multiple options for authenticating the user of a SMART app, each of which utilize the app's selected FHIR version to control how information about the authenticated user is returned. These affected methods include:

  • OpenID Connect (OIDC)
  • Introspect

OpenID Connect (OIDC)

In an OpenID Connect token, the app’s FHIR version determines whether or not the fhirUser claim included. The app’s FHIR version is respected for the sub claim beginning in the STU3 version.

fhirUser Element

When the app version is R4+ a fhirUser element can be returned when the fhirUser scope is included. This element returns an absolute reference to the authenticated user's FHIR ID.

Sub Element

The sub element will respect an app's FHIR version in STU3+ such that the FHIR reference returned in the sub element will match that of the app's FHIR version starting at an app FHIR version of STU3. While apps with a FHIR version of DSTU2 may still return a FHIR reference in the sub element, that reference will utilize an STU3 FHIR ID.

Introspect

Some integrations may utilize the Introspect web service to obtain information about the access token. When no epic_user_id_type parameter is specified in the Introspect request, the sub response element includes an absolute FHIR reference. That FHIR ID’s version will match the app’s specific FHIR version.

CDS Hooks

For a CDS Hooks integration, the app's FHIR version controls the following behaviors:

  • Prefetch support
  • Resource ID version
  • Supported hooks

Prefetch Support

Prefetch is supported only in apps with a FHIR version of STU3 and above. Prefetch is not supported for DSTU2.

Resource ID Version

Some FHIR resources are returned in the CDS Hooks context. The app's FHIR version will determine if the resource IDs returned are DSTU2 or STU3+ FHIR IDs.

Supported Hooks

Some hooks are supported only in specific FHIR versions. For example, an app with a FHIR version of DSTU2 can use only the patient-view hook.

FHIR ID Generation Scheme

A new app’s FHIR version determines whether the FHIR ID Generation Scheme can be set. An app must have a FHIR version of STU3 or greater and use OAuth 2.0 or a FHIR API with a version of STU3 or greater to select one of the following:

  • Use Unconstrained FHIR IDs
  • Use 64-Character-Limited FHIR IDs for USCDI FHIR Resources

These settings do not have an effect for DSTU2 versions.

For historical reasons, some of Epic’s FHIR resources have identifiers that are longer than 64 characters. For example, Medication resource IDs sometimes exceed this length. Starting in the February 2022 version of Epic, new clients can be configured to respect the 64-character ID-length limit described in the HL7 FHIR standard for all STU3 and R4 version FHIR resources that are part of the USCDI data classes. To avoid disrupting existing integrations or new integrations that rely on historical data, this configuration is not enabled by default. You can set your client to observe the 64-character identifier length limit for USCDI resources by changing the FHIR ID Generation Scheme setting on the Build Apps page. To change this setting on an active app you can submit an email request.

Unconstrained FHIR IDs

Apps default to the unconstrained FHIR ID generation scheme. This means apps may use and may receive FHIR IDs longer than 64 characters.

64-Character-Limited FHIR IDs

The 64-Character-Limited FHIR ID option can be selected to ensure all FHIR IDs will be 64 characters or less for USCDI resources. This length limit will apply to both the production and non-production client IDs for your application. For cases where an ID was generated to be longer than 64 characters most versions of Epic have an alternate ID that represents the same resource but doesn’t exceed the 64-character limit.

ID Types for FHIR APIs

ID Types for APIs

Table of Contents

  • FHIR API IDs and HL7 OIDs
    • Identifier Type
    • Examples
    • Identifier System
  • Organization-Specific IDs and Value Sets

FHIR API IDs and HL7 OIDs

Epic’s FHIR APIs often use object identifiers (OIDs) to uniquely identify and exchange information about particular entities. An OID is a global, unambiguous identifier for an object. It is formatted as a series of numbers and periods that represent a hierarchy of information. Organizations such as HL7, Epic, and individual healthcare organizations have registered their own unique node in the OID namespace with the International Standards Organization (ISO). Some examples include:

  • Epic: 1.2.840.114350
  • NPI: 2.16.840.1.113883.4.6
  • HL7: 2.16.840.1.113883

OIDs are widely used by a variety of applications. FHIR APIs are only one place where you might see them. For example, HL7v2 messages, HL7v3 messages, and CDA exchanges all involve the use of OID identifiers. HL7 provides a useful site for reviewing existing registered OIDs: https://www.hl7.org/oid/.

Each organization is responsible for managing additional sub-nodes within their registered space. For example, Epic has registered OID 1.2.840.114350, and has assigned the following child OIDs (among many others):

  • 1.2.840.114350.1.13.0.1.7.2.657380 – Represents the Interface Specification (AIP) master file in the open.epic sandbox environment
  • 1.2.840.114350.1.13.0.1.7.5.737384.0 – Represents the Epic MRN ID type in the open.epic sandbox environment
  • 1.2.840.114350.1.72.3.10 – Represents an observation about a patient FYI in Care Everywhere

Epic also issues child OIDs to each unique organization that uses Epic software, so that identifiers can be uniquely distinguished across organizations. When Epic issues a child OID to an organization, we have a specific structure we use. A common pattern is:

1.2.840.114350.1.13.<CID>.<EnvType>.7.<Value>

The pieces of this example OID represent the concepts outlined in the following table:

OID Segment

Meaning

1.2.840.114350

Epic’s root OID

.1

Indicates that this is an application-type ID

.13

The application ID. Some common application IDs include:

  • 13: EDI
  • 72: Care Everywhere

.<CID>

The unique Epic organization ID

.<EnvType>

The environment type. 1-Epic refers to Epic environments, such as the open.epic sandboxes. 2-Production and 3-Non-production refer to instances of Epic deployed at healthcare organizations.

  • 1: Epic
  • 2: Production
  • 3: Non-production

.7

Indicates that this OID is intend for use in an HL7-type message

.<Value>

The value. This varies greatly depending on the concept we are generating an OID for. It might include multiple additional sub-nodes.

Note that the structure shown above is just one of several that Epic uses. It is included as an illustrative example. OIDs should be treated as opaque identifiers by external applications and systems. However, knowing this common structure can help analysts easily identify issues when troubleshooting.

Identifier Type

Epic uses this term differently than HL7. The distinction is important.

  • Within HL7 standards, an identifier type is a categorization of the identifier. For example, “MR” for medical record numbers or “DL” for driver’s license numbers. A given patient might have multiple identifiers with the same type (e.g., multiple medical record numbers).
  • Within Epic, identifier types are represented by Identity ID Type (IIT) records. IIT records store metadata about a specific set of identifiers. That metadata includes both the HL7 identifier type, (e.g., “MR”), which is stored in the “HL7 ID Type” field of the IIT record, as well as the HL7 identifier system, which is stored in the “HL7 Root” field of the IIT record. The identifier system is the globally unique namespace identifier for the set of identifiers. See the definition of identifier system below.

Examples

The following example in the HL7v2 message format demonstrates how HL7 identifiers are used:

  • Format: <ID>^^^<HL7 Assigning Authority>^<HL7 Identifier Type>
  • Example: 1234^^^Example Health System^MR

This example indicates that the identifier 1234 was assigned by Example Health System and is a Medical Record (MR) identifier. This example uses a human-readable “Example Health System” value for the assigning authority, but for integrations internal to an organization, often a short textual mnemonic like “EHSMRN” is used to communicate the assigning authority. However, for cross-organizational exchanges, an OID might be necessary because a short textual mnemonic might not be unique across organizations.

Here is a similar example in a FHIR format:

<identifier> 
    <use value="usual"/> 
    <type> 
        <coding> 
            <system value="http://terminology.hl7.org/CodeSystem/v2-0203"/> 
            <code value="MR"/> 
        </coding> 
    </type> 
    <system value="urn:oid:1.2.36.146.595.217.0.1"/> 
    <value value="1234"/> 
    <assigner> 
        <display value="Example Health System"/> 
    </assigner> 
</identifier>

Identifier System

From an HL7 perspective, you might have multiple identifiers with the same type (MR) but with different systems. If two organizations merge, their shared patient population might have two medical record numbers. Both of those identifiers are MR-type identifiers, but they are from different identifier systems. Identifier systems are important because, without qualifying an identifier with an identifier system, you cannot uniquely identify a patient. Ignoring identifier systems can lead to matching to the wrong patient, which can have patient safety implications.

In Epic, the HL7 identifier system is stored in the “HL7 Root” field of the ID Type record. In some cases, if this value is not specified in the IIT record, Epic falls back to using an auto-generated OID that is specific to the current environment. While this value may often follow the structure under Epic's root OID as noted in the table above, healthcare organizations may also register for their own nodes and populate this field on a given ID type with that value.

The meaning of the “HL7 Root” field

HL7 version 3 defined two methods of ensuring identifiers were globally unique: Universally Unique Identifiers (UUIDs), and Object Identifiers (OIDs). Epic software uses the OID method. To communicate unique identifiers, HL7v3 defined an “instance identifier” data type, which had two attributes: a root and an extension. The root attribute contained the root OID of the identifier system, and the extension attribute contained the identifier. 

When Epic first developed the ability to store an OID for the identifier system, we created the “HL7 Root” field in Epic IIT records. This was initially created to support HL7 version 3, and the item was named according to the HL7v3 terms, but the use of that item has since evolved to be used by Care Everywhere, HL7v2 and FHIR. 

In many integrations, Epic will look first to the “HL7 Root” field for the OID, but if this field is blank, we fall back to an auto-generated OID that is specific to the environment. The auto-generated OID includes a sub-node that indicates the environment type (PRD vs. non-PRD). It also includes a sub-node that identifies the specific healthcare organization. This often works well, as most IIT records are used to represent identifiers generated by that organization. However, this approach won’t work if the IIT record is used to store identifiers you receive from an external organization.

Organization-Specific IDs and Value Sets

FHIR APIs can sometimes require organization-specific identifiers or value sets. In these cases, developers and Epic organizations need to work together to coordinate usage of those identifiers or value sets.

  • FHIR APIs that use Epic-specific values such as Observation.Create (Vitals) use organization-specific OIDs. FHIR APIs that use standard value sets such as AllergyIntolerance.Search use either an OID or an industry URL as the coding system. For FHIR Create operations, you will need to use the OID or URL format, and the IDs will differ from the identifiers used by non-FHIR Epic APIs such as AddAllergy.

When an API returns data, it often returns multiple IDs that reference the same entity, such as a diagnosis or procedure. Each returned ID uses a different coding system. Therefore, you might be able to retrieve the ICD-10 or industry standard codes when an API returns a particular entity. For example, calling the Condition.Search FHIR API for a patient with hypertension returns both the ICD-10 code and an Epic ID for hypertension.

FHIR APIs typically use shared value sets that are linked in the “system” element, and represent well-defined HL7 standards.

The ValueSet.$expand API can in some cases help determine systems and codes supported by a resource. Epic currently supports ValueSet.$expand for the DocumentReference resource. Additional resources might be supported in future Epic versions.

Sandbox Test Data

This page is not comprehensive of all data that exists in the sandbox. Rather, it is meant to highlight patients that contain some common types of test data.

Sandbox URI - https://fhir.epic.com/interconnect-fhir-oauth/
    Note that the Epic on FHIR sandbox is exclusively secured using OAuth 2.0
FHIR Endpoints - See 'FHIR API Endpoints' on open.epic

These test users can be used to authenticate when testing Provider-Facing Standalone OAuth 2.0 workflows with the FHIR sandbox.

Name

User Login

User Password

Description

FHIR, USER

FHIR

EpicFhir11!

User account without linked provider record. This user will not have a PractitionerRole resource.

FHIRTWO, USER

FHIRTWO

EpicFhir11!

User account with linked provider record. This user will have a PractitionerRole resource.

These are some of the test patients that exist in the Epic on FHIR sandbox. Any test patients with MyChart credentials listed can be used to authenticate when testing Patient-Facing Standalone OAuth 2.0 workflows with the FHIR sandbox.

Patient

Identifiers and Credentials

Applicable Resources

Camila Lopez

FHIR: erXuFYUfucBZaryVksYEcMg3

External: Z6129

MRN: 203713

MyChart Login Username: fhircamila

MyChart Login Password: epicepic1

  • DiagnosticReport
  • Goal
  • Medication
  • MedicationOrder
  • MedicationRequest
  • MedicationStatement
  • Observation (Labs)
  • Patient
  • Procedure

Derrick Lin

FHIR: eq081-VQEgP8drUUqCWzHfw3

External: Z6127

MRN: 203711

MyChart Login Username: fhirderrick

MyChart Login Password: epicepic1

  • CarePlan
  • Condition
  • Goal
  • Medication
  • MedicationOrder
  • MedicationRequest
  • MedicationStatement
  • Observation (Smoking History)
  • Patient

Desiree Powell

FHIR: eAB3mDIBBcyUKviyzrxsnAw3

External: Z6130

MRN: 203714

MyChart Login Username: fhirdesiree

MyChart Login Password: epicepic1

  • Immunization
  • Observation (Vitals)
  • Patient

Elijah Davis

FHIR: egqBHVfQlt4Bw3XGXoxVxHg3

External: Z6125

MRN: 203709

  • AllergyIntolerance
  • Binary
  • Condition
  • DocumentReference
  • Medication
  • MedicationOrder
  • MedicationRequest
  • MedicationStatement
  • Observation (Smoking History)
  • Patient

Linda Ross

FHIR: eIXesllypH3M9tAA5WdJftQ3

External: Z6128

MRN: 203712

  • Condition
  • Medication
  • MedicationOrder
  • MedicationRequest
  • MedicationStatement
  • Observation (Vitals)
  • Patient

Olivia Roberts

FHIR: eh2xYHuzl9nkSFVvV3osUHg3

External: Z6131

MRN: 203715

  • Binary
  • Condition
  • Device
  • DocumentReference
  • Patient

Warren McGinnis

FHIR: e0w0LEDCYtfckT6N.CkJKCw3

External: Z6126

MRN: 203710

  • AllergyIntolerance
  • Binary
  • Condition
  • DiagnosticReport
  • DocumentReference
  • Observation (Labs)
  • Observation (Vitals)
  • Patient
  • Procedure

Below are Group FHIR IDs that can be used to test bulk FHIR calls in the sandbox.

To test bulk FHIR in the sandbox, you MUST select the Kick-off, Status Request, File Request, and Delete Request APIs along with the Search interaction of any APIs for which you want to export data.

For example, if you want to extract demographics and immunizations for patients in the relevant registry, you would add the following scopes to your application:

  • Bulk Data Kick-off
  • Bulk Data Status Request
  • Bulk Data File Request
  • Bulk Data Delete Request
  • Patient.Search (R4)
  • Immunization.Search (R4)

Your client record will automatically be granted access to the test FHIR Group below so long as you include all four bulk FHIR APIs in your registered application's API list.

Bulk FHIR Group

FHIR Group ID

Group consists of test patients in table above.

e3iabhmS8rsueyz7vaimuiaSmfGvi.QwjVXJANlPOgR83

Patient-Facing Apps Using FHIR

FHIR API Response Variations in Patient-Facing Applications

Some FHIR APIs may return different data when the application is used in a patient-facing context. This typically occurs because data that should not be visible to a patient is filtered out of FHIR API responses in patient-facing applications. Some filtering criteria is configurable by the community member such that two community members could filter the same data in different ways. Below are some examples of possible differences in the results returned by a FHIR API in a patient context:

  • FHIR APIs will only return results relevant to the authenticated patient. If one patient, Susan, is logged in to the application, the Observation (Labs) API won’t return lab results for a patient other than Susan.
  • FHIR APIs may not return patient-entered data that has not yet been reviewed and reconciled by a clinician.
  • FHIR APIs may return patient friendly names for resources, such as medications. For example, a patient-facing app may return the medication name pseudoephedrine instead of PSEUDOPHEDRINE HCL 30 MG PO TABS.
  • FHIR APIs may be configured to exclude specific lab results to comply with state and local regulations

Community members may choose to disable this filtering functionality for their Epic instance, resulting in near identical behavior between patient-facing FHIR API responses and the response of apps with other audiences.

Epic recommends that you thoroughly test each API at each Community Member’s site prior to a go-live in production to ensure that you understand the behavior of each API for that community member’s Epic deployment.

Automatic Client Record Distribution

Automatic Client ID Distribution: USCDI Apps

Client IDs for USCDI apps will automatically download to a community member’s Epic instances when all of the following conditions are met:

  • The application:
    • Is created through Epic on FHIR
    • In Epic August 2024 and later:
      • Uses only USCDI v3 FHIR APIs, which are documented in the appendix below †
    • In Epic May 2024 and earlier:
      • Uses only USCDI v1 FHIR APIs, which are documented in the appendix below †
    • Only reads data from Epic
    • Is patient-facing
    • Does not use refresh tokens OR uses refresh tokens and has a client secret uploaded by the vendor for that community member
    • Is marked "Ready for Production" and was marked ready after Sept. 3rd, 2020
      • Apps can be marked "Ready for Sandbox Use" to test with our Epic on FHIR environment prior to marking the app "Ready for Production"
  • The Community Member:
    • Has signed the open.epic API Subscription Agreement
    • Has not disabled auto-download functionality

Automatic Client ID Distribution: CCDS (MU3) Apps

Client IDs for CCDS apps will automatically download to a community member’s Epic instances when all of the following conditions are met:

  • The application:
    • Is created through Epic on FHIR
    • Uses only DSTU2 FHIR APIs from the list in the appendix below ††
    • Only reads data from Epic
    • Is patient-facing
    • Does not use refresh tokens
    • Is marked "Ready for Production" and was marked ready after Dec. 17th, 2020
      • Apps can be marked "Ready for Sandbox Use" to test with our Epic on FHIR environment prior to marking the app "Ready for Production"
  • The Community Member:
    • Need not sign the open.epic API Subscription Agreement

Automatic Client ID Distribution: CMS Payor Apps

Client IDs for apps meant to meet CMS payor requirements will automatically download to a community member's Epic instances when all of the following conditions are met:

  • The application:
    • Is created through Epic on FHIR
    • Uses only ExplanationOfBenefit (of any supported version) and/or any FHIR APIs (of any supported version) from the list in the appendix below †
    • Only reads data from Epic
    • Is patient-facing
    • Uses OAuth 2.0
    • Is marked "Ready for Production"
      • Apps can be marked "Ready for Sandbox Use" to test with our Epic on FHIR environment prior to marking the app "Ready for Production"
  • The Community Member:
    • Has signed the open.epic API Subscription Agreement
    • Has enabled this auto-download functionality

Automatic Client ID Distribution: TEFCA™ IAS via FHIR Apps

Client IDs for TEFCA IAS via FHIR apps will automatically download to a community member’s Epic instances when all of the following conditions are met:

  • The application:
    • Is patient-facing
    • Has completed registration with Epic Nexus
    • Has completed testing with Epic Nexus
    • Has an active entry in the Production RCE (TEFCA) Directory
  • The Community Member:
    • Is live on TEFCA and IAS via FHIR
    • Has not disabled auto-download functionality

For more information see Epic Nexus’s Approach to Individual Access Services in TEFCA via FHIR.

Manual Client ID Distribution

Any applications that do not meet all of the above criteria will need to be manually downloaded to the community member's Epic instances. Steps to do so can be found in the App Creation and Request Process guide.

Appendix

† The following FHIR APIs qualify for USCDI v1 auto-distribution.

  • AllergyIntolerance.Read (DSTU2)
  • AllergyIntolerance.Read (R4)
  • AllergyIntolerance.Read (STU3)
  • AllergyIntolerance.Search (DSTU2)
  • AllergyIntolerance.Search (R4)
  • AllergyIntolerance.Search (STU3)
  • Binary.Read (Clinical Notes) (R4)
  • Binary.Read (Clinical Notes) (STU3)
  • Binary.Read (Generated CCDA) (DSTU2)
  • Binary.Read (Generated CCDA) (R4)
  • Binary.Read (Labs) (R4)
  • Binary.Search (Clinical Notes) (R4)
  • Binary.Search (Generated CCDA) (R4)
  • Binary.Search (Labs) (R4)
  • CarePlan.Read (Encounter-Level) (DSTU2)
  • CarePlan.Read (Encounter-Level) (R4)
  • CarePlan.Read (Longitudinal) (DSTU2)
  • CarePlan.Read (Longitudinal) (R4)
  • CarePlan.Search (Encounter-Level) (DSTU2)
  • CarePlan.Search (Encounter-Level) (R4)
  • CarePlan.Search (Longitudinal) (DSTU2)
  • CarePlan.Search (Longitudinal) (R4)
  • CareTeam.Read (Longitudinal) (R4)
  • CareTeam.Search (Longitudinal) (R4)
  • Condition.Read (Care Plan Problem) (R4)
  • Condition.Read (Encounter Diagnosis, Problems) (STU3)
  • Condition.Read (Health Concern) (R4)
  • Condition.Read (Problems) (DSTU2)
  • Condition.Read (Problems) (R4)
  • Condition.Search (Care Plan Problem) (R4)
  • Condition.Search (Encounter Diagnosis, Problems) (STU3)
  • Condition.Search (Health Concern) (R4)
  • Condition.Search (Problems) (DSTU2)
  • Condition.Search (Problems) (R4)
  • Device.Read (Implants and External Devices) (STU3)
  • Device.Read (Implants) (DSTU2)
  • Device.Read (Implants) (R4)
  • Device.Search (Implants and External Devices) (STU3)
  • Device.Search (Implants) (DSTU2)
  • Device.Search (Implants) (R4)
  • DiagnosticReport.Read (Results) (DSTU2)
  • DiagnosticReport.Read (Results) (R4)
  • DiagnosticReport.Read (Results) (STU3)
  • DiagnosticReport.Search (Results) (DSTU2)
  • DiagnosticReport.Search (Results) (R4)
  • DiagnosticReport.Search (Results) (STU3)
  • DocumentReference.Read (Clinical Notes) (R4)
  • DocumentReference.Read (Clinical Notes) (STU3)
  • DocumentReference.Read (Generated CCDA) (DSTU2)
  • DocumentReference.Read (Generated CCDA) (R4)
  • DocumentReference.Read (Labs) (R4)
  • DocumentReference.Search (Clinical Notes) (R4)
  • DocumentReference.Search (Clinical Notes) (STU3)
  • DocumentReference.Search (Generated CCDA) (DSTU2)
  • DocumentReference.Search (Generated CCDA) (R4)
  • DocumentReference.Search (Labs) (R4)
  • Encounter.Read (Patient Chart) (R4)
  • Encounter.Read (STU3)
  • Encounter.Search (Patient Chart) (R4)
  • Encounter.Search (STU3)
  • Goal.Read (Care Plan) (R4)
  • Goal.Read (Care Plan) (STU3)
  • Goal.Read (Patient) (DSTU2)
  • Goal.Read (Patient) (R4)
  • Goal.Read (Patient) (STU3)
  • Goal.Search (Care Plan) (R4)
  • Goal.Search (Care Plan) (STU3)
  • Goal.Search (Patient) (DSTU2)
  • Goal.Search (Patient) (R4)
  • Goal.Search (Patient) (STU3)
  • Immunization.Read (DSTU2)
  • Immunization.Read (R4)
  • Immunization.Read (STU3)
  • Immunization.Search (DSTU2)
  • Immunization.Search (R4)
  • Immunization.Search (STU3)
  • Introspect
  • Location.Read (R4)
  • Location.Read (STU3)
  • Location.Search (R4)
  • Medication.Read (DSTU2)
  • Medication.Read (R4)
  • Medication.Read (STU3)
  • Medication.Search (DSTU2)
  • Medication.Search (R4)
  • MedicationOrder.Read (DSTU2)
  • MedicationOrder.Search (DSTU2)
  • MedicationRequest.Read (Orders) (R4)
  • MedicationRequest.Read (Orders) (STU3)
  • MedicationRequest.Search (Orders) (R4)
  • MedicationRequest.Search (Orders) (STU3)
  • MedicationStatement.Read (DSTU2)
  • MedicationStatement.Read (STU3)
  • MedicationStatement.Search (DSTU2)
  • MedicationStatement.Search (STU3)
  • Observation.Read (Labs) (DSTU2)
  • Observation.Read (Labs) (R4)
  • Observation.Read (Labs) (STU3)
  • Observation.Read (Social History) (DSTU2)
  • Observation.Read (Social History) (R4)
  • Observation.Read (Social History) (STU3)
  • Observation.Read (Vitals) (DSTU2)
  • Observation.Read (Vitals) (R4)
  • Observation.Read (Vitals) (STU3)
  • Observation.Search (Labs) (DSTU2)
  • Observation.Search (Labs) (R4)
  • Observation.Search (Labs) (STU3)
  • Observation.Search (Social History) (DSTU2)
  • Observation.Search (Social History) (R4)
  • Observation.Search (Social History) (STU3)
  • Observation.Search (Vitals) (DSTU2)
  • Observation.Search (Vitals) (R4)
  • Observation.Search (Vitals) (STU3)
  • Organization.Read (R4)
  • Organization.Read (STU3)
  • Organization.Search (R4)
  • Patient.Read (DSTU2)
  • Patient.Read (R4)
  • Patient.Read (STU3)
  • Patient.Search (DSTU2)
  • Patient.Search (R4)
  • Patient.Search (STU3)
  • Practitioner.Read (DSTU2)
  • Practitioner.Read (R4)
  • Practitioner.Read (STU3)
  • Practitioner.Search
  • Practitioner.Search (DSTU2)
  • Practitioner.Search (R4)
  • Practitioner.Search (STU3)
  • PractitionerRole.Read (R4)
  • PractitionerRole.Read (STU3)
  • PractitionerRole.Search (R4)
  • PractitionerRole.Search (STU3)
  • Procedure.Read (Orders) (DSTU2)
  • Procedure.Read (Orders) (R4)
  • Procedure.Read (Orders, Surgeries) (STU3)
  • Procedure.Read (Surgeries) (R4)
  • Procedure.Search (Orders) (DSTU2)
  • Procedure.Search (Orders) (R4)
  • Procedure.Search (Orders, Surgeries) (STU3)
  • Procedure.Search (Surgeries) (R4)
  • Provenance.Read (R4)
  • RelatedPerson.Read (Proxy) (R4)
  • RelatedPerson.Search (Proxy) (R4)

In addition to the USCDI v1 FHIR APIs above, the following FHIR APIs qualify for USCDI v3 auto-distribution.

  • Binary.Read (Study) (R4)
  • Binary.Search (Study) (R4)
  • Condition.Read (Encounter Diagnosis) (R4)
  • Condition.Search (Encounter Diagnosis) (R4)
  • Coverage.Read (R4)
  • Coverage.Read (STU3)
  • Coverage.Search (R4)
  • Coverage.Search (STU3)
  • Media.Read (Study) (R4)
  • Media.Search (Study) (R4)
  • MedicationDispense.Read (Fill Status) (R4)
  • MedicationDispense.Search (Fill Status) (R4)
  • Observation.Read (Assessments) (R4)
  • Observation.Read (SDOH Assessments) (R4)
  • Observation.Read (SmartData Elements) (R4)
  • Observation.Read (Study Finding) (R4)
  • Observation.Search (Assessments) (R4)
  • Observation.Search (SDOH Assessments) (R4)
  • Observation.Search (SmartData Elements) (R4)
  • Observation.Search (Study Finding) (R4)
  • Procedure.Read (SDOH Intervention) (R4)
  • Procedure.Search (SDOH Intervention) (R4)
  • RelatedPerson.Read (Friends and Family) (R4)
  • RelatedPerson.Search (Friends and Family) (R4)
  • ServiceRequest.Read (Community Resource) (R4)
  • ServiceRequest.Read (Order Procedure) (R4)
  • ServiceRequest.Search (Community Resource) (R4)
  • ServiceRequest.Search (Order Procedure) (R4)
  • Specimen.Read (R4)
  • Specimen.Read (STU3)
  • Specimen.Search (R4)
  • Specimen.Search (STU3)


†† Only the following DSTU2 FHIR APIs qualify for MU3 auto-distribution.

  • AllergyIntolerance.Read
  • AllergyIntolerance.Search
  • Binary.Read
  • CarePlan.Read
  • CarePlan.Search
  • Condition.Read
  • Condition.Search
  • Device.Read
  • Device.Search
  • DiagnosticReport.Read
  • DiagnosticReport.Search
  • DocumentReference.Read
  • DocumentReference.Search
  • Goal.Read
  • Goal.Search
  • Immunization.Read
  • Immunization.Search
  • Medication.Read
  • Medication.Search
  • MedicationOrder.Read
  • MedicationOrder.Search
  • MedicationStatement.Read
  • MedicationStatement.Search
  • Observation.Read
  • Observation.Search
  • Patient.Read
  • Patient.Search
  • Practitioner.Read
  • Practitioner.Search
  • Procedure.Read
  • Procedure.Search

Search Parameters

FHIR Search Parameters

Contents

  • Overview
  • General Considerations
    • Native Search Parameters
    • Post-Filter Search Parameters
  • Common FHIR Parameters
    • _id Parameter
    • _count Parameter
    • _include Parameter
    • _revInclude Parameter
      • Provenance
      • AuditEvent

Overview

This document describes general considerations for using FHIR search parameters, including the two categories of search parameters that Epic makes available across FHIR resources. It also explains which common (for example “underscore”) FHIR search parameters are currently supported for use across all Epic FHIR resources, what those common parameters do, and Epic’s recommendations for using them.

General Considerations

Epic supports many search parameters defined by the FHIR standard. These search parameters use two different types of logic to retrieve results and can be categorized as either native search parameters or post-filter search parameters. Refer to Epic’s documentation for individual FHIR resources to see which parameters are native search parameters and which are post-filter search parameters.

Native Search Parameters

Native search parameters are parameters that Epic supports with built-in database logic or indexing. To give one example, the patient parameter is a native search parameter available in all patient-centric Epic FHIR resources. All parameters released in the February 2024 version and earlier are native search parameters, and we plan to continue to provide support for and release new native search parameters in the future.

Post-Filter Search Parameters

Post-filter search parameters are available starting in the May 2024 version of Epic. Epic supports post-filter search parameters only for the R4 version of FHIR. Epic supports post-filtering so that your integration doesn’t have to. This gives you the flexibility to search and filter on the widest variety of parameters possible within the bounds of the HL7 FHIR specification.

When a FHIR query includes post-filter search parameters, Epic first performs a search using native search parameters and converts those results to the FHIR format. Then, the results are further reduced based on the post-filter search parameters that were supplied in the query. As a result, the performance cost of using post-filter search parameters is directly proportional to how many resources match to the native search parameters in a request. For example, if a theoretical patient has 5000 lab results, but only one of those lab results matches to a post-filter search parameter value in your query, the Epic database has to first retrieve all 5000 lab results before it can filter them down to the single result that is ultimately returned to your application.

"Unsupported" Post-Filter Search Parameters

Some post-filter parameters are marked as “Unsupported” in Epic documentation. This means that Epic does not currently support any of the corresponding response element(s) that the search parameter is meant to query for, so using the parameter will always cause all results to be filtered out of the response.

Common FHIR Parameters

Common FHIR search parameters, which include a variety of underscore-prefixed parameters, can modify how results are returned in a Search API’s response. See the table below for the common search parameters that are supported by Epic and the versions in which they are supported.


Parameter Supported in DSTU2? Supported in STU3? Supported in R4?
_id Yes Yes Yes
_count Yes, for the Observation resource only Yes Yes
_include No Yes Yes
_revInclude No No Yes
_lastUpdated No No No

_id Parameter

The _id parameter is used to indicate a FHIR ID, allowing read-type functionality in a search FHIR API.

So why would you want to use _id in a search API instead of a read API? Well, what if you wanted to perform a read with a FHIR search parameter? The _id parameter allows you to do just that by using FHIR search parameters while still reading data about a single resource.

Examples:

GET https://hostname/instance/api/FHIR/STU3/Patient?_id=eYg3-1aJmCMq-umIIq2Njxw3&_include=Patient:generalPractitioner:Practitioner

_count Parameter

The _count parameter is used to limit the number of results returned by a search request. Once the number of results specified has been reached, the URL to the next page of results will be generated and included in the response. This URL contains a session ID which returns the next group of _count results. This is useful for resources that could return a large number of results (such as Observation), as using _count can minimize the demand on the server.  _count can be set to any number from 1 to 999.

Important note on resources that return many results:

If your search returns more than 1000 results, the search will automatically be broken into pages with 1000 results each, regardless of whether or not you use the _count parameter. This means that your app needs to support this functionality to ensure that you receive all applicable data associated with the API request.

The API response when paging behaves differently depending on what FHIR version you are on, as described below.

STU3 and Later FHIR Versions

This section summarizes the steps to use _count functionality.

Iterate over the Bundle.link array and look for the link.relation parameter. If there is a link where link.relation is "next", use the request in link.url to obtain the next page of resources. Continue until there is no longer a link.url with a link.relation of "next", indicating you are on the final page.

There will be a second URL in link.url with a link.relation of "self" on every page. In STU3 paging, the self link will contain a session ID that points to the current page of results. In R4 paging, the self link will include the valid search parameters that were used in the original request.

STU3 Sample Request

GET https://apporchard.epic.com/interconnect-aocurprd-username/api/FHIR/STU3/Observation?patient=e63wRTbPfr1p8UW81d8Seiw3&category=vital-signs&_count=10

STU3 Sample Response

{ 
   "resourceType": "Bundle",
    "type": "searchset",
    "total": 10,
    "link": [
        {
            "relation": "next",
            "url": "https://apporchard.epic.com/interconnect-aocurprd-oauth/api/FHIR/STU3/Observation?sessionID=16-BAF392808B2B11EA92E00050568B7BE6"
        },
        {
            "relation": "self",
            "url": "https://apporchard.epic.com/interconnect-aocurprd-oauth/api/FHIR/STU3/Observation?sessionID=16-854D71651G6711EA92E00050568B7BE6"
        }
...{response continues}

R4 Sample Request

GET https://apporchard.epic.com/interconnect-aocurprd-oauth/api/FHIR/R4/Observation?patient=e63wRTbPfr1p8UW81d8Seiw3&category=vital-signs&_count=10

R4 Sample Response

{ 
    "resourceType": "Bundle",
    "type": "searchset",
    "total": 10,
    "link": [
        {
            "relation": "next",
            "url": "https://apporchard.epic.com/interconnect-aocurprd-oauth/api/FHIR/R4/Observation?sessionID=16-BAF392808B2B11EA92E00050568B7BE6"
        },
        {
            "relation": "self",
            "url": "https://apporchard.epic.com/interconnect-aocurprd-oauth/api/FHIR/R4/Observation?patient=e63wRTbPfr1p8UW81d8Seiw3&category=vital-signs&_count=10"
        }
...{response continues}

DSTU2 FHIR Versions

Similar to the STU3 version, there will be a link.relation parameter which tells you the relationship of the URL in the API response relative to the API URL in the current request. The link.relation parameters that can be returned are:

  • self: a url to the same page of results as your current page
  • previous: a url to the previous page of results relative to your current page
  • next: a url to the next page of results relative to your current page

The first page of results will only return relations of “self” and “next” while the last page of results will only return relations of “self” and “previous”. If a page only returns the “self” parameter, this indicates that only one page of results exists. Omitting a page number and session ID defaults to displaying the first page of results. In the DSTU2 version, paging functionality only exists for the Observation resource.

  1. Perform a Search with the FHIR Observation resource, and include the parameter “_count=<# of resources to return>” in the URL. This will control the number of resources returned in a single API call.
GET https://apporchard.epic.com/interconnect-aocurprd-username/api/FHIR/DSTU2/Observation?patient=e63wRTbPfr1p8UW81d8Seiw3&category=vital-signs&_count=10
  1. The beginning of the first response will look similar to that shown below. The “self” relation contains a URL that points to the current page of results. The “next” relation contains a URL that points to the page of results following the current page. If the first page contains all results, only the “self” relation will appear.
{
    "resourceType": "Bundle",
    "type": "searchset",
    "link": [
        {
            "relation": "self",
            "url": "https://apporchard.epic.com/interconnect-aocurprd-oauth/api/FHIR/DSTU2/Observation?patient=e63wRTbPfr1p8UW81d8Seiw3&category=vital-signs&_count=10"
        },
        {
            "relation": "next",
            "url": "https://apporchard.epic.com/interconnect-aocurprd-oauth/api/FHIR/DSTU2/Observation?patient=e63wRTbPfr1p8UW81d8Seiw3&category=vital-signs&_count=10&session=989750&start=2"
        }
...{response continues}
  1. As long as there are more than two pages, the response will contain three URLs now, “self”, “next”, and “previous”. As a counterpart to the “next” relation, the “previous” relation indicates a URL that points to the page of results preceding the current page of results in the response.
{
    "resourceType": "Bundle",
    "type": "searchset",
    "link": [
        {
            "relation": "self",
            "url": "https://apporchard.epic.com/interconnect-aocurprd-oauth/api/FHIR/DSTU2/Observation?patient=e63wRTbPfr1p8UW81d8Seiw3&category=vital-signs&_count=10&session=989750&start=2"
        },
        {
            "relation": "next",
            "url": "https://apporchard.epic.com/interconnect-aocurprd-oauth/api/FHIR/DSTU2/Observation?patient=e63wRTbPfr1p8UW81d8Seiw3&category=vital-signs&_count=10&session=989750&start=3"
        },
        {
            "relation": "previous",
            "url": "https://apporchard.epic.com/interconnect-aocurprd-oauth/api/FHIR/DSTU2/Observation?patient=e63wRTbPfr1p8UW81d8Seiw3&category=vital-signs&_count=10&session=989750&start=1"
        }
...{response continues}
  1. When you reach the last page of results, the response will look something like that shown below. There will still be a “self” and “previous” relation, but no “next” relation as there are no additional pages to display.
{
    "resourceType": "Bundle",
    "type": "searchset",
    "link": [
        {
            "relation": "self",
            "url": "https://apporchard.epic.com/interconnect-aocurprd-oauth/api/FHIR/DSTU2/Observation?patient=e63wRTbPfr1p8UW81d8Seiw3&category=vital-signs&_count=10&session=989750&start=3"
        },
        {
            "relation": "previous",
            "url": "https://apporchard.epic.com/interconnect-aocurprd-oauth/api/FHIR/DSTU2/Observation?patient=e63wRTbPfr1p8UW81d8Seiw3&category=vital-signs&_count=10&session=989750&start=2"
        }
...{response continues}

_include Parameter

The _include parameter is used to return resources related to the initial search result. Any other resource included as a reference can be returned in the response bundle. This reduces the number of requests needed to see information. For example, when searching for a patient's encounters, you can include the encounter providers as well.

In the February 2021 version of Epic and prior versions, included references are returned immediately following the associated search result. Starting in the May 2021 version of Epic for R4 FHIR resources, included resource references from all search results are at the end of the response bundle rather than after each individual search result, and each unique referenced resource instance is included only once in the bundle. Consumers of FHIR search results should be able to accept both bundle formats until a future version when we expect all resources to eventually transition to the latter format.

Epic supports the _include parameter primarily for FHIR search interactions. In addition, it is supported for particular operation types, like Appointment.$find.

If you want to return multiple referenced resources, this can be accomplished by repeating the _include criteria. A single _include parameter should not include multiple values. Instead, the parameter is repeated for each different include criteria.

For this parameter to work, the Search response must include a reference to another FHIR resource.

General format: _include = [resource you’re searching]:[name of reference element you want included]:[type of resource if element can refer to multiple (optional)]

  • [resource you’re searching] – this is the initial API you are calling
    • Ex: When calling Observation.Search, you are searching the Observation resource.
  • [name of reference element you want included] – one of the response parameters for that API that has a type of “Reference”
  • [type of resource if element can refer to multiple] – when the second parameter could refer to multiple resources, the third parameter specifies which resource to include

Examples:

  • Immunization.Search
    • Parameter in response: Practitioner.actor (Reference)
    • From this, the second parameter would be “actor”
    • _include=Immunization:actor:Practitioner
    • Note: there is only one actor for Immunizations so the third parameter is optional (_include=Immunization:actor would also work here)
  • Patient.Search
    • Parameter in response: generalPractitioner (Reference (Practitioner))
    • From this, the second parameter would be “generalPractitioner”
    • _include=Patient:generalPractitioner:Practitioner
  • Appointment $find (STU3)
    • Parameters in response: Participant.actor (Reference(Patient|Practitioner|Location))
    • From this, the second parameter would be "actor" just like in the first example
    • Unlike the first example, you need to specify a third parameter as there are three potential actors referenced. Any of the following examples are valid here:
      • _include=Appointment:actor:Patient
      • _include=Appointment:actor:Practitioner
      • _include=Appointment:actor:Location
  • Observation.Search
    • Parameters in response:
      • performer (Reference (Practitioner))
      • subject (Reference (Patient))
    • To include both resources, the _include criteria is used multiple times
    • _include=Observation:performer:Practitioner&_include=Observation:subject:Patient

_revInclude Parameter

The _revInclude parameter (reverse include) is used to return a particular resource, along with other resources that refer to that resource. For this parameter to return results, the FHIR resource specified in the search response must be referenced by the resource specified in the _revInclude parameter. Epic's implementation of the _revInclude parameter supports returning Provenance and AuditEvent data.

Provenance

Epic supports the _revInclude parameter to fetch provenance data for search interactions with resources that are part of the U.S Core Data for Interoperability starting in Epic version February 2020. Support for all other resources begins in August 2023.

Parameter format: _revInclude=Provenance:target

The response will have the following format:

Element Type Cardinality Description
target Reference (resource) 1..* The resource this provenance supports
recorded Instant 1..1 Timestamp that the activity was recorded/updated
agent Backbone Element 1..* Actor involved
agent.type CodeableConcept 0..1 How the agent participated
agent.who Reference (Practitioner | Patient | Organization) 1..1 Who participated
agent.onBehalfOf Reference (Organization) 0..1 Who the agent is representing.

Examples:

Sample Request
https://www.example.org/api/FHIR/R4/DocumentReference?_id=eiuHBlNpiGxJwXRUWVqHjN9QaKO0ODrkAJrvUByRLuMc3&_revInclude=Provenance:target
Sample Response
{
    "link": [{
        "relation": "self",
        "url": "https://www.example.org/api/FHIR/R4/Provenance/eC7YEH-XJufbPDHTDFWShEk2b9Ki98xTRyLbhFXwQ6sfcjhyfmeem2nFqdxZYHcS0FhROyzJUToN.o1UE0DQeXQ3"
    }],
    "fullUrl": "https://www.example.org/api/FHIR/R4/Provenance/eC7YEH-XJufbPDHTDFWShEk2b9Ki98xTRyLbhFXwQ6sfcjhyfmeem2nFqdxZYHcS0FhROyzJUToN.o1UE0DQeXQ3",
    "resource": {
        "resourceType": "Provenance",
        "id": "eC7YEH-XJufbPDHTDFWShEk2b9Ki98xTRyLbhFXwQ6sfcjhyfmeem2nFqdxZYHcS0FhROyzJUToN.o1UE0DQeXQ3",
        "target": [{"reference": "DocumentReference/eiuHBlNpiGxJwXRUWVqHjN9QaKO0ODrkAJrvUByRLuMc3"}],
        "recorded": "2020-06-23T07:33:12Z",
        "agent": [{
            "type": {
                "coding": [{
                    "system": "http://terminology.hl7.org/CodeSystem/provenance-participant-type",
                    "code": "author",
                    "display": "Author"
                }],
                "text": "Author"
            },
            "who": {
                "reference": "Practitioner/eIOnxJsdFcfgJ4iy716I3MA3",
                "display": "Starter Provider, MD"
            },
            "onBehalfOf": {"display": "Example Organization"}
        },
        {
            "type": {
                "coding": [{
                    "system": "http://terminology.hl7.org/CodeSystem/provenance-participant-type",
                    "code": "transmitter",
                    "display": "Transmitter"
                }],
                "text": "Transmitter"
            },
            "who": {
                "display": "Example Organization"
            }
        }]
    },
    "search": {"mode": "include"}
}

AuditEvent

Epic currently supports using this parameter to fetch audit event data for search interactions with select FHIR resources starting in Epic version May 2021. AuditEvent returns audit event information for a specific resource instance. Examples of audited events include edits to an order or the signing of a note. AuditEvent is supported only for a subset of FHIR version R4 resources.

Parameter format: _revInclude=AuditEvent:entity

The response will have the following format:

Element Type Cardinality Description

type

Coding

1..1

Type or identifier of the event

recorded

Instant

1..1

Time when the event was recorded

agent

BackboneElement

1..*

Actors involved in the event

agent.role

CodeableConcept

0..*

Agent role in the event

agent.who

Reference (PractitionerRole | Practitioner | Organization | Device | Patient | RelatedPerson)

 

0..1

Identifier of who participated

agent.requestor

Boolean

1..1

Whether the user is the initiator

source

BackboneElement

1..1

The audit event reporter

source.observer

Reference (PractitionerRole | Practitioner | Organization | Device | Patient | RelatedPerson)

1..1

The identity of the source detecting the event

entity

BackboneElement

0..*

Data or objects used

entity.what

Reference (Any)

0..1

Specific instance of the resource

entity.detail

BackboneElement

0..*

Additional information about the entity

entity.detail.type

String

1..1

Name of the property that changed

 

String

1..1

Property value

entity.detail extension (audit-event-detail-state)

Extension

0..1

Indicates whether the value in entity.detail.valueString is the value before or after the audited event

Examples:

Sample Request
https://www.example.org/api/FHIR/R4/MedicationRequest?_id=eiuHBlNpiGxJwXRUWVqHjN9QaKO0ODrkAJrvUByRLuMc3&_revInclude=AuditEvent:entity
Sample Response
{
"link": [{
"relation": "self",
"url": https://www.example.org/api/FHIR/R4/AuditEvent/erLtgh1AreI5Y0dE6YVZxVzXV3FaxnhsMWrsnBmt-JUcxF7D14JKfYlolngfGblRgIoJNa8UzOyGJ38eJBF3aKn94wMPR9m6M.LWkVmDWuRxpwYKzNEloKILbZHuXeidD3
}],
"fullUrl": https://www.example.org/api/FHIR/R4/AuditEvent/erLtgh1AreI5Y0dE6YVZxVzXV3FaxnhsMWrsnBmt-JUcxF7D14JKfYlolngfGblRgIoJNa8UzOyGJ38eJBF3aKn94wMPR9m6M.LWkVmDWuRxpwYKzNEloKILbZHuXeidD3,
"resource": {
"resourceType": "AuditEvent",
"id": "erLtgh1AreI5Y0dE6YVZxVzXV3FaxnhsMWrsnBmt-JUcxF7D14JKfYlolngfGblRgIoJNa8UzOyGJ38eJBF3aKn94wMPR9m6M.LWkVmDWuRxpwYKzNEloKILbZHuXeidD3",
"type": {
"system": "http://open.epic.com/FHIR/StructureDefinition/audit-event-type",
"code": "Modify Order",
                    "display": "Modify Order"
},
"recorded": "2020-12-09T21:00:03Z",
"agent": [{
       "role": [{
              "coding": [{
                     "system": "http://open.epic.com/FHIR/StructureDefinition/audit-event-agent-role",
                     "code": "Ordering Provider",
                     "display": "Ordering Provider"
              }]
       }],
"who": {
"reference": "Practitioner/eEISPNvH1RKgu1t69j6Bx2g3",
                           "display": "Starter Provider, MD"
                     },
                     "requestor": true
              }],
              "source": {
                     "observer": {"display": "Example Organization"}
              },
              "entity": [{
                     "what": {"reference": "MedicationRequest/eiuHBlNpiGxJwXRUWVqHjN9QaKO0ODrkAJrvUByRLuMc3"},
                     "detail": [{
                                  "type": "Comment",
                                  "valueString": "Order Modified"
                           },
                           {
                                  "type": "Administration instructions",
                                  "valueString": "Administration Instructions edited"
                           }
              }]
       }
       "search": {"mode": "include"}
}

FHIR Bulk Data Access Tutorial

FHIR Bulk Data Access Tutorial

Contents

  • Background
  • Important Considerations for Using Bulk Data
    • Technical Constraints
    • When to Use Bulk Data
      • Good Use Cases for Bulk Data
      • Poor Use Cases for Bulk Data
    • Best Practices
  • Epic's Implementation of the Bulk Data Standard
  • Using the Bulk Data APIs
    • Client Configuration
    • Kicking Off the Bulk Data Request
      • _type Parameter
      • Include Associated Data
      • _typeFilter Parameter
      • Requirements and Restrictions
    • Checking the Status of the Request
    • Viewing Resource Files
    • Viewing Request Errors
    • Deleting Requests

Background

The FHIR Bulk Data Access specification, also known as Flat FHIR, gives healthcare organizations the ability to provide external clients with large amounts of data for a population of patients formatted as FHIR resources. This functionality is available starting in the August 2021 version of Epic.

Important Considerations for Using Bulk Data

The bulk data API is powerful, and because it uses the FHIR standard, you might find it easier to export and process data from bulk data if you are already familiar with that standard. However, there are important things to take into consideration before deciding to use bulk data. As always, carefully consider your use case to determine whether bulk data is the best solution.

Technical Constraints

Bulk data runs on an organization's operational database, so it is important to consider performance. Bulk data exports large amounts of data for large groups of patients, which takes more time to complete the larger the data set or patient population. Responses are not instantaneous, so use cases of bulk data should not rely on immediate responses. Later in this tutorial, we will cover how to set up requests to help minimize this wait time.

Also, bulk data requests are not incremental. The API collects all data for the requested patients and resources before it starts returning any data. You cannot retrieve any results until all results are ready.

When to Use Bulk Data

As with any data exchange, form should follow function. While having all of this data formatted as FHIR resources might be exciting or sound easier to work with, the data won't be useful if bulk data's technical capabilities don't align with your use case. Consider your exchange paradigm and workflow needs first, then see if bulk data meets those requirements. If it does - great! If it doesn't, explore other options to interoperate with Epic on open.epic. Below are examples of use cases that fit bulk data and some that don't.

Good Use Cases for Bulk Data

  • A one-time load of data in preparation for continuous data exchange using other methods
  • Monthly loads of a targeted set of data (for example, _type=Patient,Condition)
  • Weekly export of a dynamic group of patients (for example, all patients discharged in the last week with a certain diagnosis)
  • Weekly loads of small patient populations (less than one hundred), such as for registry submissions

Poor Use Cases for Bulk Data

  • Data synchronization with data warehouses or other databases
  • Periodic loads of large amounts of clinical data
  • Incremental data loads
  • Data exports for groups of over one thousand patients

Best Practices

Epic has several recommendations that can help you have the best experience with bulk data APIs.

  • Limit group sizes to around a thousand patients or fewer.
  • Use the _type parameter whenever possible to improve response time and minimize storage requirements.
  • For groups of under one hundred patients, check the request status every ten minutes, or use exponential backoff. For groups of over one hundred patients, check the status every thirty minutes, or use exponential backoff. More information on exponential backoff can be found in the bulk data export specification.
  • After retrieving all of the resource file content, use the FHIR Bulk Data Delete Request API to allow the server to clean up the data from your request.

Epic's Implementation of the Bulk Data Standard

The implementation guide for Bulk Data Access is on HL7's website. Starting in the August 2021 version, Epic supports the 1.0.1 version of the specification for R4 FHIR resources. We have also incorporated some features from the 1.1.0 version to provide additional functionality. Epic supports only the Group Export operation. We do not support _since or other bulk data operations at this time.

Using the Bulk Data APIs

Now that you have determined that bulk data is an appropriate solution for your use case, let’s go through how to use the APIs. This diagram shows the typical flow of the bulk data APIs.

Client Configuration and Authorization

External clients must be authorized to use the bulk data APIs: Bulk Data Kick-off, Bulk Data Status Request, Bulk Data File Request, and Bulk Data Delete Request. Additionally, they must be authorized for the R4 search API for each resource they want to request, for example, AllergyIntolerance.Search (R4). The healthcare organization you're integrating with also needs to authorize your client to access the specific groups of patients. Work with that organization to enable a group that is appropriate for your use case.

This tutorial assumes you are passing a form of authorization covered in one of our authentication guides. It is generally assumed that in production environments FHIR APIs will use OAuth 2.0; however, it's important to note FHIR APIs do support HTTP Basic Authentication.

Kicking Off the Bulk Data Request

To begin, you need the base URL of the organization you want to integrate with, as previously described in the FHIR Tutorial. Let's say this is our base URL:

https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4

Bulk data uses the Group resource for the export operation. Contact the organization you are integrating with to discuss what group of patients to use for your integration and to get the FHIR ID for that group.

Use the Group FHIR ID to call the group export operation:

https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/Group/eIscQb2HmqkT.aPxBKDR1mIj3721CpVk1suC7rlu3yX83/$export

The request above returns results for a default set of R4 resources in the ndjson format. The default set includes the resources in the U.S. Core Data for Interoperability (USCDI) data classes, resources from the patient compartment of the bulk data access specification, and additional supporting resources outside of the patient compartment. Starting in the November 2021 version of Epic (and in the August 2021 version with a special update), Provenance is also in the default set of resources.

Epic also supports the following parameters for the group export operation:

  • _type
  • includeAssociatedData
  • _typeFilter (starting in the November 2023 version of Epic)

_type Parameter

The _type parameter accepts a comma-delimited list of FHIR resource types. When used, bulk data returns only the resource types specified in the parameter. Epic recommends using this parameter whenever possible because limiting the scope of the request to only the resources you need decreases both response times and the amount of data stored.

The _type parameter is also the only way to retrieve Binary resources. Binary files can become very large in this workflow, so Binary resources are not returned by default. Certain very large Binary files cannot be retrieved by bulk data operations at all. If a Binary file is not returned, you can still get the ID from the resource that it's associated with, for example, from the DocumentReference.content.attachment.url element, and perform a separate Binary.Read request for the content. Starting in the November 2021 version of Epic and in August 2021 by special update, an OperationOutcome with the resource type and FHIR ID is included in the response file.

For resources where Epic doesn't support a search by Patient ID (for example, Medication), a resource that has search by Patient ID and references the resource of interest (for example, MedicationRequest) should be included. For example, you could use the _type parameter to limit the request to include only Patient, MedicationRequest, and Medication resources as follows:

https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/Group/eIscQb2HmqkT.aPxBKDR1mIj3721CpVk1suC7rlu3yX83/$export?_type=patient,medicationrequest,medication

includeAssociatedData Parameter

The includeAssociatedData parameter can be set to "LatestProvenanceResources" to include the Provenance resource associated with each resource instance included in the bulk data files.

https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/Group/eIscQb2HmqkT.aPxBKDR1mIj3721CpVk1suC7rlu3yX83/$export?includeAssociatedData=LatestProvenanceResources

_typeFilter Parameter

The _typeFilter parameter accepts a comma-delimited list of FHIR resource search queries. This is used to filter the results of the bulk data export so you can retrieve only the data you need. To use _typeFilter for a specific resource type, that resource must be included in the _type parameter. You cannot use _typeFilter without also using _type. Query strings for multiple resources can be included in _typeFilter.

Query strings in the _typeFilter parameter use the same search parameters as the R4 search APIs, and are formatted in much the same way. The general format is “_typeFilter=Resource%3Fparameter%3Dvalue\,value%26parameter%3Dvalue.” The standard query string reserved characters (question mark, equals sign, and ampersand) must be URL encoded within _typeFilter and commas must be backslash escaped.

Here is an example of a simple query string in _typeFilter. This would limit the MedicationRequest resources returned in the bulk data export to those with a category of “inpatient.”

https://vendorservices.epic.com/interconnect-amcurprd-oauth/api/FHIR/R4/Group/eIscQb2HmqkT.aPxBKDR1mIj3721CpVk1suC7rlu3yX83/$export?_type=MedicationRequest&_typeFilter=MedicationRequest%3Fcategory%3Dinpatient

This is an example of a more complex use of _typeFilter. This would limit the Observation resources returned to vital signs from 2023 and laboratory results from 2022 onwards, and the Condition resources returned to active problems on the problem list.

https://vendorservices.epic.com/interconnect-amcurprd-oauth/api/FHIR/R4/Group/eIscQb2HmqkT.aPxBKDR1mIj3721CpVk1suC7rlu3yX83/$export?_type=Observation,Condition&_typeFilter=Observation%3Fcategory%3Dvital-signs%26date%3D2023,Observation%3Fcategory%3Dlaboratory%26date%3Dge2022,Condition%3Fcategory%3Dproblem-list-item%26clinical-status%3Factive

When you use _typeFilter, we recommend following these best practices:

  1. Avoid using multiple query strings that would have overlapping results. For example, including “Observation%3Fcategory%3Dlaboratory” and “Observation%3Fcategory%3Dlaboratory\,vital-signs” in the same request.
  2. When possible, simplify _typeFilter values into a single query string. For example, rather than “_typeFilter=MedicationRequest%3Fstatus%3Dactive,MedicationRequest%3Fstatus%3Don-hold” use “_typeFilter=MedicationRequest%3Fstatus%3Dactive\,on-hold”
  3. Test your query strings with the respective resources in a search request to ensure they return the results you expect before using the query strings in _typeFilter.

Note that the following are not supported by _typeFilter:

  • Searching by patient, subject, or _id
  • Query strings with search result parameters, such as _count, _include, or _revInclude
  • Query strings for the Patient resource
  • Query strings for resources that don't contain patient information
  • Applying the search query strings to resources included by reference

Requirements and Restrictions

When you have compiled all components of the request URL, you can submit the GET request to kick-off the bulk data workflow. Note that by default, a client can request a specific group of patients only once in a twenty-four-hour period. If you need to request bulk data more frequently, work with the Epic organization you're integrating with to configure an appropriate request window.

Your request must include the following headers:

  • Accept: application/fhir+json
  • Prefer: respond-async

Checking the Status of the Request

After kicking off the bulk data request, use the status API to track the progress of the request. Note that the same client and user that made the kickoff request must make the status request. In the response headers of the kick-off request, the value of the “Content-Location” header is the status URL for this bulk data request. Each bulk data request has a unique identifier used to request the status of a specific bulk data request.

https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/BulkRequest/B0F84FB8D37411EB92726C04221B350C

If the bulk data request has not finished processing, the response body is empty. An approximate measure of progress is available in the "X-Progress" response header. The value of this header is "Searched X of Y patients" where "Y" is the number of patients in the group and "X" is the number of patients processed so far. This is only an approximate measure of progress because there is additional processing required after all patients have been searched, so it's possible for the header to be set to "Searched 100 of 100 patients" with no response body returned.

Epic recommends pinging for the request status every ten minutes for groups with a hundred or fewer patients, every thirty minutes for groups over a hundred, or using exponential backoff as described in the bulk data export specification.

After the request is completed, the status API returns the URLs for the resource files. For more information on the structure of the response, reference the API details.

{
    "transactionTime": "2021-06-23T16:39:52Z",
    "request": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/Group/eIscQb2HmqkT.aPxBKDR1mIj3721CpVk1suC7rlu3yX83/$export?_type=patient,encounter,condition",
    "requiresAccessToken": "true",
    "output": [
        {
            "type": "Patient",
            "url": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/BulkRequest/9ED3042CD44111EB84F2D2068206269D/e19upATM-PTKGZuHsy04IUQ3"
        },
        {
            "type": "Condition",
            "url": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/BulkRequest/9ED3042CD44111EB84F2D2068206269D/e8D8N1JwB3qCiHJkmeV.98w3"
        }
    ],
    "error": [
        {
            "type": "OperationOutcome",
            "url": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/BulkRequest/9ED3042CD44111EB84F2D2068206269D/eKbGgVw9pUn6xIiB8kZ756w3"
        }
    ]
}

Viewing Resource Files

Bulk data generates a separate file for each resource type. The "output" elements in the status API response lists each resource type and the corresponding file request URL. If a single resource has a very large number of results, the data is split into multiple files, all linked in the response. There is a maximum of three thousand resource instances per file, but the actual number of resource instances in a given file varies based on the size of the resource instances included in the file. To view a file, you'll use the file request API. The file URLs for this API are found in the file manifest from the status API. Note that the same client and user that made the kickoff request must make the file request.

https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/BulkRequest/9ED3042CD44111EB84F2D2068206269D/e19upATM-PTKGZuHsy04IUQ3

The format of the bulk data files is ndjson. The ndjson format is similar to JSON, but is newline-sensitive. Resource instances are included in the bulk data file through a search by Patient ID or through a reference in a previously gathered resource instance. The files do not differentiate by patient, so results for all patients are included in each resource file.

Viewing Request Errors

In addition to the resource files, if there are any request-level errors, the "error" element in the status API response includes the URL for the OperationOutcome file. This file includes only request-level errors. Bulk data does not return resource-level errors.

Error Code

Error Text

Notes

59130

"Some information was not or may not have been returned due to business rules, consent or privacy rules, or access permission constraints."

This error is logged when a resource is requested that the client is not authorized for. OperationOutcome.issue.diagnostics lists the resource.

59100

"Content invalid against the specification or a profile."

This error is logged when a parameter is included in the request that Epic doesn't support in bulk data. OperationOutcome.issue.diagnostics lists the parameter.

59136

"The resource or profile is not supported."

This error is logged when a resource is requested that Epic doesn't support in bulk data. OperationOutcome.issue.diagnostics lists the resource.

59176

"Error Converting FHIR Resource to ndjson. Resource: <resource type> FHIR ID: <resource FHIR ID>"

This error is logged when a resource instance cannot be included in the resource file because of its size. The resource instance can be requested separately using the resource's read interaction.

This error is logged starting in the November 2021 version of Epic and in August 2021 by special update.

Deleting Requests

If you no longer want to run a request after starting it, or no longer need the data stored, the bulk data delete API is available. This API uses the same URL as the status API, but uses the DELETE HTTP method rather than GET.

DELETE https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/BulkRequest/B0F84FB8D37411EB92726C04221B350C

If you delete a request before it is completed (the status API hasn’t returned any content), the request does not finish processing and is marked as deleted in the receiving system. The deleted request does not count towards your request per group per client per time period limit.

If you delete a request after it is completed (the status API returns file URLs), the request remains marked as completed in the system, but the data generated by the request is deleted. Deleting the generated data after you've retrieved it can help prevent excessive storage requirements for the server. In this case, the request does count towards the client's request limit. Epic recommends running the delete API after you finish retrieving the data files.

CDS Hooks Tutorial

CDS Hooks Tutorial

Introduction

Clinical Decision Support (CDS) Hooks is an HL7 standard for in-workflow decision support integrations between electronic health record systems and remote, real-time, provider-facing decision support services. This tutorial describes Epic’s support for this standard.

Epic’s support for CDS Hooks is built on a workflow engine and CDS rule engine. Organizations using Epic can configure native CDS alerts by defining the workflow point at which they are triggered, known as the hook, and the criteria to evaluate when they are triggered. These criteria determine whether a CDS alert appears to the clinician and, if it does, what information it contains. Epic’s implementation of CDS Hooks enables native criteria to call a remote CDS Hooks API when evaluating whether to show a CDS alert. In Epic, the native CDS alert is referred to as an OurPractice Advisory (OPA).

Guidance to CDS Developers

CDS Hooks is a powerful integration technology because it directly interacts with clinician workflows. Treat the ability to interact with clinicians carefully. As a CDS developer, you now share responsibility for a positive user experience. Your CDS service must be fast, must avoid alert fatigue, and should improve over time.

Hooks

As of Epic version May 2021, Epic primarily supports three standard hooks built on pre-existing CDS alert workflow triggers. Note that Epic supports many more native workflow triggers than CDS Hooks has standardly defined.

patient-view

The patient-view hook is triggered in two scenarios:
  1. When a patient’s chart is opened. The native Epic trigger is named Open Patient Chart. Epic recommends against firing a CDS Hooks request every time a patient chart is opened because it results in a poor user experience. Instead, work with the health system to use this trigger in combination with more specific, native business criteria in Epic to limit the number of CDS Hooks requests.
  2. When a native Epic trigger calls a CDS Hooks service that is not standardized by CDS Hooks. For example, Enter Allergy is a native Epic workflow trigger that an organization can configure to show an OPA, including evaluating criteria that calls a CDS Hooks service. Because there is no industry standard hook for the Enter Allergy workflow step, we reuse the patient-view hook. The Epic-specific "com.epic.cdshooks.request.bpa-trigger-action" extension (see below) can be used to differentiate between native Epic triggers.

order-select

Epic supports the order-select hook in workflows when a clinician enters orders for a patient. In addition to the new selection of one or more orders (which triggers the order-select event), the clinician might have already selected other orders that have not yet been signed. The draftOrders JSON object contains a bundle of both the newly selected orders and the previously selected, unsigned orders. The "selections" array of FHIR identifiers identifies which of the unsigned orders from the draftOrders bundle are newly selected. Decision support using this hook should focus on the newly selected orders.

The draftOrders bundle can contain the MedicationRequest, ServiceRequest, or ProcedureRequest resource.

order-sign

The order-sign hook is triggered in Epic as the final step in the ordering process. It occurs after a clinician has clicked the Sign Orders button, but before the system has finalized the order. Decision support from this hook is the final chance for the clinician to revise the order. This hook allows for collection of all unsigned order details to send in the CDS Hooks call as MedicationRequest, ServiceRequest, or ProcedureRequest resources.

CDS Hooks Request

Prefetch

Prefetch can be a valuable method for optimizing the performance of your CDS service. Evaluate whether you can collect the data needed to inform your CDS service using the CDS Hooks prefetch model.

Prefetch within Epic can be configured in your CDS Hooks service’s OurPractice Advisory (LGL) record. Any resource that you’d like to prefetch must be appropriately scoped to your client and match the client’s primary FHIR version. Epic does not support automatically retrieving the contents of a CDS Hooks service's discovery endpoint.

Here is an example of how prefetch might be configured for a CDS service:

Prefetch Property Name FHIR Query
patientPatient/{{context.patientId}}
medicationsMedicationRequest?patient={{context.patientId}}&status=active
encounterDxCondition?patient={{context.patientId}}&encounter={{context.encounterId}}&category=encounter-diagnosis
user{{context.userId}}

Note that the {{context.patientId}}, {{context.encounterId}}, and {{context.userId}} prefetch tokens are all used here. These IDs are returned by default in the CDS Hooks request context field and are also available to use in constructing contextual read/search FHIR requests that are included in the initial request to the CDS Hooks service. In contrast to the FHIR IDs, which are always sent in the context, the patient prefetch property defined above returns the entire patient resource within the prefetch. For searches, the medications and encounterDx prefetch properties above show how to use prefetch tokens as query parameters when implementing your CDS Hooks service.

The {{context.userId}} prefetch token is unique in that it returns both a FHIR resource name and FHIR resource ID. If the clinician triggering the CDS Hooks request has a provider record in Epic, the request returns "PractitionerRole/". If the clinician does not have a provider record, the request returns "Practitioner/". Additionally, the {{userPractitionerId}} and {{userPractitionerRoleId}} prefetch tokens can be used in prefetch to return the FHIR resource ID for their respective resources.

Input Fields for JSON POST Body

Field

Description

hook

Epic supports the following hooks:

  • order-select
  • order-sign
  • patient-view

hookInstance

A universally unique identifier (UUID) for each hook call.

fhirServer

The base FHIR URL for the health system. Epic will always provide this. Reach out to the Client Systems Web and Server Systems team or EDI TS for the organization to obtain this.

fhirAuthorization

A structure holding an OAuth 2.0 bearer access token, which can be used to authenticate to Epic APIs for a short period of time.

context

The CDS Hooks specification (for example, patient-view) defines the context elements associated with each hook. Epic always sends patientId and userId.
If the hook is initiated from an encounter context, encounterId is sent.
For the order-select and order-sign hooks, draftOrders is always sent. Specific to order-select, the selections array identifies which orders are newly selected.

prefetch

Resources sent in the prefetch are configured by the health system and can include FHIR resources that are already supported by Epic.

Epic Extensions

Extension

Description

com.epic.cdshooks.request.bpa-trigger-action

The specific trigger action in Epic that is mapped to the hook. Common values include, but are not limited to:

  • 5 (General OPA section)
  • 6 (Enter problem)
  • 7 (Enter diagnosis)
  • 18 (Enter order)
  • 23 (Sign orders)
  • 26 (IP Admission OPA section)
  • 27 (IP Discharge OPA section)
  • 29 (IP Transfer OPA section)
  • 60 (Open patient chart)

com.epic.cdshooks.request.cds-hooks-specification-version

The CDS hooks specification version used by Epic.

com.epic.cdshooks.request.fhir-version

The primary FHIR version of the CDS service as specified during OAuth registration.

com.epic.cdshooks.request.criteria-id

The ID of the OPA criteria record in Epic. This value can be helpful during troubleshooting.

com.epic.cdshooks.request.epic-version

The version of Epic that the health system is currently using.

com.epic.cdshooks.request.cds-hooks-implementation-version

The internal version Epic assigns to CDS Hooks implementations. Can be used to determine what features are supported.

Example HTTP Request

{
  "hookInstance": "f399c67c-c703-11ea-af16-460231621f93",
  "fhirServer": "https://example.com/interconnect-instance-oauth/api/FHIR/R4",
  "hook": "patient-view",
  "fhirAuthorization": {
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ1cm46ZXBpYzpjZWMuY2RlIiwiY2xpZW50X2lkIjoiYWVhYzNkYTctNjQ4MS00NWFmLWFkNDEtNDJlNWU3MDExNDFkIiwiZXBpYy5lY2kiOiJ1cm46ZXBpYzpDdXJyZW50LURldmVsb3BtZW50LUVudmlyb25tZW50IiwiZXBpYy5tZXRhZGF0YSI6Ik4yTDdoSFE3clNxLXdQbkstcVMyZlBTSjY1Q1EzS1ZOVHhhTUlBZGRVTllwemQyOEE0Q2d4ZHA0SVg0NUI5YmE4TVVJdEl1NkEzYjBkYmU1c1ZlY093V1ZZN0tTd2VZb09KZDR5R2hmblcxdk9yOENoX195N2NrT1VtV005cnVhIiwiZXBpYy50b2tlbnR5cGUiOiJhY2Nlc3MiLCJleHAiOjE1OTQ4NjMzMzcsImlhdCI6MTU5NDg2MzAzNywiaXNzIjoidXJuOmVwaWM6Y2VjLmNkZSIsImp0aSI6ImU4MjFhMDA5LTgzNjAtNDRkYy1iZmMwLTEyZmU4ODA3MjQyYSIsIm5iZiI6MTU5NDg2MzAzNywic3ViIjoiZUJxZ0Foc2pVcHF3a3lJWm9LZU1jVUEzIn0.hF2Ir9717QxJ0TgiFXKN1bhRdicgUHEf_0tjZ7qiy2Enzsn7_dW4HaueJqg22vcnrEayLxlW4h6Q6J5He3-U7VXmGBFd5AKXxVLbTQZupN6x4owt5n_N1OJKDu5UVJdlWoq-fBVn2sEyDfvITQ-BK21MWXxGaEcnttJNxS0Mk847JnxBby2oLzAd5NnIbdT733S2nL36ViK_mVnkucLu4vxwVRcaec2zCpvqF8Pn2Crl3yKaPKQbRqc6y12hprdUeB2VxOm3H5KxJQKYnEGTKNCmCy5w3Yaz86KtP9OlX1e3n6Km3TSCtZbj0Gm7QIVyDARAlREPA_XzbgQ3ItJang",
    "token_type": "Bearer",
    "expires_in": 300,
    "scope": "ALLERGYINTOLERANCE.READ ALLERGYINTOLERANCE.SEARCH Observation.Read (Smoking History) Observation.Read (Labs) (R4) Observation.Read (Vitals) Observation.Read (Vitals) (R4) Observation.Search (Labs) (R4) Observation.Search (Smoking History) Observation.Search (Vitals) Observation.Search (Vitals) (R4) Practitioner.Read (R4) Practitioner.Search (DSTU2) PROCEDURE.READ PROCEDURE.SEARCH MedicationRequest.Read (R4) MedicationRequest.Search (R4) CONDITION.READ CONDITION.SEARCH Condition.Read (R4) Condition.Search (R4) PATIENT.READ PATIENT.SEARCH PRACTITIONER.READ PRACTITIONER.SEARCH OBSERVATION.READ OBSERVATION.SEARCH urn:Epic-com:Informatics.2014.Services.Order.AnnotateOrders AllergyIntolerance.Search (R4) AllergyIntolerance.Read (R4) Patient.Read (R4) Patient.Search (R4) Location.Read (R4) Encounter.Read (R4) Encounter.Search (R4) urn:Epic-com:Core.2016.Services.DataUtility.GetImportDataLog Condition.Create (R4) Medication.Read (R4) MEDICATION.READ (DSTU2) MEDICATION.READ MEDICATIONORDER.READ MEDICATIONORDER.READ (DSTU2) MEDICATIONORDER.SEARCH MEDICATION.SEARCH MedicationStatement.Read (R4) MEDICATIONSTATEMENT.READ (DSTU2) MEDICATIONSTATEMENT.READ MEDICATIONSTATEMENT.SEARCH MedicationStatement.Search (R4) Condition.Search (R4) Patient.Read (R4) Practitioner.Read (R4) Patient.Search (R4) Practitioner.Search (R4) Condition.Read (R4) Condition.Create (R4) AllergyIntolerance.Read (R4) AllergyIntolerance.Search (R4) Observation.Read (R4) Observation.Search (R4) Practitioner.Search (R4) PractitionerRole.Search (R4) PractitionerRole.Search (R4) medication medication.create Encounter.Read (R4) Encounter.Search (R4) ProcedureRequest.Read (R4) ProcedureRequest.Search (R4) EPIC.FHIR.R4.SERVICES.MEDICATIONREQUEST.CREATE EPIC.FHIR.R4.SERVICES.MEDICATIONREQUEST.CREATE Condition.Create (Encounter Diagnosis) (R4) Condition.Create (Problems) (R4) EPIC.FHIR.R4.SERVICES.PROCEDUREREQUEST.CREATE EPIC.FHIR.R4.SERVICES.SERVICEREQUEST.CREATE Condition.Read (Encounter Diagnosis) (R4) Condition.Read (Health Concern) (R4) Condition.Read (Problems) (R4) Condition.Search (Encounter Diagnosis) (R4) Condition.Search (Health Concern) (R4) Condition.Search (Problems) (R4) Observation.Read (Core Characteristics) (R4) Observation.Read (Labs) (R4) Observation.Read (LDA-W) (R4) Observation.Read (Obstetric Details) (R4) Observation.Read (Smoking History) (R4) Observation.Read (Vitals) (R4) Observation.Search (Core Characteristics) (R4) Observation.Search (Labs) (R4) Observation.Search (LDA-W) (R4) Observation.Search (Obstetric Details) (R4) Observation.Search (Smoking History) (R4) Observation.Search (Vitals) (R4) MedicationRequest.Read (R4) MedicationRequest.Search (R4) condition.create(diagn Condition.Create Problem (R4) PractitionerRole.Read (R4) PractitionerRole.Read (R4)",
    "subject": "aeac3da7-6481-45af-ad41-42e5e701141d"
  },
  "context": {
    "patientId": "eXoGxqgBaJuNkuahMYmiDhg3",
    "encounterId": "eFyoeOuWgXtlQmOQzPdkQWwy3s8a49yrUc-LtjwhWT6g3",
    "userId": "PractitionerRole/e-QokEGUJIzyynNdkCFrs9w3"
  },
  "prefetch": {
    "-user": {
      "resourceType": "PractitionerRole",
      "id": "e-QokEGUJIzyynNdkCFrs9w3",
      "active": true,
      "practitioner": {
        "reference": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/Practitioner/eqZb1t82mP4YeBjiLldSFPQ3",
        "display": "Family Medicine Physician"
      },
      "code": [
        {
          "coding": [
            {
              "system": "urn:oid:1.2.840.114350.1.13.861.1.7.10.836982.1040",
              "code": "1",
              "display": "Physician"
            }
          ],
          "text": "Physician"
        }
      ],
      "specialty": [
        {
          "coding": [
            {
              "system": "urn:oid:1.2.840.114350.1.72.1.7.7.10.688867.4160",
              "code": "10",
              "display": "Cardiology"
            }
          ],
          "text": "Cardiology"
        },
        {
          "coding": [
            {
              "system": "urn:oid:1.2.840.114350.1.72.1.7.7.10.688867.4160",
              "code": "5",
              "display": "Anesthesiology"
            }
          ],
          "text": "Anesthesiology"
        },
        {
          "coding": [
            {
              "system": "urn:oid:1.2.840.114350.1.72.1.7.7.10.688867.4160",
              "code": "15",
              "display": "Dermatology"
            }
          ],
          "text": "Dermatology"
        },
        {
          "coding": [
            {
              "system": "urn:oid:1.2.840.114350.1.72.1.7.7.10.688867.4160",
              "code": "18",
              "display": "Endocrinology"
            }
          ],
          "text": "Endocrinology"
        }
      ]
    },
    "patient": {
      "resourceType": "Patient",
      "id": "eXoGxqgBaJuNkuahMYmiDhg3",
      "extension": [
        {
          "extension": [
            {
              "valueCoding": {
                "system": "http://hl7.org/fhir/us/core/ValueSet/omb-race-category",
                "code": "UNK",
                "display": "Unknown"
              },
              "url": "ombCategory"
            },
            {
              "valueString": "Unknown",
              "url": "text"
            }
          ],
          "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race"
        },
        {
          "extension": [
            {
              "valueCoding": {
                "system": "http://hl7.org/fhir/us/core/ValueSet/omb-ethnicity-category",
                "code": "UNK",
                "display": "Unknown"
              },
              "url": "ombCategory"
            },
            {
              "valueString": "Unknown",
              "url": "text"
            }
          ],
          "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity"
        },
        {
          "valueCode": "M",
          "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex"
        }
      ],
      "identifier": [
        {
          "use": "usual",
          "type": {
            "text": "EPI"
          },
          "system": "urn:oid:1.2.840.114350.1.1",
          "value": "113822"
        },
        {
          "use": "usual",
          "type": {
            "text": "EXTERNAL"
          },
          "value": "Z16282"
        },
        {
          "use": "usual",
          "type": {
            "text": "FHIR"
          },
          "value": "TDjw8.slp7UtBKr3jNvYPsSGUb0qoZMC5OEhD5iu5ticB"
        },
        {
          "use": "usual",
          "type": {
            "text": "FHIR R4"
          },
          "value": "eXoGxqgBaJuNkuahMYmiDhg3"
        },
        {
          "use": "usual",
          "type": {
            "text": "INTERNAL"
          },
          "value": "    Z16282"
        },
        {
          "use": "usual",
          "system": "urn:oid:2.16.840.1.113883.4.1"
        }
      ],
      "active": true,
      "name": [
        {
          "use": "official",
          "text": "Family Medicine Physician",
          "family": "Physician",
          "given": [
            "Family Medicine"
          ]
        },
        {
          "use": "usual",
          "text": "Family Medicine Physician",
          "family": "Physician",
          "given": [
            "Family Medicine"
          ]
        }
      ],
      "gender": "male",
      "birthDate": "1981-06-24",
      "deceasedBoolean": false,
      "managingOrganization": {
        "reference": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/Organization/e1aU.Joccsz0IPnyexqMjXw3",
        "display": "EXAMPLE ORGANIZATION"
      }
    },
    "conditions": {
      "resourceType": "Bundle",
      "type": "searchset",
      "total": 0,
      "link": [
        {
          "relation": "self",
          "url": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/Condition?patient=eXoGxqgBaJuNkuahMYmiDhg3&_include=Condition:patient&_include=Condition:patient:organization"
        }
      ],
      "entry": [
        {
          "resource": {
            "resourceType": "OperationOutcome",
            "issue": [
              {
                "severity": "warning",
                "code": "processing",
                "details": {
                  "coding": [
                    {
                      "system": "urn:oid:1.2.840.114350.1.13.861.1.7.2.657369",
                      "code": "4101",
                      "display": "Resource request returns no results."
                    }
                  ],
                  "text": "Resource request returns no results."
                }
              }
            ]
          },
          "search": {
            "mode": "outcome"
          }
        }
      ]
    },
    "medications": {
      "resourceType": "Bundle",
      "type": "searchset",
      "total": 2,
      "link": [
        {
          "relation": "self",
          "url": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/MedicationRequest?patient=eXoGxqgBaJuNkuahMYmiDhg3"
        }
      ],
      "entry": [
        {
          "link": [
            {
              "relation": "self",
              "url": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/MedicationRequest/e4jAeTRRFdgN-QzpjNRbwQkDnp1BQlqrukNdC6BvYzU43"
            }
          ],
          "fullUrl": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/MedicationRequest/e4jAeTRRFdgN-QzpjNRbwQkDnp1BQlqrukNdC6BvYzU43",
          "resource": {
            "resourceType": "MedicationRequest",
            "id": "e4jAeTRRFdgN-QzpjNRbwQkDnp1BQlqrukNdC6BvYzU43",
            "identifier": [
              {
                "use": "usual",
                "system": "urn:oid:1.2.840.114350.1.13.861.1.7.2.798268",
                "value": "1000182255"
              }
            ],
            "status": "active",
            "intent": "order",
            "category": {
              "coding": [
                {
                  "system": "http://hl7.org/fhir/medication-request-category",
                  "code": "inpatient",
                  "display": "Inpatient"
                }
              ],
              "text": "Inpatient"
            },
            "medicationReference": {
              "reference": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/Medication/ehjM3OtjgdW8Bea3jIQgMFVhzPMcMELCBS5v775xzbLetNaOP5vZRG-7BUwFy1hYFzJ7rXwELBf63P6zbB4tI56DXbUTYtEZEWyFIzxmyONE3",
              "display": "IBUPROFEN 200 MG PO TABS"
            },
            "subject": {
              "reference": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/Patient/eXoGxqgBaJuNkuahMYmiDhg3",
              "display": "Physician, Family Medicine"
            },
            "authoredOn": "2020-07-06T20:25:56Z",
            "recorder": {
              "reference": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/Practitioner/eqZb1t82mP4YeBjiLldSFPQ3",
              "display": "Family Medicine Physician"
            },
            "dosageInstruction": [
              {
                "extension": [
                  {
                    "valueQuantity": {
                      "value": 2,
                      "unit": "tablet",
                      "system": "http://unitsofmeasure.org",
                      "code": "{tbl}"
                    },
                    "url": "https://open.epic.com/fhir/extensions/admin-amount"
                  },
                  {
                    "valueQuantity": {
                      "value": 400,
                      "unit": "mg",
                      "system": "http://unitsofmeasure.org",
                      "code": "mg"
                    },
                    "url": "https://open.epic.com/fhir/extensions/ordered-dose"
                  }
                ],
                "timing": {
                  "repeat": {
                    "boundsPeriod": {
                      "start": "2020-07-06T20:25:32Z"
                    },
                    "frequency": 1,
                    "period": 6,
                    "periodUnit": "h"
                  },
                  "code": {
                    "text": "Q6H PRN"
                  }
                },
                "asNeededBoolean": true,
                "route": {
                  "coding": [
                    {
                      "system": "urn:oid:1.2.840.114350.1.13.861.1.7.4.798268.7025",
                      "code": "15",
                      "display": "Oral"
                    },
                    {
                      "system": "http://snomed.info/sct",
                      "code": "260548002",
                      "display": "Oral"
                    }
                  ],
                  "text": "Oral"
                },
                "doseQuantity": {
                  "value": 400,
                  "unit": "mg",
                  "system": "http://unitsofmeasure.org",
                  "code": "mg"
                }
              }
            ]
          },
          "search": {
            "mode": "match"
          }
        },
        {
          "link": [
            {
              "relation": "self",
              "url": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/MedicationRequest/es5s.w.VqTbI-3sTtgDY0f.oJSZ8leYTXykPvcBCZrWQ3"
            }
          ],
          "fullUrl": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/MedicationRequest/es5s.w.VqTbI-3sTtgDY0f.oJSZ8leYTXykPvcBCZrWQ3",
          "resource": {
            "resourceType": "MedicationRequest",
            "id": "es5s.w.VqTbI-3sTtgDY0f.oJSZ8leYTXykPvcBCZrWQ3",
            "identifier": [
              {
                "use": "usual",
                "system": "urn:oid:1.2.840.114350.1.13.861.1.7.2.798268",
                "value": "1000182291"
              }
            ],
            "status": "active",
            "intent": "order",
            "category": {
              "coding": [
                {
                  "system": "http://hl7.org/fhir/medication-request-category",
                  "code": "inpatient",
                  "display": "Inpatient"
                }
              ],
              "text": "Inpatient"
            },
            "medicationReference": {
              "reference": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/Medication/eIBy3xFZsuXmpUpNLdojkXC2jhRNQyq0NyjvMBlrASIS4dl.7I3naftOtzE1KVToZwnuJ5ty7Zonwkhzz9kHIeEYjUV102TvDEv7zaGJFkKQ3",
              "display": "MITOMYCIN 40 MG IV SOLR"
            },
            "subject": {
              "reference": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/Patient/eXoGxqgBaJuNkuahMYmiDhg3",
              "display": "Physician, Family Medicine"
            },
            "authoredOn": "2020-07-06T20:40:24Z",
            "recorder": {
              "reference": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/Practitioner/eqZb1t82mP4YeBjiLldSFPQ3",
              "display": "Family Medicine Physician"
            },
            "dosageInstruction": [
              {
                "extension": [
                  {
                    "valueQuantity": {
                      "value": 10,
                      "unit": "mg",
                      "system": "http://unitsofmeasure.org",
                      "code": "mg"
                    },
                    "url": "https://open.epic.com/fhir/extensions/ordered-dose"
                  }
                ],
                "timing": {
                  "repeat": {
                    "boundsPeriod": {
                      "start": "2020-07-06T23:00:00Z"
                    },
                    "frequency": 2,
                    "period": 1,
                    "periodUnit": "d"
                  },
                  "code": {
                    "text": "0900 & 1800"
                  }
                },
                "asNeededBoolean": false,
                "route": {
                  "coding": [
                    {
                      "system": "urn:oid:1.2.840.114350.1.13.861.1.7.4.798268.7025",
                      "code": "11",
                      "display": "Intravenous"
                    }
                  ],
                  "text": "Intravenous"
                }
              }
            ]
          },
          "search": {
            "mode": "match"
          }
        }
      ]
    }
  },
  "extension": {
    "com.epic.cdshooks.request.bpa-trigger-action": "18",
    "com.epic.cdshooks.request.cds-hooks-specification-version": "1.0",
    "com.epic.cdshooks.request.fhir-version": "R4",
    "com.epic.cdshooks.request.criteria-id": "2852",
    "com.epic.cdshooks.request.epic-version": "9.4",
    "com.epic.cdshooks.request.cds-hooks-implementation-version": "1.0",
    "com.epic.cdshooks.request.cds-hooks-su-version": "0",
    "internal.epic.cdshooks.request.epic-emp-id": "17805"
  }
}

CDS Hooks Response

Upon receiving a CDS Hooks request from Epic, a CDS Service should quickly and synchronously respond with a CDS Hooks response. Optionally, the CDS Service may query Epic's FHIR server to learn additional information before responding using the OAuth 2.0 access token provided in the fhirAuthorization attribute in the request.

Upon evaluating the CDS Hooks request (including the relevant information contained in the request, and any data retrieved) according to your app’s business logic, your application responds. This response determines whether content is displayed to end users and what that content should be. Possible types of content include one or more of:

  • textual information
  • suggested actions to be taken, such as adding diagnoses
  • links to your SMART app or other web pages

Suggestion or app launch?

Generally, launching and interacting with an app is more time-consuming and therefore more disruptive to a user's workflow than returning a suggestion. If your CDS recommendations can be fully determined with only the information provided in the CDS Hooks request and other APIs, you should return these suggestions in the CDS Hooks response.

If you are unable to determine recommendations with only the information from the CDS Hooks request and other APIs, a link to launch an app enables additional user interaction. The app launch link is returned in the CDS Hooks response. A card response is a one-time event. CDS Hooks suggestions can only be returned in a CDS Hooks response. An app launched from a CDS Hooks response can't use CDS Hooks APIs.

Generally, the user needs to manually click a link to launch your app. There's nothing forcing the user to do this, and therefore they may not.

Beginning in Aug 23, Epic supports an "auto-launch" feature, such that a CDS Service may inform the EHR in its CDS Hooks response that it's appropriate to auto-launch an app because, for example, there's no other CDS guidance in the card. In the case where the only content in a CDS Hooks' response is an app url, and there's no other cards or native CDS, Epic may auto-launch an app.

Actions

In the CDS Hooks response, a CDS Service can suggest suggestions, which include one or more actions. An action creates or deletes a FHIR resource. For example, a CDS Service can recommend that a problem be added to the current patient’s problem list by returning a FHIR Condition resource and a card.suggestion.action.type of “create”. The list of suggestions supported by Epic are listed in the API library (search for CDS Hooks).

If using the MedicationRequest, ServiceRequest, or ProcedureRequest resources to create an unsigned order in Epic, order overrides are NOT set with information from the service response, even if additional order details are specified. All order details come from the default values defined on the medication record, procedure record, or preference list. To use these CREATE resources to specify a preference list item, specify the system as "urn:com.epic.cdshooks.action.code.system.preference-list-item". The code, assigned while building the preference list, will be the corresponding key used to identifty a particular order on the preference list.

HTTP Response Fields

Field

Description

cards

An array of cards that provide any of the following:

  • Information
    • Text to be shown to the user, optionally formatted as GitHub-flavored markdown, or with html markup if also providing an Epic-specific extension defining the content type.
  • Suggestions
  • Work with the health system to map the problem list items, encounter diagnoses, medications, procedures and/or multi-order sets.

    • Containing CREATE actions for Condition resources, specifically Problems, or Encounter Diagnosis.
    • Medication single order follow-up suggestions with CREATE actions using MedicationRequest resources
    • Medication single order follow-up suggestions with DELETE actions using MedicationRequest resources
    • Procedure single order follow-up suggestions with CREATE actions using ProcedureRequest (STU3) or ServiceRequest (R4) resources
    • Procedure single order follow-up suggestions with DELETE actions using ProcedureRequest (STU3) or ServiceRequest (R4) resources
    • Order Set, SmartSet, Pathway or Express Lane multiple order follow-up suggestions with CREATE actions using ProcedureRequest (STU3) or ServiceRequest (R4) resources
  • Links
    • Reference links do not require any additional configuration
    • SMART App links need to be added to the allowlist in Epic

systemActions

Beginning in Feb 24, Epic supports system actions to annotate orders via the ServiceRequest.Update (Unsigned Order) (R4) API.

Card Attributes

Epic-specific nuances regarding Card attributes:

Field

Description

indicator

The info, warning, and critical values can be mapped to Epic-specific values by the Epic application team for display.

uuid

Unique identifier, used for auditing and logging suggestions. This field is optional. However, it is required if you intend to receive feedback.

selectionBehavior

Only the value "any" is currently supported.

detail

A CDS Service may return content as mere plain text, as GitHub flavored markdown, or, with an Epic-specific extension, as html. (See "com.epic.cdshooks.card.detail.content-type" extension, below).

source.topic.code

Epic only supports alpha-numeric strings as the code of source.topic, for example: "Card123" or "869e7c5587e04d0da96a60a84b5b8eac". The value returned in source.topic.code is used for logging and auditing, and is returned to the CDS Service in the feedback request. Maximum Length: 100 Characters

Source.topic.code should be a static identifier representing the particular topic of a card. When an end user overrides a given card, their acknowledgment is associated with this identifier. If you want the end user's override to be respected on subsequent requests to your CDS service, the topic identifier should remain static if sending the same card content.

links

Allows your service to suggest a link to a user for additional information or a SMART app. Allowed links are allow-listed by the health system.

Suggestion Attributes

Epic-specific nuances regarding Suggestions and Action attributes:

Field

Description

label

This field is not used, but is required.

uuid

Unique identifier, used for auditing and logging suggestions. This field is optional. However, it is required if you intend to receive feedback.

isRecommended

Boolean. When there are multiple suggestions, allows a service to indicate that a specific suggestion is recommended from all the available suggestions on the card. In Epic, this is used to control whether the suggested action is pre-selected or not.

actions

Epic only supports a single action per suggestion.

actions.type

For a given resource, confirm the action types supported in the API Library.

actions.description

This value is the primary content of the OPA. A CDS Service may return content as plain text.

actions.resource

The FHIR resource provided by the CDS Service representing the action to be suggested to the user. Check the API Library to see what resources (and action types) are supported.

Codesets for Create APIs

The following table calls out codesets that may be used when specifying a coding for a given Create action.

System

Description

Resource(s)

urn:com.epic.cdshooks.action.code.system.preference-list-item

Used to order a specific preference list item.

MedicationRequest.Create

ServiceRequest.Create

ProcedureRequest.Create

urn:com.epic.cdshooks.action.code.system.orderset-item

Used to order a specific SmartSet, OrderSet, or Pathway.

ServiceRequest.Create

ProcedureRequest.Create

urn:com.epic.cdshooks.action.code.system.cms-hcc

Used to suggest a Visit Diagnosis through a CMS-HCC model.

Condition.Create

urn:com.epic.cdshooks.action.code.system.hhs-hcc

Used to suggest a Visit Diagnosis through a HHS-HCC model.

Condition.Create

urn:oid:2.16.840.1.113883.6.90

Used to suggest ICD-10 codes.

Condition.Create

urn:oid:2.16.840.1.113883.6.69

Used to suggest NDC codes.

MedicationRequest.Create

urn:oid:2.16.840.1.113883.6.88

Used to suggest by RxNorm code.

MedicationRequest.Create

Link Attributes

Attribute

Description

label

This field is not used, but is required.

url

The URL to load.

type

This field is required, and may either be absolute or smart.

appContext

This field is optional, and will share information from the CDS card with the subsequently launched SMART app.

autolaunchable

Supported by Epic beginning in Aug 23, this will autolaunch the SMART app if only one OPA requests an autolaunch at a given time.

Supported Extensions

Extension

Description

com.epic.cdshooks.card.detail.content-type

If this is not set, or set to "text/markdown" or "text/markdown; variant=GitHub", Epic will treat it as GitHub Flavored Markdown
Other options are "text/plain", "text/html", and "text/markdown; variant=CommonMark".

Basic Example HTTP Response with Card

{
  "cards": [
    {
      "summary": "Testing Informational Card",
      "detail": "* Suspected colorectal cancer. \n\n* Please add to patient's diagnoses.\n\nSuspected Bladder cancer. Please add to patient's diagnoses.",
	  "indicator": "critical",
      "source": {
        "topic": {
          "code": "examplecode1"
        }
      },	  
    },]
}

Example HTTP Response for no decision support needed

{
  "cards": []
}

Example HTTP Response with Links, Override Reasons, and Suggestions

{  
   "cards":[  
	  {  
         "summary":"Example",
         "indicator":"info",
         "extension":{  
            "com.epic.cdshooks.card.detail.content-type":"text/html"
         },
         "detail":"Another card to test suggestions",
		 "source": {
			"label": "Clinical Inferences",
			"url": "https://www.example.com/",
			"icon": "file://example/CDSHooks/images/example.png",
			"topic": {
			  "code": "BCSCard2",
			  "system": "card-system",
			  "display": "BCS Card 2"
			}
		  },
		  "links": [
			{
			  "label": "Github",
			  "url": "https://github.com",
			  "type": "absolute"
			},
			{
			  "label": "R4 SMART Example App",
			  "url": "https://example.com/EpicSMARTApp/Default.aspx?appFhirVersion=R4",
			  "type": "smart",
			  "appContext": "%FNAME%-%EXTENSION;74901%-420fe522-193c-11eb-9552-460231621f93~!@#$%^&*()-+{}[]|\\"
			},
		  ],
		  "overrideReasons":[
			{
				"code":"patrefused",
				"system":"http://example.org/cds-services/fhir/CodeSystem/override-reasons",
				"display":"Patient refused"
			},
			{
				"code":"seecomment",
				"system":"http://example123.org/cds-services/fhir/CodeSystem/override-reasons",
			}
		 ],
           "suggestions":[
		   {  
               "label":"Arthritis",
			   "uuid": "cf72fe83-1eb9-410c-94aa-04ec98736388",
               "actions":[  
                  {  
                     "type":"create",
					 "description":"Arthritis",
                     "resource": {
						"resourceType": "Condition",
						"category": [
						  {
							"coding": [
							  {
								"system": "http://terminology.hl7.org/CodeSystem/condition-category",
								"code": "encounter-diagnosis",
								"display": "Encounter diagnosis"								
							  }
							],
							"text": "Encounter diagnosis"
						  }
						],
						"code": {
						  "coding": [
							{
							  "system": "urn:com.epic.cdshooks.action.code.system.cms-hcc",
							  "code": "40",
							  "display": "Arthritis"
							}
						  ],
						  "text": "Stomach ache"
						}
					  },
                  }
               ]
            },
			{  
               "label":"Stroke",
			   "uuid": "12035ae1-5d60-4f58-b922-882140b98283",
               "actions":[  
                  {  
                     "type":"create",
					 "description":"Stroke",
                     "resource": {
						"resourceType": "Condition",
						"category": [
						  {
							"coding": [
							  {
								"system": "http://terminology.hl7.org/CodeSystem/condition-category",
								"code": "encounter-diagnosis",
								"display": "Encounter diagnosis"								
							  }
							],
							"text": "Encounter diagnosis"
						  }
						],
						"code": {
						  "coding": [
							{
							  "system": "urn:com.epic.cdshooks.action.code.system.cms-hcc",
							  "code": "100",
							  "display": "Stroke"
							}
						  ],
						  "text": "Stroke"
						}
					  },
                  }
               ]
            },
			{  
               "label":"Stroke",
			   "uuid": "1a53ba14-a06b-4e82-bbb0-09ae01a75515",
               "actions":[  
                  {  
                     "type":"create",
                     "description":"Stroke prognosis",
                     "resource": {
						"resourceType": "Condition",
						"category": [
						  {
							"coding": [
							  {
								"system": "http://terminology.hl7.org/CodeSystem/condition-category",
								"code": "problem-list-item",
								"display": "Problem List"
							  }
							],
							"text": "Problem list"
						  }
						],
						"code": {
						  "coding": [
							{
							  "system": "urn:oid:2.16.840.1.113883.6.90",
							  "code": "R10.9"
							  
							},
							{
							  "system": "urn:oid:2.16.840.1.113883.6.96",
							  "code": "271681002"
							  
							}
						  ],
						  "text": "Stomach ache"
						}
					  },
                  }
               ]
            },
			{
			"label":"Diabetes",
			"uuid": "85126ce5-b0a7-4a54-86f4-d7b52426cc58",
            "actions":[  
               {  
                  "type":"create",
                  "description":"Diabetes Order Set from CDS Hooks",
                  "resource": {
					"resourceType": "ServiceRequest",
					"status": "draft",
					"intent": "proposal",
					"category": [
					  {
					  "coding": [
						{
						  "system": "http://terminology.hl7.org/CodeSystem/medicationrequest-category",
						  "code": "outpatient",
						  "display": "Outpatient"
						}]
					}],
					"code": {
					  "coding": [
					    {
						  "system": "urn:com.epic.cdshooks.action.code.system.orderset-item",
						  "code": "DIABETES"
						}
					  ]
					}
				  },
               },
            ]
         },
			 {  
				"label":"Test IP Medication Order",
				"uuid": "b1e0575b-2381-4625-b687-2def804d7c79",
				"actions":[  
				   {  
					  "type":"create",
					  "description":"Test Medication IP Order",
					  "resource": {
						"resourceType": "MedicationRequest",
						"status": "draft",
						"intent": "proposal",
						"category": [
						{
						  "coding": [
							{
							  "system": "http://terminology.hl7.org/CodeSystem/medicationrequest-category",
							  "code": "inpatient",
							  "display": "Inpatient"
							}
						  ],
						  "text": "Inpatient"
						}
						],
						"medicationCodeableConcept": {
						  "coding": [
							{
							  "system": "urn:oid:2.16.840.1.113883.6.69",
							  "code": "55111-682-01"
							}
						  ],
						  "text": "Test Med Display name"
						}
					  },
				   },
				]
			 },
			 {  
				"label":"Test Medication Order from Pref",
				"uuid": "5e0ce5e2-d33a-467c-bfc6-cde8d304e73d",
				"actions":[  
				   {  
					  "type":"create",
					  "description":"Test Medication Order from Pref",
					  "resource": {
						"resourceType": "MedicationRequest",
						"status": "draft",
						"intent": "proposal",
						"category": [ 
						{
						  "coding": [
							{
							  "system": "http://terminology.hl7.org/CodeSystem/medicationrequest-category",
							  "code": "inpatient",
							  "display": "Inpatient"
							}
						  ],
						  "text": "Inpatient"
						}
						],
						"medicationCodeableConcept": {
						  "coding": [
							{
							  "system": "urn:com.epic.cdshooks.action.code.system.preference-list-item",
							  "code": "BENAD25"
							}
						  ],
						  "text": "Test Proc Display name"
						}
					  },
				   },
				]
			 },
		 {  
            "label":"CBC",
			"uuid": "613b0192-4243-4384-8294-4316dfb726bb",
            "actions":[  
               {  
                  "type":"create",
                  "description":"CBC from CDS Hooks",
                  "resource": {
					"resourceType": "ServiceRequest",
					"status": "draft",
					"intent": "proposal",
					"category": [
					  {
					  "coding": [
						{
						  "system": "http://terminology.hl7.org/CodeSystem/medicationrequest-category",
						  "code": "outpatient",
						  "display": "Outpatient"
						}]
					}],
					"code": {
					  "coding": [
					    {
						  "system": "urn:com.epic.cdshooks.action.code.system.preference-list-item",
						  "code": "CBC_IP"
						}
					  ],
					  "text": "Test Proc Display name"
					}
				  },
               },
            ]}            
		]
      },
]
}

When sending override reasons in your response, configuration must be completed within the CDS Hooks criteria record to map overrideReasons.code to released acknowledgment reasons. overrideReasons.system does not have to be a specific string; it can be any non-empty string.

Trusting CDS Clients

JWT Tokens

To enable your CDS service to authenticate the identity of the CDS client, CDS hooks use digitally signed JSON web tokens (JWTs). Each time a client transmits a request to your service which requires authentication, the request must include an authorization header presenting the JWT as a “Bearer” token, which contains the following fields:

Field

Description

alg

The cryptographic string used to sign this JWT. The default is RSA SHA-384.

jku

The URL to the JWK Set containing the public key(s).

kid

The identifier of the key-pair used to sign this JWT.

typ

Fixed value: "JWT".

aud

Your CDS service’s endpoint. This must be manually configured by the health system.

exp

The expiration timer for the authentication JWT.

iat

The issue time of the JWT.

iss

The URI of the JWT issuer, the FHIR endpoint of the organization. This must be manually configured.

jti

A string value that uniquely identifies the JWT.

nbf

Typically set to 5 minutes prior to the issue time of the JWT.

sub

CDS hooks client ID. This must be manually configured.

Header
{
  "alg": "RS384",
  "jku": "{{Base URL}}/api/epic/2019/Security/Open/PublicKeys/530013/530013",
  "kid": "d3bmo5HzW61TUgikHZH+A8Tx4UOXz2iOs4KvVU4eLY0=",
  "typ": "JWT"
}
Payload
{
  "aud": "https://example.com/savecdshooksrequest",
  "exp": 1708124873,
  "iat": 1708123973,
  "iss": "{{Base URL}}/api/FHIR/R4",
  "jti": "a42df6b7-2074-479a-b216-50eee1b09fb6",
  "nbf": 1708123673,
  "sub": "12345678-ce08-4ac7-ac0c-12345678"
}

Install Considerations for the JWT Token

JWT authentication is configured in an External Endpoint Configuration record. The aud, iss, and sub claims are manually specified. These can be changed if your service requires a particular value.

  • alg – This is typically RSA SHA-384
  • aud – This is typically your CDS service’s endpoint
  • iss – This is the FHIR server’s base url of the CDS Client
  • sub – This is typically your CDS service’s client ID

To obtain the jku (JSON Web Key Set URL) from the installing organization, decode the JWT you receive in the “Authorization: Bearer” header, parse the jku claim from the JWT header, and verify the jku exists on your trusted allowlist of jku’s.

Make sure to not confuse the JWT you receive in the "Authorization: Bearer" header with the access token JWT you receive in the CDS Hooks request body. These two JWTs serve distinct purposes.

Feedback

When configuring your CDS Hooks service during implementation, you can choose to receive feedback immediately or to receive it in a batch. If you use the batch format, the interval at which you receive feedback is defined through configuration at the implementing organization. By default, feedback is sent to your CDS Hooks service endpoint with “/feedback” appended.

A UUID must be sent in the CDS Hooks service response to uniquely identify cards or suggestions. If a UUID is not sent for these fields, the CDS Hooks client does not trigger feedback because the feedback can't be associated with a particular card or suggestion the CDS Hooks service provided.

Acknowledgement reasons can be configured within Epic or sent from your CDS service. Each option has varying Epic configuration steps and a different feedback message. For acknowledgement reasons built in Epic, the health system will need to add the reasons to the CDS build and the details will be sent in the extension component of the feedback message. If you are dynamically sending acknowledgement reasons, build needs to be completed by the health system to map the value being sent to an Epic record. In this case, the feedback message will utilize the reason field. The two Overridden Cards below show examples of the reason component and the extension field being used.

Feedback Message Demonstrating Overridden Card with Reason

{"feedback": [{
            "card": "123456",
            "outcome": "overridden",
            "outcomeTimestamp": "2022-05-19T19:44:04Z",
            "overrideReasons": {
                "reason": {
                    "code": "contraindicated",
                    "display": "bad",
                    "system": "http: //example.org/cds-services/fhir/CodeSystem/override-reasons"
                },
                "userComment": "User Entered Free Text"}}]}

Feedback Message Demonstrating Overridden Card with Extension

{"feedback": [{
            "card": "123456",
            "outcome": "overridden",
            "outcomeTimestamp": "2022-05-19T19:44:04Z",
            "overrideReasons": {
                  "extension": {
                       "com.epic.cdshooks.feedback.overrideReason.reasonCategory": "45",
                       "com.epic.cdshooks.feedback.overrideReason.reasonDisplay": "Does not meet criteria",
                       "com.epic.cdshooks.feedback.overrideReason.reasonTitle": "Does not meet criteria"
                },
                "userComment": "User Entered Free Text"}}]}

Feedback Message Demonstrating Accepted Suggestion

{"feedback": [{
            "acceptedSuggestions": [{
                    "id": "replaceWithGUID"
                }
            ],
            "card": "123456",
            "outcome": "accepted",
            "outcomeTimestamp": "2022-05-19T19:48:34Z"}]}

Medication References

Beginning in the February 2024 version of Epic, medications referenced from MedicationRequest resources in the draftOrders context as part of the order-select and order-sign hooks will be included as a contained resource within the draftOrders context. This eliminates the need for additional roundtrips to the server to obtain medication codes.

Example Medication Reference

"context": {
    "patientId": "eXoGxqgBaJuNkuahMYmiDhg3",
    "encounterId": "eAJ9U5Zv9Vzeg4lBWxmAQcItP6nlidE0QacJVtVudEQ43",
    "userId": "PractitionerRole/e-QokEGUJIzyynNdkCFrs9w3",
    "draftOrders": {
      "resourceType": "Bundle",
      "type": "collection",
      "entry": [
        {
          "resource": {
            "resourceType": "MedicationRequest",
            "id": "ez067mnwOAKP5z1.YJwRAsd9gCwdOzQ8wSrsM04QFoz882hxQZKBulF4smVj2SxHWfesiTZ1qsgBez8Rdeb2GpbWYuU.L0KkOqiZSgl2GBPFYUucdOKx53adK51FHbIL.9fyjChlCongwxq0kmbsAb63ITW14qpRUnm2l2PXADxvuolXYdkN80RBgyQwLK2O33",
            "status": "draft",
            "intent": "order",
            "category": [
              {
                "coding": [
                  {
                    "system": "http://terminology.hl7.org/CodeSystem/medicationrequest-category",
                    "code": "inpatient",
                    "display": "Inpatient"
                  }
                ],
                "text": "Inpatient"
              }
            ],
            "priority": "routine",
            "medicationReference": {
              "reference": "Medication/eVBXvKwrWZIkPmaGwY.s1hQ3",
              "display": "DEXTROMETHORPHAN HBR 15 MG/5ML PO SYRP"
            },
            "subject": {
              "reference": "Patient/eXoGxqgBaJuNkuahMYmiDhg3",
              "display": "Patient, Test"
            }
          }
        },
        {
          "resource": {
            "resourceType": "MedicationRequest",
            "id": "ez067mnwOAKP5z1.YJwRAsfY.5YNxZJJDzDQrJbuKBDbIp.vKaSouyn36H4Tc6-z2y9h2OU5FqxVeqUHFJRpGe4BxmHoRsVJB9GLVkHUmLPX-LyD02h3sLRMgK9uFNRU73KWgj0Tmeqi8Y.vVgy-6vka7hpwQHl1zLGD2OaLJ9rJfzfKH89zl25piLYkeGQMz3",
            "status": "draft",
            "intent": "order",
            "category": [
              {
                "coding": [
                  {
                    "system": "http://terminology.hl7.org/CodeSystem/medicationrequest-category",
                    "code": "inpatient",
                    "display": "Inpatient"
                  }
                ],
                "text": "Inpatient"
              }
            ],
            "priority": "routine",
            "medicationReference": {
              "reference": "Medication/emvpHliA4OaUxXJ4wp6N.Ig3",
              "display": "MERCAPTOPURINE 50 MG PO TABS"
            },
            "subject": {
              "reference": "Patient/eXoGxqgBaJuNkuahMYmiDhg3",
              "display": "Patient, Test"
            }
          }
        },
        {
          "resource": {
            "resourceType": "Medication",
            "id": "eVBXvKwrWZIkPmaGwY.s1hQ3",
            "identifier": [
              {
                "use": "usual",
                "system": "urn:oid:1.2.840.114350.1.13.861.1.7.2.698288",
                "value": "2356"
              }
            ],
            "code": {
              "coding": [
                {
                  "system": "urn:oid:2.16.840.1.113883.6.253",
                  "code": "6379"
                },
                {
                  "system": "urn:oid:2.16.840.1.113883.6.68",
                  "code": "43102030501215"
                },
                {
                  "system": "http://www.nlm.nih.gov/research/umls/rxnorm",
                  "code": "3289"
                },
                {
                  "system": "http://www.nlm.nih.gov/research/umls/rxnorm",
                  "code": "102490"
                },
                {
                  "system": "http://www.nlm.nih.gov/research/umls/rxnorm",
                  "code": "1090496"
                },
                {
                  "system": "urn:oid:2.16.840.1.113883.6.162",
                  "code": "01592"
                }
              ],
              "text": "dextromethorphan syrup 15 mg/5mL"
            },
            "form": {
              "coding": [
                {
                  "system": "urn:oid:1.2.840.114350.1.13.861.1.7.4.698288.310",
                  "code": "SYRP",
                  "display": "Syrup"
                }
              ],
              "text": "Syrup"
            },
            "ingredient": [
              {
                "itemCodeableConcept": {
                  "coding": [
                    {
                      "system": "urn:oid:2.16.840.1.113883.6.253",
                      "code": "6379"
                    },
                    {
                      "system": "urn:oid:2.16.840.1.113883.6.68",
                      "code": "43102030501215"
                    },
                    {
                      "system": "http://www.nlm.nih.gov/research/umls/rxnorm",
                      "code": "3289"
                    },
                    {
                      "system": "http://www.nlm.nih.gov/research/umls/rxnorm",
                      "code": "102490"
                    },
                    {
                      "system": "http://www.nlm.nih.gov/research/umls/rxnorm",
                      "code": "1090496"
                    },
                    {
                      "system": "urn:oid:2.16.840.1.113883.6.162",
                      "code": "01592"
                    }
                  ],
                  "text": "dextromethorphan syrup 15 mg/5mL"
                },
                "strength": {
                  "numerator": {
                    "value": 15,
                    "unit": "MG/5ML"
                  },
                  "denominator": {
                    "value": 15,
                    "unit": "MG/5ML"
                  }
                }
              }
            ]
          }
        },
        {
          "resource": {
            "resourceType": "Medication",
            "id": "emvpHliA4OaUxXJ4wp6N.Ig3",
            "identifier": [
              {
                "use": "usual",
                "system": "urn:oid:1.2.840.114350.1.13.861.1.7.2.698288",
                "value": "10531"
              }
            ],
            "code": {
              "coding": [
                {
                  "system": "urn:oid:2.16.840.1.113883.6.253",
                  "code": "28533"
                },
                {
                  "system": "urn:oid:2.16.840.1.113883.6.68",
                  "code": "21300040000305"
                },
                {
                  "system": "urn:oid:2.16.840.1.113883.6.162",
                  "code": "00597"
                },
                {
                  "system": "http://www.nlm.nih.gov/research/umls/rxnorm",
                  "code": "103"
                },
                {
                  "system": "http://www.nlm.nih.gov/research/umls/rxnorm",
                  "code": "197931"
                }
              ],
              "text": "mercaptopurine (PURINETHOL) tablet 50 mg"
            },
            "form": {
              "coding": [
                {
                  "system": "urn:oid:1.2.840.114350.1.13.861.1.7.4.698288.310",
                  "code": "TABS",
                  "display": "Tablet"
                }
              ],
              "text": "Tablet"
            },
            "ingredient": [
              {
                "itemCodeableConcept": {
                  "coding": [
                    {
                      "system": "urn:oid:2.16.840.1.113883.6.253",
                      "code": "28533"
                    },
                    {
                      "system": "urn:oid:2.16.840.1.113883.6.68",
                      "code": "21300040000305"
                    },
                    {
                      "system": "urn:oid:2.16.840.1.113883.6.162",
                      "code": "00597"
                    },
                    {
                      "system": "http://www.nlm.nih.gov/research/umls/rxnorm",
                      "code": "103"
                    },
                    {
                      "system": "http://www.nlm.nih.gov/research/umls/rxnorm",
                      "code": "197931"
                    }
                  ],
                  "text": "mercaptopurine (PURINETHOL) tablet 50 mg"
                },
                "strength": {
                  "numerator": {
                    "value": 50,
                    "unit": "MG",
                    "system": "http://unitsofmeasure.org",
                    "code": "mg"
                  },
                  "denominator": {
                    "value": 50,
                    "unit": "MG",
                    "system": "http://unitsofmeasure.org",
                    "code": "mg"
                  }
                }
              }
            ]
          }
        }
      ]
    }
  }

Implementing CDS Hooks with an Epic Organization

When implementing your CDS service with an Epic organization, they will need certain pieces of information to complete their setup:

  • Your CDS Hooks Client ID
    • When registering your app, the website creates an app record in the Epic database and assigns your app production and non-production client IDs.
    • The steps to register and receive your client IDs can be found in the App Request Process.
  • Your CDS service’s endpoint
  • Your service’s prefetch configuration, if applicable
  • Expectations for JWT claims – aud, iss, and sub
  • What workflow steps you expect to fire the hook
    • During implementation, the organization’s analysts will configure this to trigger during appropriate workflows and will determine where in workflows the service should be invoked.

There are also pieces of information that you will need to receive from the organization:

  • The FHIR iss of the client
  • The jku (JSON Web Key Set URL)

For more information, refer to our Implementing a CDS Hooks App document.

Gotchas

Event Notification Streaming

CDS Hooks should be used only to trigger real-time, clinician-facing decision support. If your service never returns clinician-facing content, you should not use CDS Hooks.

If your use case requires notifications when an event occurs, you should use an event-based interface. For more information, refer to our interfaces document. If your use case requires context synchronization, you should use FHIRcast.

FHIR Version

When you register your CDS service with Epic’s authorization server infrastructure, the default FHIR version that you specify determines the FHIR version of resources in the CDS Hooks request, such as those in context or prefetch, as well as the specific fhirServer URL sent in the prefetch.

Required OAuth 2.0 Scopes

  • Context.draftOrders is only sent to a CDS service that’s authorized for the OAuth 2.0 scopes corresponding to the CDS Hooks-specific FHIR order resource (for example, MedicationRequest (Unsigned Order)).
  • Similarly, a CDS service must be authorized for scopes corresponding to any resources requested as part of prefetch.

Pre-Registration of Links and SMART Apps

As a security mechanism, SMART app link URLs returned within CDS Hooks cards must be pre-registered with the CDS client. SMART app URLs not pre-registered won't appear to the user.

Endpoint URI Validation

For CDS Hooks to function appropriately, the URL used for your CDS Service must be defined in and exactly match an Endpoint URI defined on your application/client ID. If your CDS Service URL is not defined on your application's client ID, the hook will still trigger but it will be stripped of the information necessary for you to provide CDS.

Troubleshooting

Web App and Web Service Troubleshooting

Important note on making changes to your application:

Whenever you make changes to your app on the website, they may take up to 1 hour to sync with the Sandbox, and 12 hours to sync at an Epic organization's Epic instance. Keep this in mind for changes you make to endpoint uris, public keys and selected APIs.

Contents

  • OAuth 2.0 Errors
  • API Call Errors
  • Hyperspace/MyChart Embedding Errors
  • Web Page Errors
  • OAuth 2.0 Errors

    There are two types of OAuth 2.0 apps, launched apps and backend apps. Backend apps interact with only Epic's /token endpoint, while launched apps interact with the /token AND /authorize endpoints. We've broken out the error messages below by endpoint:

    Authorize Endpoint

    "Something went wrong trying to authorize the client. Please try logging in again."

    • Check that that redirect URI in your request matches one of the redirect URIs registered on the app. This includes the following:
      • If you've edited your app's URIs, wait for the client sync before testing
      • URIs are case sensitive
      • URIs must be exact matches. For instance "/" will not match "/index.html", even though browsers treat them the same
    • Check that the client ID in the request matches the client ID of the app, and that the response_type is code.
    • If you have a backend app encountering this error code, make sure the URL you're hitting ends in "/token" instead of "/authorize".
    • Make sure you're including a proper "aud" parameter in the request.
    • If using a POST request, make sure your body is of Content-Type: application/x-www-form-urlencoded instead of application/json, and make sure your request data is in the body instead of the query string (vice versa for GET requests).

    "invalid_launch_token"

    • The client ID in the FDI must match the client ID of the app. Confirm the client ID in the app on Epic on FHIR make sure the client record exists in the Epic Community Member's Epic instance and that they have used that client ID in the integration. Make sure your app record and the authorization code in your request match exactly.
    • If you are redeeming a launch token at the wrong Epic instance (example: production vs non-production) this can occur.

    404 or 500 error

    • Request URL might be too long. Shorten your state parameter.

    I'm being asked to log in, but I'm launching from Epic/the Hyperspace Simulator

    • If you're launching from Epic (EHR launch) and still being asked to log in, have the app request the "launch" scope.

    Token Endpoint

    400 error:

    • "invalid_request" - Formatting of the HTTP Request can cause this issue. Try out the following:
      • Use the POST verb, not GET
      • Content-Type should be "application/x-www-form-urlencoded".
        • Do not send JSON, for example.
      • Content should be in the body of the request, not the querystring
        • There should be no "?" after "/token"
    • "invalid_client" - Your client authentication may be invalid. Try the following:
      • For apps using JWT authentication, refer to the linked JWT Auth Troubleshooting resource below.
      • If you're still having issues, try re-uploading your client authentication (client secret or public key) and waiting for the sync.

    400 error: "unauthorized_client"

    • If using a Backend OAuth 2.0 integration, there is required community member setup that will cause this error if not completed. This step is auto-completed when testing in the FHIR sandbox.

    API Call Errors

    This section assumes your app has obtained an access token and is having issues making API calls using it. If you have not received an access token yet, refer to the OAuth 2.0 errors section above.

    401 errors

    • 400 usually means you had the right security, but the data in your request was incorrect somehow.
    • Check that you're putting data in the right place (body vs querystring).
    • Look for error messages in the response body to assist. Here is a common one:
      • NO-PATIENT-FOUND - When searching using an ID such as MRN, the ID Type in Epic needs to have a descriptor. Make sure the descriptor (I IIT 600) is populated for the target ID Type, and distribute to the app for searching.
    • It's helpful if your app is able to log the response body, since Epic returns detailed error messages for most APIs.

    403 errors

    • For these errors, Epic identified who the client and user were, but denied access.
    • If the issue is with the client, we generally don't return a response body, just 403 Forbidden
      • In this case, check the API you are calling is attached to your client ID. Wait for the client to sync before testing any changes
      • For scope errors, the WWW-Authenticate HTTP Response header should say this:
        • Bearer error="insufficient_scope", error_description="The access token provided is valid, but is not authorized for this service"
    • If the issue is with the user, we generally return an error message describing why, such as:
      • "The authenticated user is not authorized to view the requested data" is common for FHIR calls. In this case, try the following:
        • Make sure you are handling access tokens correctly. If you mistakenly use one person's access token on another person's behalf, you'll see this error.
        • Organizations set up user security checks that also apply to access tokens. These security checks, if failed, produce this error.
        • If using a backend services app, these security checks are applied to the background user configured for your client ID. Otherwise, they are applied to the user who authenticated into your app.

    Hyperspace/MyChart Embedding Errors

    Many EHR Launched apps attempt to embed in Epic workflows, and rely on Epic build to do so. Below are common issues you might see when configuring one of these launches.

    Attempted to embed in Hyperspace, but the window popped out to a new browser:

    • Check the FDI record has the correct launch type.
    • There are 2 types of FDIs and 2 types of launch activities, they need to match to respect launch settings:
      • PACS FDIs - Need RIS_PACS_REDIRECTOR
      • Web FDIs - Need MR_CLINKB_* (Active Guidelines)

    Hyperspace launch displays a green/white spinning circle indefinitely (AGL spinner).

    • Confirm that all domains in the login workflow are added to the Epic Browser Allowlist.
    • Confirm that the workspace build points at the right FDI. Consider Active Guidelines debug mode to troubleshoot (shows URL navigated to).

    App launches externally in MyChart mobile when launchtype specifies embedded launch.

    • Confirm that all the app domains are allowlisted in the MyChart Mobile FDI record.

    Web Page Errors

    OAuth involves several web servers taking control of a browser in a sequence/handshake. The following errors could come from EITHER the Interconnect/Authorization server or the third-party app's server when they are trying to display HTML.

    This content cannot be displayed in a frame.

    • This could be hiding an OAuth 2.0 error, or an accidental login page.
      • Interconnect cannot be iframed, and is trying to show an error page or login page.
      • Will appear in trace logs, OR you can pop out the launch with FDI settings for "external window" workspace type.
      • Refer to these sections above for scenarios where Interconnect returns HTML:
        • "Something went wrong trying to authorize the client. Please try logging in again."
        • "I'm being asked to login, but I'm launching from Epic"
    • Otherwise, the third-party app is likely blocking the iframe.
      • The app needs to adjust the X-Frame-Options response header. The website can omit the "X-Frame-Options” header or set it to "ALLOW-FROM https://domain”. The domain should be that of the Hyperspace web server for the organization.

    Content was blocked because it was not signed by a valid security certificate.

    • Accompanied by pink background
    • Implies a web site's certificate is invalid
    • Check the third-party server and Interconnect server for expired or invalid certificates.

    This page can't be displayed

    • Error normally calls out the domain/server being requested
    • One cause is the browser being unable to resolve the website's IP address. Make sure the website's domain is publicly available, or at least available from the Hyperspace server for provider-facing embedded launches
    • If you can resolve the IP address, another cause is the server/website hanging up during the TLS handshake. Try using SSL Labs to diagnose why the server is hanging up. "Handshake Simulation" is especially useful since it calls out compatability with IE-11 and various windows versions

    Sex, Gender, & Names

    Sex, Gender, & Names

    Introduction

    For a given patient, Epic differentiates between several sex and gender properties, stores preferred and alternate names, and determines which to show to an end user based on the workflow. For example, patient interaction, clinical care, reimbursement, and patient identification and matching might use different sex, gender and names for the same person.

    As an app developer, you should carefully determine the appropriate sex, gender, and name properties to retrieve and use based on your users, workflows, and healthcare setting. Some scenarios are more straightforward than others. Possible scenarios include:

    • Patient interaction: Any time users interact with the patient (in person, through printed materials and handouts, letters, MyChart, or in other ways), the patient's gender identity should be respected when possible.
    • Legal: Administrative systems need to identify a patient by their legal name and legal sex to interface with external systems such as state registries or systems that print legal forms for signature. For most standards-based data exchange, Epic communicates the patient’s legal sex as the administrative sex.
    • Insurance: Insurance claims normally require the patient sex that is known to the payer (Medicare, Medicaid, other insurance). The best name and sex to use on an insurance claim might not be the best name to use when addressing the patient in person.
    • Clinical: Clinicians need to know how to treat a patient based on the patient's current biology. When deciding whether to administer a prostate exam, the relevant question is whether the patient has a prostate, which is a distinct question from what sex is listed on the patient's driver's license. Epic has a Sex for Clinical Use concept that can be used as a broad categorization of the patient’s likely physiological properties in cases where the exchange of discrete organ inventory or other phenotypical properties is not practical.

    Considerations for apps involving patient interaction

    If the app needs a patient gender value only to communicate with the patient and not for clinical use, then you should use gender identity. However, not all patients have a gender identity documented, and not all organizations collect it, so you should plan to fall back to the administrative (legal) gender, which is almost always available.

    Your app should support different values for gender identity (for patient communication) and administrative sex (for patient matching and as a fallback when gender identity isn’t documented).

    Organizations might use different values on patient wristbands, room name cards, specimen labels, and other identifiers. Expect to coordinate with each organization to align the data your app uses for patient identification with the organization’s specific policies.

    Considerations for administrative apps (legal, insurance)

    Does your app need a patient’s sex for formal documents or communication with government agencies? If so, you should likely use the patient’s Legal Sex, which is communicated as the administrative sex in most data exchange standards. For communicating with insurance, Coverage Sex tracks the sex the health plan has on file for the patient, and might be different than the patient’s legal sex. For example, most plans keep either F or M on file for nonbinary patients with a legal sex of X or NB, and using the Coverage Sex is necessary for submitting successful claims.

    Considerations for clinical apps

    If a system needs a single sex value for the purpose of clinical decision making, be that by an end user of that system or by an automated process such as a reference range, then you should use the derived Sex for Clinical Use value instead of other related fields.

    Epic calculates the Sex for Clinical Use value using a number of data points. If Sex for Clinical Use is unknown, individuals and systems should not assume sex by default.

    Note that based upon jurisdictional law, health systems policies, and patient preferences, a patient proxy authorizing an app to access FHIR APIs might not have access to a patient’s preferred name, gender identity, or sex for clinical use.

    Available sex, gender, and name data elements

    Element Name Definition Why it matters API HL7v2
    Preferred Name

    Name used by patient in day-to-day life

    Note that using preferred name is something that can be important for more than just transgender patients. For example, a dementia patient who goes by Tessie might not remember or respond to her legal name of Theresa. It can be important to providing a better experience at the healthcare organization if everyone sees her name as Tessie in the chart and in ancillary systems so that everyone can use that name to address her. Preferred name might also be referred to as a chosen name, nickname, name in use, or lived name.

    FHIR Patient resource (all versions): name.use = ‘usual’

    PID-5

    Legal Sex

    Legal sex of the patient

    Legal sex might be required for patient identification, matching, or integration with government registries.

    FHIR Patient resource (all versions): gender

    PID-8

    Coverage Sex

    Sex documented on the patient’s coverage

    Health plans track a patient’s demographics and compare against those submitted by a provider during administrative processes (for example, authorization or claims). Coverage Sex tracks the patient sex the health plan has on file, and can be different than the patient's legal sex.

    Not supported

    IN1-43 (subscriber sex)

    Gender Identity

    How the patient identifies their gender

    Helps to assist users in the other system to address the patient respectfully. Might be required for reporting and registries.

    FHIR Patient resource (STU3+): Patient.extension (gender-identity)

    ZPD-20 or OBX value

    Sex assigned at Birth

    Sex assessed at birth

    Helps to assist users in the other system with background information.

    FHIR Patient resource (STU3+): Patient.extension (birth sex)

    ZPD-22 or OBX value

    Menarche Flag

    Whether the patient can get pregnant

    Useful for systems that deal with pregnancy and delivery, most commonly OB systems. This is intentionally not a gender/sex value and should not be used to determine if a patient is female.

    Not supported

    ZPD-23

    Sex for Clinical Use

    Patient proxy sex for clinical decisions

    Sex for Clinical Use is useful for systems that make clinical decisions based on patient sex and can help prevent harmful assumptions about a patient's body and health.

    FHIR Patient resource (STU3+): Patient.extension (sex-for-clinical-use)

    ZPD-24

    Individual Pronouns

    Patient’s pronouns

    Referring to a patient using the wrong pronouns can lead to them feeling disrespected and excluded.

    FHIR Patient resource (R4+): individual-pronouns

    Patient Pronouns: ZPD-36

    Pronoun Comment: ZPD-37

    Sexual Orientation

    Sexual Orientation

    This information can be used for continuity of care during conversions or for reporting purposes.

    FHIR Patient resource (STU3+)

    ZPD-21

    Appendix

    Why related fields aren’t reliable for clinical decision making

    Here are some examples of why related sex and gender properties, such as sex assigned at birth, gender identity, and legal sex, aren’t reliable for clinical decision making:

    • Sex assigned at birth is often accurate for newborns without intersex conditions, but becomes less accurate as a person ages. A transgender patient might choose to undergo treatments to transition. In this case, the sex assigned at birth is not representative of the patient’s current physiological status, but their gender identity might be.
    • A patient might undergo hormone therapy, which could impact reference ranges for lab tests, but the patient might still have the organs they were born with.
    • A trans patient might not choose to undergo treatments to transition. In this case, sex assigned at birth might be representative of the patient’s current physiological status, and gender identity might not be.
    • Many jurisdictions allow citizens to update the sex marker on their government documents, including the sex on their birth certificate, so the legal sex or sex assigned at birth recorded in the system might not represent current physiological status, and the sex assigned at birth documented in the system might not represent the physiological status of the patient at birth.

    How Epic derives Sex for Clinical Use

    To derive a Sex for Clinical Use value, the sex assigned at birth (SAAB), gender identity (GI), legal sex, OB history, and organ inventory are used as “hints” to determine if the patient is not fully represented by a physiological category of male or female. If any of these values (GI, SAAB, Legal Sex) differ, or if they are inconsistent with documented organ inventory or OB history, then the patient might have a physiological status that is not fully known or is not fully represented by physiological categories based only on the data.

    What if Sex for Clinical Use is unknown?

    Current diagnostics, analytics, and treatment best practices might be undefined or not aligned with existing sex-derived reference populations.

    For these reasons, absent a Sex for Clinical Use, individuals and systems should not make assumptions about a patient’s physiological status for the purpose of providing care, but should rather:

    • Use default behavior that is safe for both male and female populations.
    • Individually review treatment options with the patient.
    • Carefully inspect relevant observations before proceeding with treatment.

    Vaccine Credentials

    Epic's support for vaccine credentials using the SMART Health Cards specification is evolving. The content on this page might change without notice.

    • What's a "Vaccine Credential?
      • Related Terms & Definitions
      • Audience and Intent
    • What's a SMART Health Card?
      • Accessing Epic-Generated SMART Health Cards
      • Reading SMART Health Cards
        • QR codes
          • Numerical Decoding
          • Chunked QR Codes
        • .smart-health-card Files
      • Consuming SMART Health Cards
        • JOSE Header
        • Payload
          • Inflation
          • SMART Health Card Header
          • FHIR Bundle
          • FHIR Patient
            • Considerations For Patient Identity
          • FHIR Immunization
            • Example Immunization
          • FHIR Observation
            • Example Observation
        • SMART Health Cards & Trust
          • Verify the Signed Web Token
    • Open Source Libraries & References

    What's a "Vaccine Credential"?

    A vaccine credential binds an individual’s demographic information with information about their lab tests or vaccine history into a package that is machine-readable and digitally signed by the issuer. Unlike a paper vaccine card, a vaccine credential is verifiable and therefore much more difficult to tamper with or counterfeit.

    A CDC vaccine card, widely issued in the United States to patients when they receive a COVID-19 vaccination.

    A CDC vaccine card, widely issued in the United States to patients when they receive a COVID-19 vaccination.

    Related Terms & Definitions

    • Vaccine or immunity passport. Passports are issued by governments. An immunity passport asserts immunity. In contrast, a vaccine credential is just an alternative way of representing information documented about a patient--specifically, their vaccination or lab history.
    • Health Wallet. An app available for install and use on a consumer's device that aggregates and stores vaccine credentials from different sources. A health wallet is a convenient way to store multiple credentials, but is not strictly required.
    • Pass App. An app available for install and use on a consumer's device that reads one or more vaccine credentials to determine if the user meets entry criteria for a specific venue or building, for example. might also perform some form of identity verification and might also commonly have health wallet-type functionality. A pass app might be useful in creating "fast lanes" in which a consumer's identity would not need to be re-verified, but a verifier should not require the patient to have a pass app.
    • Verifier. A consumer of a SMART Health Card.

    Audience and Intent

    This document was designed to support health wallets, pass apps, and verifiers, in designing apps and technologies that can consume a patient-provided SMART Health Card. We note particularly that any developer creating a pass app should also consider how verifiers using that pass app will interact with patients who do not have access to technology and present only with their ID and a printed SMART Health Card. Any verifier that requires patients to download a specific type of pass app (and thus, cannot read and interact with printed SMART Health Cards) is disadvantaging individuals without technology. SMART Health Cards were specifically designed to provide patient access regardless of technological ability or sophistication.

    In other words, if you require an individual to download and present a certain type of app to enter your venue, you have not implemented the specification correctly.

    What's a SMART Health Card?

    A SMART Health Card is a standard for representing a vaccine credential. The SMART Health Cards standard is open, interoperable, and built on top of HL7 FHIR and compatible with W3C's Verifiable Credentials. It's designed to be:

    • Open – The standard is open source.
    • Interoperable – It's built on top of FHIR.
    • Privacy-preserving – Clinical content is embedded within SMART Health Cards, ensuring that a patient can't be tracked based upon where they use their vaccine credential.
    • Equitable – It can be represented entirely on paper.

    A SMART Health Card contains very limited patient demographics, and either information about the patient's COVID-19 immunization or COVID-19 lab result.

    Technically, a SMART Health Card is a JSON object containing a JWS of compressed JSON, consisting of a FHIR Patient and either one or two FHIR Immunizations; or a FHIR Observation resource. The card can be represented as a file ending in the .smart-health-card extension, or as a QR code printed on paper or displayed digitally.

    You can learn more about SMART Health Cards by visiting the following websites:

    • https://vci.org/
    • https://smarthealth.cards/
    • http://build.fhir.org/ig/dvci/vaccine-credential-ig/branches/main/

    Accessing Epic-Generated SMART Health Cards

    Epic generates SMART Health Cards in two different ways and via a few different workflows:

    1. Patients can access SMART Health Cards as either a QR code displayed in or a file downloaded from the MyChart website or the MyChart mobile apps.
    2. Health systems using Epic can generate and print a QR code containing a patient's SMART Health Card in the clinician-facing interface, Hyperspace. Typically, this workflow would be used to provide SMART Health Cards to patients without access to MyChart.

    Patients can share QR codes either directly from a mobile device or from a piece of paper. You should test and validate that your app can consume the QR code from both a mobile device and from a piece of paper.

    A patient can download their SMART Health Card from MyChart onto their device. If you're developing an app that can run on a patient's device, alongside MyChart, you should associate your app with the .smart-health-card filename extension.

    Reading SMART Health Cards

    QR codes

    Every SMART Health Card can be represented as a QR code. Typical QR scanner apps expect the QR code to contain a URL that can be navigated to in a browser, but a SMART Health Card's QR code does not contain an http:// URL. Rather, the content begins with an shc:/ prefix, followed by a series of numbers and optionally forward slashes (/).

    For example, a SMART Health Card QR code could contain this example content after it is scanned:

    shc:/567629595326546034602925407728043360287028647167452228092864456652064567322455425662713655612967382737094440447142272560543945364126252454415527384355065405373633654575390303603122290952432060346029243740446057360106413733531270742335036958065345203372766522453157257438663242742309774531624034227003622673375938313137693259503100426236772203355605036403580035610300766508583058374270060532535258081003347207250832110567524430725820265707614406667441643124353569754355376170447732541065607642436956282976307276247374426657326827736143256004577460541121230968766729300406344232537345706367327527360526620626030777047434662721590753712525295755717157712404305365553050007570070626252762326973342068033331323970433259453532396071684011083877646011585542067525276750403569746574042500432127583065602700296834605068374126617637383032031153273532295552400564655669374044767208283743690777676126004361576025612868262976107611767042442744504570773124097729726134647000600635240740664475562634400569324229237641072230287177047474320721676355073560542058377743647371335212506320077300352940590840740926502974284434626571632106437507760408126227344173035234272224650859284424550330072738216709675476296859290842564163266200100030407726503740243343666063287100680677296841286766396657316871405227413263621127776161647443056825046903656367560042552754107309332612290435433150635250555600416734505731035241562857000866406707250066585029295368623020723771290300716970507307715829230409632369722208220022421235364507226273407623637673266125102552327255034322275824010800676966507066627420335210701037520576422230612371756123652765287762007444002131666470397727224500564164325437330542095250237703346428534576030771757133406430361224385420

    The numbers following the shc:/ prefix are the content of the SMART Health Card and must be numerically decoded.

    Numerical Decoding

    SMART Health Cards' QR codes represent characters as their ASCII code. The SMART Health Cards specification defines a simple algorithm to represent each character from the JWS as a two-digit number. Following the prefix, each pair of digits is converted into a single character by adding 45 to determine the encoded letter that the integer represents.

    For example, the first pair of digits in the numerically encoded SMART Health Card example above is 56. By adding our offset constant 45 to 56, the value translates to ASCII's lower-case e (Chr(101) = "e"). The constant 45 was chosen because 45 is the ASCII value for "-", which is the lowest character that can exist in our Base64-URL encoded content.

    Decoding the string proceeds as follows:

    56 + 45 = e

    76 + 45 = y

    29 + 45 = J

    Etc.

    When parsing is complete, the above shc:/ string resolves to the following JWS:

    eyJhbGciOiJFUzI1NiIsImtpZCI6ImZoa3ZpMEdWektQdjJpSHR6YUYtWHFicTZQVGFEcVdHSXd3c2RQNnZxT00iLCJ6aXAiOiJERUYifQ.3VRNb9swDP0rg3bZANuynCZLfFwSoMWwD6zZLkUOCs0kGvRhSLLRrMh_L-WkQzC0Pe20m0g-Pj0-yn5gKgRWs32Mbag570Ou4F5M82paYKugAGf4jY3owVmLEPPrxXdRjsYzMc7niyWXreIJyKuyEvwWofMqHvjXFi1fwic8BD6qypJK13OWMbvZslpMxHQ2Gk3G04z1wOoHBh4btFFJfdttftE1KbndK_-xs43GFHkMrvOAq0NLMTsXMhZPMTitqU85Szmi8gdW3xFHp_UPrwnw1F-XBHgKniH-JqOi_qRVGjyRSKM08bHPMJdaU2mnerRUYyu5IRXr4zpjG-XjfiFjIqGJy7y8ysWYHY_ZszLE6zJujOms-i3PE4UoYxeGOU2rMWJDyV4CKItz1wwM4Bpld4PicAgRzXmvtNa9_lA4v-PJUh5Uw6G_JwIYOkntlB3Xx4y159kHOVv0aOHCEn5hIYEd0K4HSBp6pcyJqhJ5WeVlGk-7-KUzG_RUENXoilIt-q3zJqVIpoTofLqtUaHVMlk8HzjjmwX2qF1r0nlpe-WdHc7v6NG9J1PXL_la_de-VpO_fL0aVeIf-5oUp4F-og_JJbqkKAuRtJ0-trs_v4tgJD16lDruC5C-CW9PQZ4CkvUyDlyvGjF7FaMud0XCHgE.5-pro_sokwANa7s7Ra2yWCKjDtxjDnHnIzk-wY-BLomsTzHCZ-eVmMcRN2W6a_Dz0OmIbZy04txtNUmKQ9EScA

    See the SMART Health Cards specification for an alternate explanation of numerical encoding.

    Chunked QR Codes

    In some cases, the underlying demographic and clinical data stored in a SMART Health Card QR code might simply be too large for a single QR code. If so, the SMART Health Card is "chunked" -- split across multiple QR codes that must be scanned and reassembled before any data can be accessed. In this case, the shc:/ prefix is appended with two additional values, delimited by forward slashes ("/"). The first value defines the order of the current QR code for reassembly and the second is the total number of QR codes required to reassemble the data.

    For example, a SMART Health Card of 3500 characters is split across three distinct QR codes. The prefix of the first QR code will be: shc:/1/3/, the second: shc:/2/3/ and the third: shc:/3/3/. The contents of all three QR codes (following the final forward slash) must be concatenated in order before inflation.

    Requiring multiple QR codes to receive a single SMART Health Card is undesirable and likely confusing to consumers. Epic will rarely place so much information in a single SMART Health Card such that chunking the data across multiple QR codes is necessary. However, apps consuming these QR codes must be prepared to scan and assemble multiple QR codes.

    .smart-health-card Files

    In addition to presenting a QR code to a scanner, a consumer can also download a file to share a SMART Health Card. In this case, there's no QR code, no numeric encoding, and no chunking. The SMART Health Card is represented as a JWS wrapped in a simple JSON array. The filename extension of this file will always be ".smart-health-card".

    Here's an example of a .smart-health-card file containing a single SMART Health Card:

    
    {
      "verifiableCredential": [
    "eyJhbGciOiJFUzI1NiIsImtpZCI6ImZoa3ZpMEdWektQdjJpSHR6YUYtWHFicTZQVGFEcVdHSXd3c2RQNnZxT00iLCJ6aXAiOiJERUYifQ.3VRNb9swDP0rg3bZANuynCZLfFwSoMWwD6zZLkUOCs0kGvRhSLLRrMh_L-WkQzC0Pe20m0g-Pj0-yn5gKgRWs32Mbag570Ou4F5M82paYKugAGf4jY3owVmLEPPrxXdRjsYzMc7niyWXreIJyKuyEvwWofMqHvjXFi1fwic8BD6qypJK13OWMbvZslpMxHQ2Gk3G04z1wOoHBh4btFFJfdttftE1KbndK_-xs43GFHkMrvOAq0NLMTsXMhZPMTitqU85Szmi8gdW3xFHp_UPrwnw1F-XBHgKniH-JqOi_qRVGjyRSKM08bHPMJdaU2mnerRUYyu5IRXr4zpjG-XjfiFjIqGJy7y8ysWYHY_ZszLE6zJujOms-i3PE4UoYxeGOU2rMWJDyV4CKItz1wwM4Bpld4PicAgRzXmvtNa9_lA4v-PJUh5Uw6G_JwIYOkntlB3Xx4y159kHOVv0aOHCEn5hIYEd0K4HSBp6pcyJqhJ5WeVlGk-7-KUzG_RUENXoilIt-q3zJqVIpoTofLqtUaHVMlk8HzjjmwX2qF1r0nlpe-WdHc7v6NG9J1PXL_la_de-VpO_fL0aVeIf-5oUp4F-og_JJbqkKAuRtJ0-trs_v4tgJD16lDruC5C-CW9PQZ4CkvUyDlyvGjF7FaMud0XCHgE.5-pro_sokwANa7s7Ra2yWCKjDtxjDnHnIzk-wY-BLomsTzHCZ-eVmMcRN2W6a_Dz0OmIbZy04txtNUmKQ9EScA"
      ]
    }
    

    The .smart-health-card files downloaded from MyChart web or mobile will contain one or two entries in the verifiableCredential JSON array. Each array entry is a self-contained SMART Health Card.

    Consuming SMART Health Cards

    Regardless of how a SMART Health Card is received -- a QR code scan or a file download, the same steps are used to verify and access the information contained within it.

    A JWS, including our SMART Health Card JWS, is made up of three, Base64-encoded parts:  JOSE header, a payload, and a signature. Per RFC 7515, these three parts are concatenated together with periods.

    JOSE Header

    The JOSE (Javascript Object Signing and Encryption) Header identifies the algorithms used to sign and compress the payload and a unique identifier of the public key to be used to verify the signature.

    For example, the JOSE header from the above JWS is:

    eyJhbGciOiJFUzI1NiIsImtpZCI6ImZoa3ZpMEdWektQdjJpSHR6YUYtWHFicTZQVGFEcVdHSXd3c2RQNnZxT00iLCJ6aXAiOiJERUYifQ

    Upon Base64-decoding it (and formatting for readability):

    {
    "alg": "ES256",
    "kid": "fhkvi0GVzKPv2iHtzaF-Xqbq6PTaDqWGIwwsdP6vqOM",
    "zip": "DEF"
    }
    

    • alg – the algorithm used to sign the JWS.
    • kid – a "key identifier" which is used to find the public key for validating the signature.
    • zip – the compression algorithm used to deflate the payload. Note that the JWS standard does not define the zip header; the SMART Health Card uses borrows it from the closely related JWT standard.

    In the SMART Health Card standard, the alg header will always be "ES256" and the zip header will always be "DEF". In Epic's implementation of the standard, only the kid header will change, as it uniquely identifies the public key used by the healthcare delivery organization that issued the SMART Health Card. The public key may be rotated periodically.

    Payload

    The payload of a SMART Health Card JWS (that is, the second of the three period-delimited pieces in the JWS) is base64-encoded, compressed JSON. The JSON is compressed with the DEFLATE algorithm.

    Inflation

    The DEFLATE algorithm is open source and has significant library support. DEFLATE is the same compression algorithm used by gzip but doesn't make use of the gzip header and footer. See RFC 1951 for more information about this compression algorithm.

    As an example, starting with this JWS payload from our example above,  

    3VRNb9swDP0rg3bZANuynCZLfFwSoMWwD6zZLkUOCs0kGvRhSLLRrMh_L-WkQzC0Pe20m0g-Pj0-yn5gKgRWs32Mbag570Ou4F5M82paYKugAGf4jY3owVmLEPPrxXdRjsYzMc7niyWXreIJyKuyEvwWofMqHvjXFi1fwic8BD6qypJK13OWMbvZslpMxHQ2Gk3G04z1wOoHBh4btFFJfdttftE1KbndK_-xs43GFHkMrvOAq0NLMTsXMhZPMTitqU85Szmi8gdW3xFHp_UPrwnw1F-XBHgKniH-JqOi_qRVGjyRSKM08bHPMJdaU2mnerRUYyu5IRXr4zpjG-XjfiFjIqGJy7y8ysWYHY_ZszLE6zJujOms-i3PE4UoYxeGOU2rMWJDyV4CKItz1wwM4Bpld4PicAgRzXmvtNa9_lA4v-PJUh5Uw6G_JwIYOkntlB3Xx4y159kHOVv0aOHCEn5hIYEd0K4HSBp6pcyJqhJ5WeVlGk-7-KUzG_RUENXoilIt-q3zJqVIpoTofLqtUaHVMlk8HzjjmwX2qF1r0nlpe-WdHc7v6NG9J1PXL_la_de-VpO_fL0aVeIf-5oUp4F-og_JJbqkKAuRtJ0-trs_v4tgJD16lDruC5C-CW9PQZ4CkvUyDlyvGjF7FaMud0XCHgE

    we Base64 URL-decode and inflate to arrive at the actual JSON content. The JSON of a SMART Health Card is made up of a header and a few FHIR resources.

    SMART Health Card Header

    A SMART Health Card's header elements identify if the card contains immunization or lab result information, a unique url for the organization that issued the card and the time at which the card was issued.

    • iss – Append /.well-known/jwks.json to this url to access the JSON Wek Key Set containing a public key for verification of the JWS signature. See SMART Health Cards & Trust for additional information.
    • nbf – The time at which the SMART Health Card was issued (per RFC 7519). Epic generates SMART Health Cards on-demand, so this timestamp will correspond to the time at which the card was accessed by the patient.
    • type – Identifies the SMART Health Card as containing either COVID-19 vaccination or lab result. Epic-generated SMART Health Cards will always be one of the following:
      • ["https://smarthealth.cards#health-card", "https://smarthealth.cards#covid19", "https://smarthealth.cards#immunization"]
      • ["https://smarthealth.cards#health-card", "https://smarthealth.cards#covid19", "https://smarthealth.cards#laboratory"]
    • credentialSubject – Contains a FHIR Bundle of a Patient resource, either one or two FHIR Immunization resources, or an Observation resource representing a lab result.

    This is an example of the SMART Health Card header describing an immunization card, issued on April 20, 2021.

    {
    	"iss": "https://vs-icx18-28.epic.com/Interconnect-HDR1035915-CDE/api/epic/2021/Security/Open/EcKeys/32001/SHC",
    	"nbf": 1618933658,
    	"type": ["https://smarthealth.cards#health-card", "https://smarthealth.cards#covid19", "https://smarthealth.cards#immunization"],
    	"vc": {
      "credentialSubject": {
          //payload removed for readability
    		}
    	}
    }
    

    FHIR Bundle

    The credentialSubject element from the SMART Health Card header defines the version of FHIR and contains a Bundle of FHIR resources.

    • fhirVersion – Version of FHIR resources contained in this SMART Health Card. Epic generated SMART Cards will always use FHIR R4 resources, represented in this field as “4.0.1”.
    • fhirBundle – A Bundle of type collection containing two to three FHIR resources. Each entry in the Bundle is a distinct FHIR resource.

    Here's an example of a credentialSubject containing a fhirBundle:

    {
    	"credentialSubject": {
    		"fhirVersion": "4.0.1",
    		"fhirBundle": {
    			"resourceType": "Bundle",
    			"type": "collection",
    			"entry": [
            { //FHIR resource },
    				{ //FHIR resource #2 },
    				{ //another FHIR resource! },
    			}
    		}
    	}
    

    FHIR Patient

    SMART Health Cards always contain a FHIR Patient resource. Unlike a typical Patient resource, the only information in the resource is the patient's given and family name, and date of birth.

    When consuming a SMART Health Card, the demographics in this Patient resource are verified against the user's identity.

    {
    	"resource": {
    		"resourceType": "Patient",
    		"name": [{
    			"family": "McCall",
    			"given": ["Table"]
    		}],
    		"birthDate": "2000-04-15"
    	}
    }
    

    Considerations For Patient Identity

    Importantly, possession of a SMART Health Card does not prove a patient's identity. In fact, patient caregivers, for example: parents, may access and download the SMART Health Cards of their wards. Pass apps and verifiers must verify identity.

    When a patient shares a SMART Health Card with a verifier, the verifier is expected to validate the patient's identity independently. For example, if a patient presents their driver's license along with their SMART Health Card, the verifier should confirm that the demographics within the SMART Health Card (specifically, the name and date of birth), match those on the driver's license.

    FHIR Immunization

    A SMART Health Card documenting a vaccination contains one or two FHIR Immunization resources. The below example describes a  Pfizer immunizations completed on February 1.

    • status – contains the value “completed”
    • vaccineCode – contains a CVX code identifying the given vaccine.
    • occurrenceDateTime – the date on which the vaccine was administered
    • lotNumber – the product lot number, further identifying the vaccine
    • performer – contains the name of the organization at which the vaccine was administered. Will not be present if administering organization is unknown.
    Example Immunization
    {
    	"resource": {
    		"resourceType": "Immunization",
    		"status": "completed",
    		"vaccineCode": {
    			"coding": [{
    				"system": "http://hl7.org/fhir/sid/cvx",
    				"code": "208"
    			}]
    		},
    		"patient": {
    			"reference": "Patient/resource:0"
    		},
    		"occurrenceDateTime": "2021-02-01",
    		"lotNumber": "1234",
    		"performer": [{
    			"actor": {
    				"display": "Current Development Environment (CDE)"
    			}
    		}]
    	}
    } 
    

    FHIR Observation

    A SMART Health Card documenting a lab result contains one FHIR Observation resource. The below example describes a negative lab test resulted on November 23, 202.

    • status – contains the value "final"
    • code – contains a LOINC code identifying the lab test, if known
    • effectiveDateTime – the time at which the lab test was resulted
    • performer – contains the name of the organization at which the test was performed.
    • value – the result of a COVID-19 lab test may be represented as a SNOMED code, a quantity, or a string.
    Example Observation
    {
     	"resource": {
     		"resourceType": "Observation",
     		"status": "final",
     		"code": {
     			"coding": [{
     				"system": "http://loinc.org",
     				"code": "882-1"
     			}]
     		},
     		"subject": {
     			"reference": "Patient/resource:0"
     		},
     		"effectiveDateTime": "2020-11-23T06:00:00Z",
     		"performer": [{
     			"display": "Health system"
     		}],
     		"valueString": "Negative"
     	}
     }
    

    SMART Health Cards & Trust

    A SMART Health Card is verifiable because its signed payload can be verified against a trusted public key. Each issuer publishes a JSON Web Key Set (JWKS) containing at least one public key. Typically, each healthcare organization that uses Epic acts as a distinct issuer.

    A verifier must determine which issuers to trust. Issuers participating in the Vaccine Credential Initiative have shared their publicly accessible JSON Web Key Set URLs in this directory:

    • https://raw.githubusercontent.com/the-commons-project/vci-directory/main/vci-issuers.json

    Verifiers should consider trusting and caching all of the public keys provided in the key sets from this directory.

    Verify the Signed Web Token

    An issuer's key set is discoverable during verification by appending "/.well-known/jwks.json" to a SMART Health Card's iss value. For example, iss value in the above SMART Health Cards is an inaccessiable, url internal to Epic: https://vs-icx18-28.epic.com/Interconnect-HDR1035915-CDE/api/epic/2021/Security/Open/EcKeys/32001/SHC

    HTTP Get https://vs-icx18-28.epic.com/Interconnect-HDR1035915-CDE/api/epic/2021/Security/Open/EcKeys/32001/SHC/.well-known/jwks.json
    
    {
      "keys": [{
        "x": "gfoeoAb2l-hqEHXyZIBqa1OObZ7Tunx5hlYRXtnPJwg",
        "y": "HcBgiLJ53BFqOp5lxiMUz5Jsik9DtM286pyWv2hVKNA",
        "kid": "fhkvi0GVzKPv2iHtzaF-Xqbq6PTaDqWGIwwsdP6vqOM",
        "use": "sig",
        "kty": "EC",
        "alg": "ES256",
        "crv": "P-256"
      }]
    }
    

    The key identifier from the JWKS's kid element corresponds to kid element in the header of the SMART Health Card's JWS. Epic issued SMART Health Cards are signed using ECDSA P-256 SHA-256.

    Verifiers should use standard security libraries to verify SMART Health Card signatures. Refer to Appendix A.3 of RFC 7515 to understand how Epic’s signature is calculated and formatted.

    Open Source Libraries & References

    While it is not an exhaustive list, nor maintained or affiliated with Epic, the following resources might be helpful:

    • Reference implementation: https://c19.cards/venue and open source code: https://github.com/smart-on-fhir/health-cards-tests
    • Microsoft-developed SMART Health Card validation SDK: https://github.com/microsoft/health-cards-validation-SDK
    • MITRE-developed SMART Health Cards tutorial Jupyter Notebook: https://github.com/dvci/health-cards-walkthrough/blob/main/SMART%20Health%20Cards.ipynb

    International Patient Summary Specification

    Epic is among the first organizations to support the International Patient Summary (IPS) FHIR specification, which is being released in May 2025. IPS improves care for patients by making it simpler for healthcare organizations to exchange data securely with partner organizations or across international borders. Support for IPS extends Epic's decades-long track record as a leader in universal interoperability.

    IPS builds on Epic's existing Care Everywhere and CDA workflows to help customers coordinate care with groups that exclusively use FHIR. The new specification governs the creation of electronic health record FHIR documents containing essential healthcare information about a patient. It is endorsed by multinational organizations such as the G7 and the GDHP.

    The initial release includes all required IPS data types – problems, allergies, and medications – as well as immunizations. We will add results, procedures, and other optional data types over time. Organizations that use Epic can generate IPS documents for treatment-based exchange between providers and make those documents available for patients.

    We look forward to helping Epic organizations connect with the global community and encourage you to support the new specification. If you believe you have a good use case for implementing the Epic IPS, reach out to interop@epic.com to get started.

    We use cookies to improve our website. By accepting, you will receive these cookies from Epic on FHIR. Decline if you wish to use Epic on FHIR without these cookies. Read our privacy policy here.

    Frequently Asked Questions (FAQs)
    Contact
    Terms of Use
    Privacy Policy
    1979 Milky Way, Verona, WI 53593
    © 1979-2025 Epic Systems Corporation. All rights reserved. Protected by U.S. patents. For details visit www.epic.com/patents.
    HL7®, FHIR® and the FLAME mark are the registered trademarks of HL7 and are used with the permission of HL7. The use of these trademarks does not constitute a product endorsement by HL7.
    Epic, open.epic, and Epic on FHIR are trademarks or registered trademarks of Epic Systems Corporation.

    Due to inactivity you will be logged out in 60 seconds.

    Saving
    Loading
    False