*
*
*
*
*
Loading...
That document wasn't found. It may have moved or require you to log in to view.
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.
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.
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.
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.
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.
2024-07-02
Epic will be among the first organizations to support the International Patient Summary (IPS) FHIR specification when it is released in May 2025. IPS will improve care for patients by making it simpler for healthcare organizations to exchange data securely 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 will include all required data types – problems, allergies, and medications – as well as immunizations. We will add results, procedures, and other optional data types over time. Epic customers will be able to generate IPS documents for treatment-based exchange between providers and make documents available for patients and their providers to download.
We look forward to helping customers connect with the global community and encourage you to support the new specification. Please join us at an upcoming FHIR Connectathon or reach out to interop@epic.com to get involved.
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.
Epic recommends apps be well-behaved, adhering to the following principles:
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.
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.
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.
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.
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)
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.
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
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."↩
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:
Log in to see step-by-step instructions for using the website to complete these workflows.
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:
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.
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.
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.
Epic community members should do the following prior to the request process:
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.
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.
To be able to download a client record, the following steps must have been completed:
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.
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.
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.
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:
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:
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:
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.
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.
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.
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.
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.
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).
This should be the logo for your organization or product. The recommended image dimensions are 500 x 300.
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.
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.
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:
Epic community members should do the following prior to the request process:
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.
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.
To be able to download a client record, the following steps must have been completed:
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.
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.
Epic defines three different user contexts for an application:
*The "Clinicians, Staff, or Administrative Users" context is commonly referred to as "Provider" context, so the rest of this tutorial uses that term.
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 |
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).
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:
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.
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:
Below is a list of common use cases for backend applications:
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.
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.
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.
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.
An API can support a combination of three possible user types, and when an API supports a given user type, it means the following:
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.
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.
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.
Connection Hub is open to any application or integration by attesting to having a live connection to an Epic system. A Connection Hub listing offers you the ability to share information about your product.
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.
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.
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 |
Bedside Dining | Bedside Dining applications present patients dining menus and options to place dining orders within MyChart Bedside. |
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. |
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. |
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. |
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. |
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. |
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. |
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 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 |
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. |
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. |
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 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.
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.
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.
Epic APIs process in real-time, so every time you make an API call, the call needs to be processed on a customer's Epic operational database. This is what end users interact with, so your API processing competes with end user workflows for processing power. Be mindful that high API volumes from your app could have a negative outcome for a customer's users. If having frequent data pulls is important to your app, it's critical to set expectations during a project kick-off so a customer is well-aware of how high API volumes could impact their system.
When you’re designing your app, consider the questions below.
Your app should be stable and predictable and should not negatively impact operations for users or Epic community members. Follow the best practices below to develop an application that runs efficiently.
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.
Epic offers many technologies that can be used to exchange data and integrate products. One of the best tools in your toolbox 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.
For clinical decision support apps, also 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.
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 might or might not have changed—you can receive updates as they are made to the patient chart.
If your app does require a large amount 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. If possible, schedule your API calls outside of the customer’s peak hours. Think about whether you could decrease the frequency of API calls while still getting the data you need.
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 encounters, you can include the associated encounter locations in the same response, removing the need to perform subsequent Location.Read API calls.
If your app uses FHIR Search APIs, 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.
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.
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. Consider how frequently you want to update each resource in your app’s database to minimize unnecessary API calls.
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.
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.
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:
client_id
: The client_id
identifies your application to authentication servers within the Epic community and allows you to connect to any organization.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_uri
s. redirect_uri
is not needed for backend services using the client_credentials grant type.
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_uri
s, 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.
Standalone launch: The app launches directly to the authorize endpoint outside of an EHR session and requests context from the EHR's authorization server.
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.
Desktop integrations through Subspace: The app requests access to APIs directly available on the EHR's desktop application via a local HTTP server.
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/
Contents
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.
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.
iss
: This parameter contains the EHR's FHIR endpoint URL, which an app can use to find the EHR's authorization server.
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"
]
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
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 should 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.
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: 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.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
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.
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
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 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.
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.
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.
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:
|
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" }
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.
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
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.
Contents
The app launches directly to the authorize endpoint outside of an EHR session and requests context from the EHR's authorization server.
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
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.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
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 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.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. 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. 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.
There are two authentication options available for confidential apps that can keep authentication credentials secret:
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.access_token
: This parameter contains the access token issued by Epic to your application and is used in future requests.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. 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. 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 ecounter 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.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.
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.
access_token
: This parameter contains the access token issued by Epic to your application and is used in future requests.
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. 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. 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.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.
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
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:
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"
}
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"
}
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.
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.
To register the dynamic client, your application needs to:
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"
}
]
}
}
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.
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
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.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"
}
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.
This is the same as the Get an authorization code step from Step 1 except:
client_id
should be that of your dynamic clientclient_assertion
– set to a JWT signed with your dynamic client’s private keyclient_assertion_type
– set to urn:ietf:params:oauth:client-assertion-type:jwt-bearer
code
or refresh_token
) described belowNote that the user will once again need to log in.
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.
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.
Contents
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.
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:
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.
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.
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.
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.
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"
}
Contents
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:
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.
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:
New-SelfSignedCertificate
PowerShell command above. The store used is displayed in the printout after the command completes.
-Subject
during key creation (e.g. "MyApp" above).
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:
Get-ChildItem
PowerShell command. For example, run Get-ChildItem -Path Cert:\LocalMachine\My
to find all certificate thumbprints in the local machine storage.
$ 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.
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. |
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
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" } ] }
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.
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.
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:
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:
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.
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:
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.
Apps can provide multiple public keys to an Epic community member by providing a JWK Set URL that contains multiple keys.
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).
Example use cases:
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 |
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.
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 |
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.
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.
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.
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.
To enable indefinite access for non-patient-facing apps, check that your customers have properly configured their external client records.
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.
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:
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.
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:
User type of Backend Systems
Uses OAuth 2.0
Functionality: Subspace
Registers Dynamic Clients
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:
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:
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.
(Not recommended) Epic Community Members can extend the default duration of your access tokens to any value, for example 4 hours.
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:
Created a new client ID with the following settings:
User type of Backend Systems
Uses OAuth 2.0
Feature: Incoming API
Select your required Web Services
Conduct Backend OAuth 2.0 as you did with your existing app, but skip the DCR step
Follow our security best practices for Backend applications, which solve the user-authentication issues above
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:
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.
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==
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.
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.
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.
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
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 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.
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.
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:
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:
{ "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:
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. 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. 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;
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.
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:
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.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.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.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.entry[n].resource.reaction
element describes the allergic reactions of the patient documented in the system. entry[n].resource.onsetDateTime
element tells you when the allergy first started happening.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.
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.
In this section we'll cover a few advanced topics that may come up in your implementation of FHIR.
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.
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.
Epic supports all of the following MIME-types for FHIR interactions:
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.
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.
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:
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.
The app’s FHIR version impacts multiple different behaviors during a SMART on FHIR launch, including:
iss
elementThe 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
.
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:
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:
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.
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.
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.
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.
For a CDS Hooks integration, the app's FHIR version controls the following behaviors:
Prefetch is supported only in apps with a FHIR version of STU3 and above. Prefetch is not supported for DSTU2.
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.
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.
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:
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.
Apps default to the unconstrained FHIR ID generation scheme. This means apps may use and may receive FHIR IDs longer than 64 characters.
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.
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:
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):
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:
|
.<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.
|
.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.
Epic uses this term differently than HL7. The distinction is important.
The following example in the HL7v2 message format demonstrates how HL7 identifiers are used:
<ID>^^^<HL7 Assigning Authority>^<HL7 Identifier Type>
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>
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.
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.
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.
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 |
|
Derrick Lin |
FHIR: eq081-VQEgP8drUUqCWzHfw3 External: Z6127 MRN: 203711 MyChart Login Username: fhirderrick MyChart Login Password: epicepic1 |
|
Desiree Powell |
FHIR: eAB3mDIBBcyUKviyzrxsnAw3 External: Z6130 MRN: 203714 MyChart Login Username: fhirdesiree MyChart Login Password: epicepic1 |
|
Elijah Davis |
FHIR: egqBHVfQlt4Bw3XGXoxVxHg3 External: Z6125 MRN: 203709 |
|
Linda Ross |
FHIR: eIXesllypH3M9tAA5WdJftQ3 External: Z6128 MRN: 203712 |
|
Olivia Roberts |
FHIR: eh2xYHuzl9nkSFVvV3osUHg3 External: Z6131 MRN: 203715 |
|
Warren McGinnis |
FHIR: e0w0LEDCYtfckT6N.CkJKCw3 External: Z6126 MRN: 203710 |
|
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:
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 |
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:
pseudoephedrine
instead of PSEUDOPHEDRINE HCL 30 MG PO TABS
.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.
Client IDs for USCDI apps will automatically download to a community member’s Epic instances when all of the following conditions are met:
Client IDs for CCDS apps will automatically download to a community member’s Epic instances when all of the following conditions are met:
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:
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:
For more information see Epic Nexus’s Approach to Individual Access Services in TEFCA via FHIR.
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.
† The following FHIR APIs qualify for USCDI v1 auto-distribution.
In addition to the USCDI v1 FHIR APIs above, the following FHIR APIs qualify for USCDI v3 auto-distribution.
†† Only the following DSTU2 FHIR APIs qualify for MU3 auto-distribution.
Contents
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.
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 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 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.
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 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 |
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.
GET https://hostname/instance/api/FHIR/STU3/Patient?_id=eYg3-1aJmCMq-umIIq2Njxw3&_include=Patient:generalPractitioner:Practitioner
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.
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.
GET https://apporchard.epic.com/interconnect-aocurprd-username/api/FHIR/STU3/Observation?patient=e63wRTbPfr1p8UW81d8Seiw3&category=vital-signs&_count=10
{ "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}
GET https://apporchard.epic.com/interconnect-aocurprd-oauth/api/FHIR/R4/Observation?patient=e63wRTbPfr1p8UW81d8Seiw3&category=vital-signs&_count=10
{ "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}
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:
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.
GET https://apporchard.epic.com/interconnect-aocurprd-username/api/FHIR/DSTU2/Observation?patient=e63wRTbPfr1p8UW81d8Seiw3&category=vital-signs&_count=10
{ "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}
{ "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}
{ "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}
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)]
_include=Immunization:actor:Practitioner
_include=Immunization:actor
would also work here)_include=Patient:generalPractitioner:Practitioner
_include=Appointment:actor:Patient
_include=Appointment:actor:Practitioner
_include=Appointment:actor:Location
_include=Observation:performer:Practitioner&_include=Observation:subject:Patient
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.
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. |
https://www.example.org/api/FHIR/R4/DocumentReference?_id=eiuHBlNpiGxJwXRUWVqHjN9QaKO0ODrkAJrvUByRLuMc3&_revInclude=Provenance:target
{ "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"} }
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 |
https://www.example.org/api/FHIR/R4/MedicationRequest?_id=eiuHBlNpiGxJwXRUWVqHjN9QaKO0ODrkAJrvUByRLuMc3&_revInclude=AuditEvent:entity
{ "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"} }
Contents
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.
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.
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.
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.
_type=Patient,Condition
)Epic has several recommendations that can help you have the best experience with bulk data APIs.
_type
parameter whenever possible to improve response time and minimize storage requirements.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.
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.
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.
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)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
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
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:
Note that the following are not supported by _typeFilter:
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:
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" } ] }
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.
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. |
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.
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).
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.
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.
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.
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.
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 |
---|---|
patient | Patient/{{context.patientId}} |
medications | MedicationRequest?patient={{context.patientId}}&status=active |
encounterDx | Condition?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/
Field |
Description |
hook |
Epic supports the following hooks:
|
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. |
prefetch |
Resources sent in the prefetch are configured by the health system and can include FHIR resources that are already supported by Epic. |
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:
|
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. |
{ "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" } }
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:
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.
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.
Field |
Description |
cards |
An array of cards that provide any of the following:
Work with the health system to map the problem list items, encounter diagnoses, medications, procedures and/or multi-order sets. |
systemActions |
Beginning in Feb 24, Epic supports system actions to annotate orders via the ServiceRequest.Update (Unsigned Order) (R4) API. |
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. |
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. |
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 |
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. |
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 |
{ "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" } }, },] }
{ "cards": [] }
{ "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.
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" }
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.
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.
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"}]}
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.
"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" } } } ] } } ] } }
When implementing your CDS service with an Epic organization, they will need certain pieces of information to complete their setup:
There are also pieces of information that you will need to receive from the organization:
For more information, refer to our Implementing a CDS Hooks App document.
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.
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.
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.
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.
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
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:
"Something went wrong trying to authorize the client. Please try logging in again."
"invalid_launch_token"
404 or 500 error
I'm being asked to log in, but I'm launching from Epic/the Hyperspace Simulator
400 error:
400 error: "unauthorized_client"
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
403 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:
Hyperspace launch displays a green/white spinning circle indefinitely (AGL spinner).
App launches externally in MyChart mobile when launchtype specifies embedded launch.
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.
Content was blocked because it was not signed by a valid security certificate.
This page can't be displayed
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:
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.
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.
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.
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 |
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:
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.
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:
Epic's support for vaccine credentials using the SMART Health Cards specification is evolving. The content on this page might change without notice.
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.
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.
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:
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:
Epic generates SMART Health Cards in two different ways and via a few different workflows:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
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
}
}
}
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! },
}
}
}
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"
}
}
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.
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 administeredlotNumber
– the product lot number, further identifying the vaccineperformer
– contains the name of the organization at which the vaccine was administered. Will not be present if administering organization is unknown.{ "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)" } }] } }
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 knowneffectiveDateTime
– the time at which the lab test was resultedperformer
– 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.{ "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" } }
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:
Verifiers should consider trusting and caching all of the public keys provided in the key sets from this directory.
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.
While it is not an exhaustive list, nor maintained or affiliated with Epic, the following resources might be helpful:
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.
Due to inactivity you will be logged out in 60 seconds.