Internet Explorer is no longer supported. To avoid issues, switch to Chrome, Edge, or Firefox.
  • FHIR
  • API Specifications
  • Build Apps
  • Documentation 
    • App Developer Guidelines
    • App Creation & Request Process Overview
    • Requesting A Connection Hub Listing For Your App
    • OAuth 2.0 Tutorial
    • FHIR Tutorial
    • Sandbox Test Data
    • Patient-Facing Apps Using FHIR
    • App Default FHIR Version
    • Search Parameters
    • Vaccine Credentials
    • FHIR Bulk Data Access Tutorial
    • ID Types for FHIR APIs
    • Troubleshooting
    • CDS Hooks Tutorial
    • User Context
  • Connection Hub
  • Login 
    Log in with
    Epic UserWebEpic UserWeb
    or

SMART on FHIR (OAuth 2.0)

*

*

*

*

*

* Indicates required field

Loading...

SMART on FHIR Launch URL

Document not found

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

open.epic Terms of Use

open.epic Terms of Use

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

Last updated September 9, 2022

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

Introduction

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

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

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

For Epic Community Members

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

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

For Developers

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

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

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

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

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

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

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

For Everyone

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

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

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

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

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

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

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

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

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

Definitions

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

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

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

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

App Developer Guidelines

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

1 Guidelines for Well-Behaved Apps

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

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

1.1 Safety Guidelines

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

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

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

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

1.2 Security Guidelines

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

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

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

The preferred mechanism for apps to authenticate with Epic is OAuth 2.0. Use of other authentication mechanisms, such as HTTP Basic Authentication, may be appropriate in certain circumstances.

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

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

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

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

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

1.3 Privacy and Data Use Guidelines

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

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

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

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

1.4 Reliability and Scalability Guidelines

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

1.5 Data and System Integrity Guidelines

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

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

2 Intellectual Property

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

3 Transparency and Honesty

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

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


Footnotes

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

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

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

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

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

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

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

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

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

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

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

App Creation & Request Process Overview

Table of Contents

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

Overview

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

The steps below walk through the process of:

The steps below describe the process of:

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

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

Creating, Activating, and Licensing an App

Registering an App / Provisioning Client IDs

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

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

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

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

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

Facilitating Epic Community Member Downloads

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

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

Provisioning Client Secrets

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

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

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

Requesting An Epic on FHIR App

Before Starting the Request Process

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

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

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

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

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

Obtaining the open.epic API Subscription Agreement

Community members who wish to use the FHIR APIs with a third-party application registered on the Epic on FHIR website must sign the open.epic API Subscription Agreement.

Community members do not need this license in order for their patients to use MU3 apps that used open.epic to develop, test, and register their products.

Downloading Client Records

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

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

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

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

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

Confirm the Client ID was Downloaded

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

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

Requesting A Connection Hub Listing For Your App

Follow the process below to list an app in Connection Hub.

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.

Create an App Record

If you need more information on creating your app, see our App Creation and Request Process document. Remember to fill out the Summary and Thumbnail as these fields will be publicly visible. If your organization already has a distributable app record in the website, skip straight to the Request Connection Hub Listing section.

Request Connection Hub Listing

  1. After your product has a live, active connection to a customer’s Epic system, find and click on your app to enter your app’s App Information page.
  2. Scroll to the bottom of your app's App Information page and click the Request Connection Hub Listing button in the bottom right corner of the App Information page.
  3. Review and accept the Connection Hub Terms of Use.
  4. Within 15 days, your organization's point of contact should hear back about payment for the listing fee before finalizing your listing.

OAuth 2.0 Tutorial

Using OAuth 2.0

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

Why OAuth 2.0?

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

Before You Get Started

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

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

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

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

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

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

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

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

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

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

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

  • See the Subspace Communication Framework specification for more details.

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


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

The base URL for the Current Sandbox environment is:

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

EHR Launch (SMART on FHIR)

Contents

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

How It Works

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

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

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

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

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

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

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

Metadata Example

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

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

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

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

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

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

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

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

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

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

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

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

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

Retrieving Conformance Statement: Additional Header Requirements

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

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

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

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

Step 3: Your Application Requests an Authorization Code

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

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

In either case, the application 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 November 2022 version of Epic, this parameter will be required. The value to use is the FHIR base URL of the resource server the application intends to access, which is typically the FHIR server returned by the iss.
  • state: This optional parameter is generated by your app and is opaque to the EHR. The EHR's authorization server will append it to each subsequent exchange in the workflow for you to validate session integrity. While not required, this parameter is recommended to be included and validated with each exchange in order to increase security. For more information see RFC 6819 Section 3.6.
    • Note: Large state values in combination with launch tokens that are JWTs (see above) may cause the query string for the HTTP GET request to the authorization endpoint to exceed the Epic community member web server's max query string length and cause the request to fail. You can mitigate this risk by:
      • Following the SMART App Launch Framework's recommendation that the state parameter be an unpredictable value ... with at least 122 bits of entropy (e.g., a properly configured random uuid is suitable), and not store any actual application state in the state parameter.
      • Using a POST request. If you use this approach, note that you might still need to account for a large state value in the HTTP GET redirect back to your own server.

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

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

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

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

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

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

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

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

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

Step 4: EHR's Authorization Server Reviews the Request

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

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

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

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

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

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

Access Token Request: If You Are Not Using a Client Secret

The following parameters are required in the POST body:

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

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

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

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

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

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

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

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

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

Access Token Request: If You Are Using a Client Secret

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: Access Token Request for Refresh Token Apps for details on how to obtain an access token if your app uses refresh tokens.

OpenID Connect id_tokens

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

A decoded OpenID Connect id_token JWT will have these headers:

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

A decoded OpenID Connect id_token JWT will have this payload:

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

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

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

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

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

Validating the OpenID Connect JSON Web Token

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

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

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

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

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

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

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

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

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

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

Standalone Launch

Contents

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

How It Works

Step 1: Your Application Requests an Authorization Code

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

  • response_type: This parameter must contain the value "code".
  • client_id: This parameter contains your web application's client ID issued by Epic.
  • redirect_uri: This parameter contains your application's redirect URI. After the request completes on the Epic server, this URI will be called as a callback. The value of this parameter needs to be URL encoded. This URI must also be registered with the EHR's authorization server by adding it to your app listing.
  • state: This optional parameter is generated by your app and is opaque to the EHR. The EHR's authorization server will append it to each subsequent exchange in the workflow for you to validate session integrity. While not required, this parameter is recommended to be included and validated with each exchange in order to increase security. For more information see RFC 6819 Section 3.6.
  • scope: This parameter describes the information for which the web application is requesting access. Starting with the November 2019 version of Epic, the "openid" and "fhirUser" OpenID Connect scopes are supported.
  • 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 November 2022 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]

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

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

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

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

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

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

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

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

Access Token Request: If You Are Not Using a Client Secret

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.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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"
}

Offline Access for Native and Browser-Based Applications

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

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

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

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

Step 2: Register the dynamic client

To register the dynamic client, your application needs to:

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

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

This request must have two elements:

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

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


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

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

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


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

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

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

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

Generate a JSON Web Token

Your app follows the guidelines below to create a JWT assertion using it's new private key

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

Here’s an example of the JWT body:


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

And here’s the final encoded JWT:

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

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

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

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

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

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

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


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

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

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

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

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


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

Step 3b: Alternate flow with refresh tokens

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

Get an authorization code

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

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

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

Exchange the authorization code for an access token

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

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

Use the refresh token to get subsequent access tokens

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

SMART Backend Services (Backend OAuth 2.0)

Contents

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

Overview

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

Building a Backend OAuth 2.0 App

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

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

You can register your application for access to both the sandbox and Epic organizations here. You'll provide information to us, including a public key used to verify the JWT signature, and Epic will generate a client ID for you. Your app will need to have the Backend Systems radio button selected in order to register a public key for backend OAuth 2.0.

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

An Epic community member's 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.

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

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

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

Step 1: Creating the JWT

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

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

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

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

Here is an example request:

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

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

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

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

JSON Web Tokens (JWTs)

Contents

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

Background

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

Creating a Public Private Key Pair for JWT Signature

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

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

Creating a Public Private Key Pair: OpenSSL

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

openssl genrsa -out /path_to_key/privatekey.pem 2048

Make sure the key length is at least 2048 bits.

Then 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.

Creating a Public Private Key Pair: Windows PowerShell

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

New-SelfSignedCertificate -Subject "MyApp"

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

The subject name does not have a functional impact in this case but it is required for creating an X.509 certificate.

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

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

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

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

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

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

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

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

Export the binary certificate to a base64 encoded certificate:

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

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

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

Finding the Public Key Certificate Fingerprint (Also Called Thumbprint)

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

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

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

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

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

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

Creating a JWT

The JWT should have these headers:

Header Description
alg The JWT authentication algorithm. Epic supports the following RSA signing algorithms for all confidential clients: RS256, RS384, and RS512. For clients that register a JSON Web Key Set URL, Epic also supports the following Elliptic Curve algorithms: ES256, ES384. Epic does not support manual upload of Elliptic Curve public keys, and we recommend new apps use a JWK Set URL and Elliptic Curve keys.
typ 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:

eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkNDUwNDljMy0zNDQxLTQwZWYtYWI0ZC1iOWNkODZhMTcyMjUiLCJzdWIiOiJkNDUwNDljMy0zNDQxLTQwZWYtYWI0ZC1iOWNkODZhMTcyMjUiLCJhdWQiOiJodHRwczovL2ZoaXIuZXBpYy5jb20vaW50ZXJjb25uZWN0LWZoaXItb2F1dGgvb2F1dGgyL3Rva2VuIiwianRpIjoiZjllYWFmYmEtMmU0OS0xMWVhLTg4ODAtNWNlMGM1YWVlNjc5IiwiZXhwIjoxNTgzNTI0NDAyLCJuYmYiOjE1ODM1MjQxMDIsImlhdCI6MTU4MzUyNDEwMn0.dztrzHo9RRwNRaB32QxYLaa9CcIMoOePRCbpqsRKgyJmBOGb9acnEZARaCzRDGQrXccAQ9-syuxz5QRMHda0S3VbqM2KX0VRh9GfqG3WJBizp11Lzvc2qiUPr9i9CqjtqiwAbkME40tioIJMC6DKvxxjuS-St5pZbSHR-kjn3ex2iwUJgPbCfv8cJmt19dHctixFR6OG-YB6lFXXpNP8XnL7g85yLOYoQcwofN0k8qK8h4uh8axTPC21fv21mCt50gx59XgKsszysZnMDt8OG_G4gjk_8JnGHwOVkJhqj5oeg_GdmBhQ4UPuxt3YvCOTW9S2vMikNUnxrhdVvn2GVg

JSON Web Key Sets

Applications using JSON Web Token (JWT) authentication can provide their public keys to Epic as a JSON Web Key (JWK) Set. Epic accepts keys of type RSA or EC, and we recommend including the kid values in your keys and in the headers of your assertions.

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

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

Key Identifier Requirements

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

Hosting a JWK Set URL

Starting in the May 2021 version of Epic, apps using JWT authentication can 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.

Note that you will still need to provide static public keys to community members on Epic versions earlier than the May 2021 version.

App-Level vs Licensing Specific JWK Set URLs

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

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

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

Registering a JWK Set URL

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

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

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

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

Key Rotation

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

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

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

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

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

Providing Multiple Public Keys

How

Apps can provide multiple public keys to an Epic community member by:

  • Providing multiple static public keys
  • Providing a JWK Set URL that contains multiple public keys

Why Not?

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

Use Cases

Example use cases:

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

Appendix

Contents

  • Epic-Issued OAuth 2.0 Tokens

Epic-Issued OAuth 2.0 Tokens

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

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

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

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

Trusting an OAuth 2.0 Token

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

Limit Use of Localhost URIs

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

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

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

Considerations for Native Apps

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

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

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

Subspace Security Considerations

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

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

  1. User type of Backend Systems

  2. Uses OAuth 2.0

  3. Functionality: Subspace

  4. Registers Dynamic Clients

  5. Functionality: Incoming Web Services

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

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

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

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

SMART App Launch

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

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

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

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

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

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

Multi-Context Application

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

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

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

    1. User type of Backend Systems

    2. Uses OAuth 2.0

    3. Feature: Incoming API

    4. Select your required Web Services

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

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

Non-OAuth 2.0 Authentication

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

HTTP Basic Authentication

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

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

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

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

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

Storing HTTP Basic Authentication Values

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

Client Certificates and SAML tokens

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

Additional Required Headers

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

Epic-Client-ID

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

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

Epic-User-ID and Epic-User-IDType

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

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

FHIR Tutorial

FHIR is all about quickly getting data about a patient from a clinical system, and FHIR is all interconnected — you can build on top of your queries to get a wide variety of data. The topics below include 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 walk through a couple of 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's important to note FHIR APIs support OAuth 2.0 and additionally HTTP Basic Authentication.

Interacting with the API

To get started, let's define the FHIR server base URL. This is a constant address that all FHIR API endpoints for the server's default FHIR version live under. 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/DSTU2"
    

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

Finding Patients via Demographic Search

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

  1. The Epic application can provide a patient to you, determined from the context or its patient selection activity. This method can apply to both clinician and patient-facing workflows
  2. Our Patient API provides the means to search for patients using demographic information such as their first and last name. We use this method when your application is independent from Epic.
    
var patientSearchString = "/Patient?given=Derrick&family=Lin&birthdate=1973-06-03"
    

We can use jQuery's $.getJSON() function to call into the URL and retrieve our data in JSON format. It takes a parameter describing the URL to query, 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:

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

The complete request URL at this point will read:

    
"https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/DSTU2/Patient?family=Lin&given=Derrick&birthdate=1973-06-03"
    

If you execute the above request, you'll be able to see the following response in your console:

{
  "resourceType": "Bundle",
  "type": "searchset",
  "total": 1,
  "link": [
    {
      "relation": "self",
      "url": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/DSTU2/Patient?family=Lin&given=Derrick&birthdate=1973-06-03"
    }
  ],
  "entry": [
    {
      "link": [
        {
          "relation": "self",
          "url": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/DSTU2/Patient/TGsj9rYGI8SV-LYYkdH1JqbxYuFGfrODXSPC6iLLYkJcB"
        }
      ],
      "fullUrl": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/DSTU2/Patient/TGsj9rYGI8SV-LYYkdH1JqbxYuFGfrODXSPC6iLLYkJcB",
      "resource": {
        "resourceType": "Patient",
        "id": "TGsj9rYGI8SV-LYYkdH1JqbxYuFGfrODXSPC6iLLYkJcB",
        "extension": [
          {
            "url": "http://hl7.org/fhir/StructureDefinition/us-core-race",
            "valueCodeableConcept": {
              "coding": [
                {
                  "system": "urn:oid:2.16.840.1.113883.5.104",
                  "code": "2028-9",
                  "display": "Asian"
                }
              ],
              "text": "Asian"
            }
          },
          {
            "url": "http://hl7.org/fhir/StructureDefinition/us-core-ethnicity",
            "valueCodeableConcept": {
              "coding": [
                {
                  "system": "urn:oid:2.16.840.1.113883.5.50",
                  "code": "UNK",
                  "display": "Unknown"
                }
              ],
              "text": "Unknown"
            }
          },
          {
            "url": "http://hl7.org/fhir/StructureDefinition/us-core-birth-sex",
            "valueCodeableConcept": {
              "coding": [
                {
                  "system": "http://hl7.org/fhir/v3/AdministrativeGender",
                  "code": "M",
                  "display": "Male"
                }
              ],
              "text": "Male"
            }
          }
        ],
        "identifier": [
          {
            "use": "usual",
            "system": "urn:oid:1.2.840.114350.1.13.0.1.7.5.737384.0",
            "value": "E4005"
          },
          {
            "use": "usual",
            "system": "urn:oid:1.2.840.114350.1.13.0.1.7.5.737384.14",
            "value": "203711"
          },
          {
            "extension": [
              {
                "url": "http://hl7.org/fhir/StructureDefinition/rendered-value",
                "valueString": "xxx-xx-0000"
              }
            ],
            "use": "usual",
            "system": "urn:oid:2.16.840.1.113883.4.1"
          }
        ],
        "active": true,
        "name": [
          {
            "use": "usual",
            "text": "Derrick Lin",
            "family": [
              "Lin"
            ],
            "given": [
              "Derrick"
            ]
          }
        ],
        "telecom": [
          {
            "system": "phone",
            "value": "785-555-5555",
            "use": "home"
          },
          {
            "system": "phone",
            "value": "785-666-6666",
            "use": "work"
          }
        ],
        "gender": "male",
        "birthDate": "1973-06-03",
        "deceasedBoolean": false,
        "address": [
          {
            "use": "home",
            "line": [
              "7324 Roosevelt Ave"
            ],
            "city": "INDIANAPOLIS",
            "state": "IN",
            "postalCode": "46201",
            "country": "US",
            "period": {
              "start": "2019-05-24T00:00:00Z"
            }
          }
        ],
        "maritalStatus": {
          "text": "Married"
        },
        "communication": [
          {
            "language": {
              "coding": [
                {
                  "system": "urn:oid:2.16.840.1.113883.6.99",
                  "code": "en",
                  "display": "English"
                }
              ],
              "text": "English"
            },
            "preferred": true
          }
        ]
      },
      "search": {
        "mode": "match"
      }
    }
  ]
}

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

  1. The total element tells us how many elements were found in the search:

    • a total of 0 elements tells us that no results were found in the search. This may mean that you didn't provide enough demographic data to make a match, or that the API doesn't know about the patient you're looking for.
    • a total of 1 element tells us that exactly one patient was found. A single patient returned in a search likely means that you've found the right person, but you should always check the demographic information to confirm.
    • a total of more than 1 element tells us that the demographic information wasn't sufficient to match to a single patient. If this happens, you can examine the response to match against known demographic data, or perform a second query against the API with additional demographic information.

    Ideally, you provide enough patient information in your initial search for the API to return a single patient — it's much safer to confirm a single patient than to compare a list of possible results, and it reduces the complexity on your app's users to select a single patient rather than review a list.

    In our case, we found exactly one patient, Derrick — exactly the patient we were looking for. 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 patient match is not found. Note the text contained within the OperationOutcome resource:

    {
      "resourceType": "Bundle",
      "type": "searchset",
      "total": 0,
      "link": [
        {
          "relation": "self",
          "url": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/DSTU2/Patient?given=Derrick"
        }
      ],
      "entry": [
        {
          "resource": {
            "resourceType": "OperationOutcome",
            "issue": [
              {
                "severity": "warning",
                "code": "informational",
                "details": {
                  "coding": [
                    {
                      "system": "urn:oid:1.2.840.114350.1.13.0.1.7.2.657369",
                      "code": "4101",
                      "display": "Resource request returns no results."
                    }
                  ],
                  "text": "Resource request returns no results."
                }
              }
            ]
          },
          "search": {
            "mode": "outcome"
          }
        }
      ]
    }
    
    

  2. The entry[n].link[n].url element points to the permanent location of a unique patient. If we make a request to this URL, we will always get back that patient's data. Storing this URL can be useful if you want to save off a list of recently used patients, for example. The last part of the url in our search example is Derrick's FHIR ID. We'll use this to make all of our other queries to FHIR APIs.

  3. The entry[0].resource element contains all of the actual data about Derrick Lin. Within this object, you'll find information about Derrick such as his address, phone number, and birth date.

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

    
var patientId = data.entry[0].link[0].url.split("/").pop();
    

Interlude: Relative Resource URLs (R4 and Later)

There are several versions of the FHIR standard. You might use DSTU2 FHIR resources (like in the example above), STU3 resources, or more recent versions of FHIR resources, such as R4 resources. A variety of R4 resources are supported starting in the August 2019 version of Epic.

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

"careProvider": [
  {
    "display": "Physician Family Medicine, MD",
    "reference": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/DSTU2/Practitioner/Thh97FZ9lU9.p-Rgpozo6vwB"
  }
]

An R4 resource would return a relative URL instead:

"careProvider": [
  {
    "display": "Physician Family Medicine, MD",
    "reference": "Practitioner/Thh97FZ9lU9.p-Rgpozo6vwB"
  }
]

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

Searching for Clinical Data by Patient ID

So 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 diagnoses. The Condition endpoint exposes exactly that, and provides a search endpoint to find diagnoses by Patient ID. We can construct a query that provides Derrick's FHIR ID to grab all of the active diagnoses recorded in our system:

    
var conditionSearchString = "/Condition?patient=" + patientId;
    

We'll use the same method as we did with the Patient search to investigate the response returned from the Condition API, and log the response body to the console as a simple means to view the data.

    
$.getJSON(baseUrl + conditionSearchString, 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/DSTU2/Condition?patient=TGsj9rYGI8SV-LYYkdH1JqbxYuFGfrODXSPC6iLLYkJcB&category=diagnosis"
    }
  ],
  "entry": [
    {
      "link": [
        {
          "relation": "self",
          "url": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/DSTU2/Condition/TH12jkQYrabXcEh-2vXMxLAB"
        }
      ],
      "fullUrl": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/DSTU2/Condition/TH12jkQYrabXcEh-2vXMxLAB",
      "resource": {
        "resourceType": "Condition",
        "id": "TH12jkQYrabXcEh-2vXMxLAB",
        "patient": {
          "display": "Derrick Lin",
          "reference": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/DSTU2/Patient/TGsj9rYGI8SV-LYYkdH1JqbxYuFGfrODXSPC6iLLYkJcB"
        },
        "asserter": {
          "display": "Physician Family Medicine, MD",
          "reference": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/DSTU2/Practitioner/Thh97FZ9lU9.p-Rgpozo6vwB"
        },
        "dateRecorded": "2019-05-28",
        "code": {
          "coding": [
            {
              "system": "http://hl7.org/fhir/sid/icd-9-cm/diagnosis",
              "code": "V49.89",
              "display": "Risk for coronary artery disease between 10% and 20% in next 10 years"
            },
            {
              "system": "urn:oid:2.16.840.1.113883.6.90",
              "code": "Z91.89",
              "display": "Risk for coronary artery disease between 10% and 20% in next 10 years"
            },
            {
              "system": "http://snomed.info/sct",
              "code": "315016007",
              "display": "At risk of coronary heart disease (finding)"
            }
          ],
          "text": "Risk for coronary artery disease between 10% and 20% in next 10 years"
        },
        "category": {
          "coding": [
            {
              "system": "http://loinc.org",
              "code": "29308-4",
              "display": "Diagnosis"
            },
            {
              "system": "http://snomed.info/sct",
              "code": "439401001",
              "display": "Diagnosis"
            },
            {
              "system": "http://hl7.org/fhir/condition-category",
              "code": "diagnosis",
              "display": "Diagnosis"
            },
            {
              "system": "http://argonautwiki.hl7.org/extension-codes",
              "code": "problem",
              "display": "Problem"
            }
          ],
          "text": "Diagnosis"
        },
        "clinicalStatus": "active",
        "verificationStatus": "confirmed",
        "onsetDateTime": "2019-05-28"
      },
      "search": {
        "mode": "match"
      }
    }
  ]
}

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

  1. As with Patient, the total element lets you know the number of diagnoses returned for a query. In this case, it lets us know how many diagnoses our system has documented for Derrick Lin.

  2. The entry[n].link[n].url element points to the permanent location of a unique Condition resource. If we make a request to this URL, we will always get back data for this specific diagnosis for the patient. The last part of the url in our search example is the FHIR ID for this Condition. The FHIR ID for this resource can also be retrieved directly from entry[n].resource[n].id. This might be useful for future reference to check whether the patient has any new conditions documented.

    • For an R4 query, the entry[n].link[n].url element value in this example would be shortened to Condition/TH12jkQYrabXcEh-2vXMxLAB. All other resources included in this response, such as the Patient and Practitioner resources, would also use relative URLs. Refer back to the Interlude: Relative Resource URLs (R4 and Later) topic for a summary of this difference.
  3. The entry[n].resource[n].patient element describes the patient the diagnosis relates to. This allows us to confirm the association between diagnosis and patient, and shows how FHIR works by linking these smaller resources to paint a larger picture. In this case, the element in each diagnosis entry points to our patient, Derrick.

  4. The entry[n].resource[n].asserter element describes the clinician who recorded the diagnosis in the system. A link to the provider resource is included to allow you to search additional information about the provider who recorded this diagnosis.

  5. The onsetDateTime tells you when the condition was first noticed.

  6. The recordedDate element tells you when the diagnosis was actually made and recorded.

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

Wrap Up

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


Advanced Topics

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

Searching from Patient Context in Coordination with OAuth 2.0

Starting in the May 2022 version of Epic, or February 2022 and November 2021 by special update, for patient-facing applications using OAuth 2.0 authorization, including the patient or subject search parameter(s) is optional when making requests for the patient on whose behalf authorization to the system was granted. This is true for STU3 and R4 versions of FHIR resources.

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

Content negotiation in FHIR

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

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

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

Using STU3 Resources in a DSTU2 Environment

You might come across a FHIR API that you want to use that is only available in STU3, but the base URL for the environment returns a DSTU2 endpoint. This means that you will have a patient's DSTU2 FHIR ID but an STU3 resource requires an STU3 FHIR ID. As of the November 2018 version, Patient FHIR resources are FHIR ID version agnostic and you can use the Patient DSTU2 ID with the Patient STU3 resource and vice versa. As of the May 2019 version, all FHIR resources are Patient FHIR ID version agnostic and you can use the Patient DSTU2 ID with all STU3 resources and vice versa. You can pass in PatientID and PatientIDType to return a collection of different identifiers unique to a patient using the Patient.Read FHIR (STU3) resource. This can be used to get different Patient FHIR version IDs or to get an Epic-specific ID from a FHIR ID or vice versa. To start, we need to construct a search string for this resource.

Using the provided URL template for this web service, we can create a search string. Note, this can be a DSTU2 ID for the STU3 resource in November 2018+:

    
var baseUrl = "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/STU3"
var identifierSearchString = "/Patient/" + patientId;
    

We can use jQuery's $.getJSON() function to call into the URL and retrieve our data in JSON format. It takes a parameter describing the URL to query, 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:

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

The response contains a number of identifiers for the patient, most notably Derrick's FHIR STU3 ID, in the identifier element:

{
  "resourceType": "Patient",
  "id": "eq081-VQEgP8drUUqCWzHfw3",
  "extension": [
    {
      "valueCode": "M",
      "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex"
    },
    {
      "extension": [
        {
          "valueCoding": {
            "system": "http://hl7.org/fhir/us/core/ValueSet/omb-race-category",
            "code": "2028-9",
            "display": "Asian"
          },
          "url": "ombCategory"
        },
        {
          "valueString": "Asian",
          "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"
    }
  ],
  "identifier": [
    {
      "use": "usual",
      "type": {
        "text": "EPIC"
      },
      "system": "urn:oid:1.2.840.114350.1.13.0.1.7.5.737384.0",
      "value": "E4005"
    },
    {
      "use": "usual",
      "type": {
        "text": "EXTERNAL"
      },
      "value": "Z6127"
    },
    {
      "use": "usual",
      "type": {
        "text": "FHIR"
      },
      "value": "TGsj9rYGI8SV-LYYkdH1JqbxYuFGfrODXSPC6iLLYkJcB"
    },
    {
      "use": "usual",
      "type": {
        "text": "FHIR STU3"
      },
      "value": "eq081-VQEgP8drUUqCWzHfw3"
    },
    {
      "use": "usual",
      "type": {
        "text": "INTERNAL"
      },
      "value": "     Z6127"
    },
    {
      "use": "usual",
      "type": {
        "text": "EPI"
      },
      "system": "urn:oid:1.2.840.114350.1.13.0.1.7.5.737384.14",
      "value": "203711"
    },
    {
      "extension": [
        {
          "valueString": "xxx-xx-0000",
          "url": "http://hl7.org/fhir/StructureDefinition/rendered-value"
        }
      ],
      "use": "usual",
      "system": "urn:oid:2.16.840.1.113883.4.1"
    }
  ],
  "active": true,
  "name": [
    {
      "use": "usual",
      "text": "Derrick Lin",
      "family": "Lin",
      "given": [
        "Derrick"
      ]
    }
  ],
  "telecom": [
    {
      "system": "phone",
      "value": "785-555-5555",
      "use": "home"
    },
    {
      "system": "phone",
      "value": "785-666-6666",
      "use": "work"
    }
  ],
  "gender": "male",
  "birthDate": "1973-06-03",
  "deceasedBoolean": false,
  "address": [
    {
      "use": "home",
      "line": [
        "7324 Roosevelt Ave"
      ],
      "city": "INDIANAPOLIS",
      "district": "MARION",
      "state": "IN",
      "postalCode": "46201",
      "country": "US",
      "period": {
        "start": "2019-05-24"
      }
    }
  ],
  "maritalStatus": {
    "text": "Married"
  },
  "communication": [
    {
      "language": {
        "coding": [
          {
            "system": "http://hl7.org/fhir/ValueSet/languages",
            "code": "en",
            "display": "English"
          }
        ],
        "text": "English"
      },
      "preferred": true
    }
  ],
  "managingOrganization": {
    "reference": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/STU3/Organization/enRyWnSP963FYDpoks4NHOA3",
    "display": "Epic Hospital System"
  }
}

You can now save off Derrick's STU3 FHIR ID taking advantage of Jquery's $.grep() function:

    
var result = $.grep(data, function(x) { return x.type[n].text == "FHIR STU3"; });
var patientIdSTU3 = result[0].value  //We’ll only ever return one FHIR STU3 ID
    

Now that you have the STU3 ID, you're free to call into STU3 resources. Remember to change your base URL to reference STU3. Here's an example of the same Condition search we did earlier but now using Derrick's STU3 FHIR ID.

var completeSearchString = "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/STU3/Condition?patient=" + patientIdSTU3 + "&clinical-status=active"

Of note, one difference you might notice between DSTU2 and STU3 resources is that paging is only supported in STU3 resources. DSTU2 responses come back in one request.

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

For historical reasons, some of Epic’s FHIR resources have identifiers that are longer than 64 characters. For example, Medication resource IDs sometimes exceed this length. 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 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 request that your new draft client be configured to observe the 64-character identifier length limit by submitting an email request. This length limit will apply to both the production and non-production client IDs for your application.

Sandbox Test Data

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

Sandbox URI - https://fhir.epic.com/interconnect-fhir-oauth/
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 ProviderRole resource.

FHIRTWO, USER

FHIRTWO

EpicFhir11!

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

These are some of the test patients that exist in the Epic on FHIR sandbox.

Patient

Identifiers and Credentials

Applicable Resources

Camila Lopez

FHIR: erXuFYUfucBZaryVksYEcMg3

External: Z6129

MRN: 203713

MyChart Login Username: fhircamila

MyChart Login Password: epicepic1

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

Derrick Lin

FHIR: eq081-VQEgP8drUUqCWzHfw3

External: Z6127

MRN: 203711

MyChart Login Username: fhirderrick

MyChart Login Password: epicepic1

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

Desiree Powell

FHIR: eAB3mDIBBcyUKviyzrxsnAw3

External: Z6130

MRN: 203714

MyChart Login Username: fhirdesiree

MyChart Login Password: epicepic1

  • Immunization
  • Observation (Vitals)
  • Patient

Elijah Davis

FHIR: egqBHVfQlt4Bw3XGXoxVxHg3

External: Z6125

MRN: 203709

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

Linda Ross

FHIR: eIXesllypH3M9tAA5WdJftQ3

External: Z6128

MRN: 203712

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

Olivia Roberts

FHIR: eh2xYHuzl9nkSFVvV3osUHg3

External: Z6131

MRN: 203715

  • Binary
  • Condition
  • Device
  • DocumentReference
  • Patient

Warren McGinnis

FHIR: e0w0LEDCYtfckT6N.CkJKCw3

External: Z6126

MRN: 203710

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

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

A Bulk FHIR access request must be sent to open@epic.com before any testing can be done with our sandbox. Include in your email the client ID of the application you'd like to use for sandbox testing. Limit of one bulk FHIR request per day.

NOTE: You MUST select the Kick-off, Status Request, File Request, and Delete Request APIs along with any APIs for which you want to export data, otherwise you will receive a 403 - Forbidden error for any data not covered by your app's scope.

Bulk FHIR Group

FHIR Group ID

Group consists of test patients in table above.

e3iabhmS8rsueyz7vaimuiaSmfGvi.QwjVXJANlPOgR83

Patient-Facing Apps Using FHIR

FHIR API Response Variations in Patient-Facing Applications

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

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

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

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

Automatic Client Record Distribution

Automatic Client ID Distribution: USCDI Apps

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

  • The application:
    • Is created through Epic on FHIR
    • Uses only FHIR APIs from the list in the appendix below †
    • Only reads data from Epic
    • Is patient-facing
    • Does not use refresh tokens OR uses refresh tokens and has a client secret uploaded by the vendor for that community member
    • Is marked "Ready for Production" and was marked ready after Sept. 3rd, 2020
      • Apps can be marked "Ready for Sandbox Use" to test with our Epic on FHIR environment prior to marking the app "Ready for Production"
      • We recommend that any apps marked "Ready for Production" will be tested with a customer's non-production environment before going live
  • The Community Member:
    • Has signed the open.epic API Subscription Agreement
    • Has not disabled auto-download functionality

Automatic Client ID Distribution: CCDS (MU3) Apps

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

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

Automatic Client ID Distribution: CMS Payor Apps

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

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

Manual Client ID Distribution

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

Appendix

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

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

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

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

App Default FHIR Version

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

  • SMART on FHIR: Multiple pieces of the SMART on FHIR authentication workflow
  • Authentication: OpenID Connect or Introspect for token inspection and user authentication
  • CDS Hooks: Supported hooks and other features

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

SMART on FHIR

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

  • iss element
  • JSON payload

iss Element

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

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

JSON Payload

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

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

Authentication

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

  • OpenID Connect (OIDC)
  • Introspect

OpenID Connect (OIDC)

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

fhirUser Element

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

Sub Element

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

Introspect

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

CDS Hooks

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

  • Prefetch support
  • Resource ID version
  • Supported hooks

Prefetch Support

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

Resource ID Version

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

Supported Hooks

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

Search Parameters

FHIR Search Parameters

This document describes the FHIR Search result parameters currently available, what they can do, and how to use each of these parameters with Epic FHIR APIs.

Search result parameters modify how results are returned in a Search API’s response. See the table below for the search result 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

  

_id Parameter

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

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

Examples:

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

_count Parameter

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

Important note on resources that return many results:

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

The API response when using the _count parameter behaves differently for STU3 and later requests, versus DSTU2 requests, as described below.

Examples:

For STU3 and above, look for the link.relation parameter. If link.relation is "next", then use the request in link.url to obtain the next set of resources. Continue until link.relation is "self".

Sample Request

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

Sample Response

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

_include Parameter

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

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

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

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

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

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

Examples:

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

_revInclude Parameter

The _revInclude parameter (reverse include) is used to return a particular resource, along with other resources that refer to that resource. For this parameter to return results, the FHIR resource specified in the search response must be referenced by the resource specified in the _revInclude parameter.

Provenance

Epic currently supports using this parameter to fetch provenance data for search interactions with any FHIR resources that are part of the U.S. Core Data for Interoperability data classes and elements. This parameter is supported in Epic version February 2020 or later (special updates are required to use this feature on the February 2020, May 2020, or August 2020 versions).

Parameter format: _revInclude=Provenance:target

The response will have the following format:

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

Examples:

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

AuditEvent

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

Parameter format: _revInclude=AuditEvent:entity

The response will have the following format:

Element Type Cardinality Description

type

Coding

1..1

Type or identifier of the event

recorded

Instant

1..1

Time when the event was recorded

agent

BackboneElement

1..*

Actors involved in the event

agent.role

CodeableConcept

0..*

Agent role in the event

agent.who

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

 

0..1

Identifier of who participated

agent.requestor

Boolean

1..1

Whether the user is the initiator

source

BackboneElement

1..1

The audit event reporter

source.observer

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

1..1

The identity of the source detecting the event

entity

BackboneElement

0..*

Data or objects used

entity.what

Reference (Any)

0..1

Specific instance of the resource

entity.detail

BackboneElement

0..*

Additional information about the entity

entity.detail.type

String

1..1

Name of the property that changed

 

String

1..1

Property value

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

Extension

0..1

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

Examples:

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

Vaccine Credentials

Epic's support for vaccine credentials using the SMART Health Cards specification is evolving. The content on this page might change without notice.

  • What's a "Vaccine Credential?
    • Related Terms & Definitions
    • Audience and Intent
  • What's a SMART Health Card?
    • Accessing Epic-Generated SMART Health Cards
    • Reading SMART Health Cards
      • QR codes
        • Numerical Decoding
        • Chunked QR Codes
      • .smart-health-card Files
    • Consuming SMART Health Cards
      • JOSE Header
      • Payload
        • Inflation
        • SMART Health Card Header
        • FHIR Bundle
        • FHIR Patient
          • Considerations For Patient Identity
        • FHIR Immunization
          • Example Immunization
        • FHIR Observation
          • Example Observation
      • SMART Health Cards & Trust
        • Verify the Signed Web Token
  • Open Source Libraries & References

What's a "Vaccine Credential"?

A vaccine credential binds an individual’s demographic information with information about their lab tests or vaccine history into a package that is machine-readable and digitally signed by the issuer. Unlike a paper vaccine card, a vaccine credential is verifiable and therefore much more difficult to tamper with or counterfeit.

A CDC vaccine card, widely issued in the United States to patients when they receive a COVID-19 vaccination.

A CDC vaccine card, widely issued in the United States to patients when they receive a COVID-19 vaccination.

Related Terms & Definitions

  • Vaccine or immunity passport. Passports are issued by governments. An immunity passport asserts immunity. In contrast, a vaccine credential is just an alternative way of representing information documented about a patient--specifically, their vaccination or lab history.
  • Health Wallet. An app available for install and use on a consumer's device that aggregates and stores vaccine credentials from different sources. A health wallet is a convenient way to store multiple credentials, but is not strictly required.
  • Pass App. An app available for install and use on a consumer's device that reads one or more vaccine credentials to determine if the user meets entry criteria for a specific venue or building, for example. might also perform some form of identity verification and might also commonly have health wallet-type functionality. A pass app might be useful in creating "fast lanes" in which a consumer's identity would not need to be re-verified, but a verifier should not require the patient to have a pass app.
  • Verifier. A consumer of a SMART Health Card.

Audience and Intent

This document was designed to support health wallets, pass apps, and verifiers, in designing apps and technologies that can consume a patient-provided SMART Health Card. We note particularly that any developer creating a pass app should also consider how verifiers using that pass app will interact with patients who do not have access to technology and present only with their ID and a printed SMART Health Card. Any verifier that requires patients to download a specific type of pass app (and thus, cannot read and interact with printed SMART Health Cards) is disadvantaging individuals without technology. SMART Health Cards were specifically designed to provide patient access regardless of technological ability or sophistication.

In other words, if you require an individual to download and present a certain type of app to enter your venue, you have not implemented the specification correctly.

What's a SMART Health Card?

A SMART Health Card is a standard for representing a vaccine credential. The SMART Health Cards standard is open, interoperable, and built on top of HL7 FHIR and compatible with W3C's Verifiable Credentials. It's designed to be:

  • Open – The standard is open source.
  • Interoperable – It's built on top of FHIR.
  • Privacy-preserving – Clinical content is embedded within SMART Health Cards, ensuring that a patient can't be tracked based upon where they use their vaccine credential.
  • Equitable – It can be represented entirely on paper.

A SMART Health Card contains very limited patient demographics, and either information about the patient's COVID-19 immunization or COVID-19 lab result.

Technically, a SMART Health Card is a JSON object containing a JWS of compressed JSON, consisting of a FHIR Patient and either one or two FHIR Immunizations; or a FHIR Observation resource. The card can be represented as a file ending in the .smart-health-card extension, or as a QR code printed on paper or displayed digitally.

You can learn more about SMART Health Cards by visiting the following websites:

  • https://vci.org/
  • https://smarthealth.cards/
  • http://build.fhir.org/ig/dvci/vaccine-credential-ig/branches/main/

Accessing Epic-Generated SMART Health Cards

Epic generates SMART Health Cards in two different ways and via a few different workflows:

  1. Patients can access SMART Health Cards as either a QR code displayed in or a file downloaded from the MyChart website or the MyChart mobile apps.
  2. Health systems using Epic can generate and print a QR code containing a patient's SMART Health Card in the clinician-facing interface, Hyperspace. Typically, this workflow would be used to provide SMART Health Cards to patients without access to MyChart.

Patients can share QR codes either directly from a mobile device or from a piece of paper. You should test and validate that your app can consume the QR code from both a mobile device and from a piece of paper.

A patient can download their SMART Health Card from MyChart onto their device. If you're developing an app that can run on a patient's device, alongside MyChart, you should associate your app with the .smart-health-card filename extension.

Reading SMART Health Cards

QR codes

Every SMART Health Card can be represented as a QR code. Typical QR scanner apps expect the QR code to contain a URL that can be navigated to in a browser, but a SMART Health Card's QR code does not contain an http:// URL. Rather, the content begins with an shc:/ prefix, followed by a series of numbers and optionally forward slashes (/).

For example, a SMART Health Card QR code could contain this example content after it is scanned:

shc:/567629595326546034602925407728043360287028647167452228092864456652064567322455425662713655612967382737094440447142272560543945364126252454415527384355065405373633654575390303603122290952432060346029243740446057360106413733531270742335036958065345203372766522453157257438663242742309774531624034227003622673375938313137693259503100426236772203355605036403580035610300766508583058374270060532535258081003347207250832110567524430725820265707614406667441643124353569754355376170447732541065607642436956282976307276247374426657326827736143256004577460541121230968766729300406344232537345706367327527360526620626030777047434662721590753712525295755717157712404305365553050007570070626252762326973342068033331323970433259453532396071684011083877646011585542067525276750403569746574042500432127583065602700296834605068374126617637383032031153273532295552400564655669374044767208283743690777676126004361576025612868262976107611767042442744504570773124097729726134647000600635240740664475562634400569324229237641072230287177047474320721676355073560542058377743647371335212506320077300352940590840740926502974284434626571632106437507760408126227344173035234272224650859284424550330072738216709675476296859290842564163266200100030407726503740243343666063287100680677296841286766396657316871405227413263621127776161647443056825046903656367560042552754107309332612290435433150635250555600416734505731035241562857000866406707250066585029295368623020723771290300716970507307715829230409632369722208220022421235364507226273407623637673266125102552327255034322275824010800676966507066627420335210701037520576422230612371756123652765287762007444002131666470397727224500564164325437330542095250237703346428534576030771757133406430361224385420

The numbers following the shc:/ prefix are the content of the SMART Health Card and must be numerically decoded.

Numerical Decoding

SMART Health Cards' QR codes represent characters as their ASCII code. The SMART Health Cards specification defines a simple algorithm to represent each character from the JWS as a two-digit number. Following the prefix, each pair of digits is converted into a single character by adding 45 to determine the encoded letter that the integer represents.

For example, the first pair of digits in the numerically encoded SMART Health Card example above is 56. By adding our offset constant 45 to 56, the value translates to ASCII's lower-case e (Chr(101) = "e"). The constant 45 was chosen because 45 is the ASCII value for "-", which is the lowest character that can exist in our Base64-URL encoded content.

Decoding the string proceeds as follows:

56 + 45 = e

76 + 45 = y

29 + 45 = J

Etc.

When parsing is complete, the above shc:/ string resolves to the following JWS:

eyJhbGciOiJFUzI1NiIsImtpZCI6ImZoa3ZpMEdWektQdjJpSHR6YUYtWHFicTZQVGFEcVdHSXd3c2RQNnZxT00iLCJ6aXAiOiJERUYifQ.3VRNb9swDP0rg3bZANuynCZLfFwSoMWwD6zZLkUOCs0kGvRhSLLRrMh_L-WkQzC0Pe20m0g-Pj0-yn5gKgRWs32Mbag570Ou4F5M82paYKugAGf4jY3owVmLEPPrxXdRjsYzMc7niyWXreIJyKuyEvwWofMqHvjXFi1fwic8BD6qypJK13OWMbvZslpMxHQ2Gk3G04z1wOoHBh4btFFJfdttftE1KbndK_-xs43GFHkMrvOAq0NLMTsXMhZPMTitqU85Szmi8gdW3xFHp_UPrwnw1F-XBHgKniH-JqOi_qRVGjyRSKM08bHPMJdaU2mnerRUYyu5IRXr4zpjG-XjfiFjIqGJy7y8ysWYHY_ZszLE6zJujOms-i3PE4UoYxeGOU2rMWJDyV4CKItz1wwM4Bpld4PicAgRzXmvtNa9_lA4v-PJUh5Uw6G_JwIYOkntlB3Xx4y159kHOVv0aOHCEn5hIYEd0K4HSBp6pcyJqhJ5WeVlGk-7-KUzG_RUENXoilIt-q3zJqVIpoTofLqtUaHVMlk8HzjjmwX2qF1r0nlpe-WdHc7v6NG9J1PXL_la_de-VpO_fL0aVeIf-5oUp4F-og_JJbqkKAuRtJ0-trs_v4tgJD16lDruC5C-CW9PQZ4CkvUyDlyvGjF7FaMud0XCHgE.5-pro_sokwANa7s7Ra2yWCKjDtxjDnHnIzk-wY-BLomsTzHCZ-eVmMcRN2W6a_Dz0OmIbZy04txtNUmKQ9EScA

See the SMART Health Cards specification for an alternate explanation of numerical encoding.

Chunked QR Codes

In some cases, the underlying demographic and clinical data stored in a SMART Health Card QR code might simply be too large for a single QR code. If so, the SMART Health Card is "chunked" -- split across multiple QR codes that must be scanned and reassembled before any data can be accessed. In this case, the shc:/ prefix is appended with two additional values, delimited by forward slashes ("/"). The first value defines the order of the current QR code for reassembly and the second is the total number of QR codes required to reassemble the data.

For example, a SMART Health Card of 3500 characters is split across three distinct QR codes. The prefix of the first QR code will be: shc:/1/3/, the second: shc:/2/3/ and the third: shc:/3/3/. The contents of all three QR codes (following the final forward slash) must be concatenated in order before inflation.

Requiring multiple QR codes to receive a single SMART Health Card is undesirable and likely confusing to consumers. Epic will rarely place so much information in a single SMART Health Card such that chunking the data across multiple QR codes is necessary. However, apps consuming these QR codes must be prepared to scan and assemble multiple QR codes.

.smart-health-card Files

In addition to presenting a QR code to a scanner, a consumer can also download a file to share a SMART Health Card. In this case, there's no QR code, no numeric encoding, and no chunking. The SMART Health Card is represented as a JWS wrapped in a simple JSON array. The filename extension of this file will always be ".smart-health-card".

Here's an example of a .smart-health-card file containing a single SMART Health Card:


{
  "verifiableCredential": [
"eyJhbGciOiJFUzI1NiIsImtpZCI6ImZoa3ZpMEdWektQdjJpSHR6YUYtWHFicTZQVGFEcVdHSXd3c2RQNnZxT00iLCJ6aXAiOiJERUYifQ.3VRNb9swDP0rg3bZANuynCZLfFwSoMWwD6zZLkUOCs0kGvRhSLLRrMh_L-WkQzC0Pe20m0g-Pj0-yn5gKgRWs32Mbag570Ou4F5M82paYKugAGf4jY3owVmLEPPrxXdRjsYzMc7niyWXreIJyKuyEvwWofMqHvjXFi1fwic8BD6qypJK13OWMbvZslpMxHQ2Gk3G04z1wOoHBh4btFFJfdttftE1KbndK_-xs43GFHkMrvOAq0NLMTsXMhZPMTitqU85Szmi8gdW3xFHp_UPrwnw1F-XBHgKniH-JqOi_qRVGjyRSKM08bHPMJdaU2mnerRUYyu5IRXr4zpjG-XjfiFjIqGJy7y8ysWYHY_ZszLE6zJujOms-i3PE4UoYxeGOU2rMWJDyV4CKItz1wwM4Bpld4PicAgRzXmvtNa9_lA4v-PJUh5Uw6G_JwIYOkntlB3Xx4y159kHOVv0aOHCEn5hIYEd0K4HSBp6pcyJqhJ5WeVlGk-7-KUzG_RUENXoilIt-q3zJqVIpoTofLqtUaHVMlk8HzjjmwX2qF1r0nlpe-WdHc7v6NG9J1PXL_la_de-VpO_fL0aVeIf-5oUp4F-og_JJbqkKAuRtJ0-trs_v4tgJD16lDruC5C-CW9PQZ4CkvUyDlyvGjF7FaMud0XCHgE.5-pro_sokwANa7s7Ra2yWCKjDtxjDnHnIzk-wY-BLomsTzHCZ-eVmMcRN2W6a_Dz0OmIbZy04txtNUmKQ9EScA"
  ]
}

The .smart-health-card files downloaded from MyChart web or mobile will contain one or two entries in the verifiableCredential JSON array. Each array entry is a self-contained SMART Health Card.

Consuming SMART Health Cards

Regardless of how a SMART Health Card is received -- a QR code scan or a file download, the same steps are used to verify and access the information contained within it.

A JWS, including our SMART Health Card JWS, is made up of three, Base64-encoded parts:  JOSE header, a payload, and a signature. Per RFC 7515, these three parts are concatenated together with periods.

JOSE Header

The JOSE (Javascript Object Signing and Encryption) Header identifies the algorithms used to sign and compress the payload and a unique identifier of the public key to be used to verify the signature.

For example, the JOSE header from the above JWS is:

eyJhbGciOiJFUzI1NiIsImtpZCI6ImZoa3ZpMEdWektQdjJpSHR6YUYtWHFicTZQVGFEcVdHSXd3c2RQNnZxT00iLCJ6aXAiOiJERUYifQ

Upon Base64-decoding it (and formatting for readability):

{
"alg": "ES256",
"kid": "fhkvi0GVzKPv2iHtzaF-Xqbq6PTaDqWGIwwsdP6vqOM",
"zip": "DEF"
}

  • alg – the algorithm used to sign the JWS.
  • kid – a "key identifier" which is used to find the public key for validating the signature.
  • zip – the compression algorithm used to deflate the payload. Note that the JWS standard does not define the zip header; the SMART Health Card uses borrows it from the closely related JWT standard.

In the SMART Health Card standard, the alg header will always be "ES256" and the zip header will always be "DEF". In Epic's implementation of the standard, only the kid header will change, as it uniquely identifies the public key used by the healthcare delivery organization that issued the SMART Health Card. The public key may be rotated periodically.

Payload

The payload of a SMART Health Card JWS (that is, the second of the three period-delimited pieces in the JWS) is base64-encoded, compressed JSON. The JSON is compressed with the DEFLATE algorithm.

Inflation

The DEFLATE algorithm is open source and has significant library support. DEFLATE is the same compression algorithm used by gzip but doesn't make use of the gzip header and footer. See RFC 1951 for more information about this compression algorithm.

As an example, starting with this JWS payload from our example above,  

3VRNb9swDP0rg3bZANuynCZLfFwSoMWwD6zZLkUOCs0kGvRhSLLRrMh_L-WkQzC0Pe20m0g-Pj0-yn5gKgRWs32Mbag570Ou4F5M82paYKugAGf4jY3owVmLEPPrxXdRjsYzMc7niyWXreIJyKuyEvwWofMqHvjXFi1fwic8BD6qypJK13OWMbvZslpMxHQ2Gk3G04z1wOoHBh4btFFJfdttftE1KbndK_-xs43GFHkMrvOAq0NLMTsXMhZPMTitqU85Szmi8gdW3xFHp_UPrwnw1F-XBHgKniH-JqOi_qRVGjyRSKM08bHPMJdaU2mnerRUYyu5IRXr4zpjG-XjfiFjIqGJy7y8ysWYHY_ZszLE6zJujOms-i3PE4UoYxeGOU2rMWJDyV4CKItz1wwM4Bpld4PicAgRzXmvtNa9_lA4v-PJUh5Uw6G_JwIYOkntlB3Xx4y159kHOVv0aOHCEn5hIYEd0K4HSBp6pcyJqhJ5WeVlGk-7-KUzG_RUENXoilIt-q3zJqVIpoTofLqtUaHVMlk8HzjjmwX2qF1r0nlpe-WdHc7v6NG9J1PXL_la_de-VpO_fL0aVeIf-5oUp4F-og_JJbqkKAuRtJ0-trs_v4tgJD16lDruC5C-CW9PQZ4CkvUyDlyvGjF7FaMud0XCHgE

we Base64 URL-decode and inflate to arrive at the actual JSON content. The JSON of a SMART Health Card is made up of a header and a few FHIR resources.

SMART Health Card Header

A SMART Health Card's header elements identify if the card contains immunization or lab result information, a unique url for the organization that issued the card and the time at which the card was issued.

  • iss – Append /.well-known/jwks.json to this url to access the JSON Wek Key Set containing a public key for verification of the JWS signature. See SMART Health Cards & Trust for additional information.
  • nbf – The time at which the SMART Health Card was issued (per RFC 7519). Epic generates SMART Health Cards on-demand, so this timestamp will correspond to the time at which the card was accessed by the patient.
  • type – Identifies the SMART Health Card as containing either COVID-19 vaccination or lab result. Epic-generated SMART Health Cards will always be one of the following:
    • ["https://smarthealth.cards#health-card", "https://smarthealth.cards#covid19", "https://smarthealth.cards#immunization"]
    • ["https://smarthealth.cards#health-card", "https://smarthealth.cards#covid19", "https://smarthealth.cards#laboratory"]
  • credentialSubject – Contains a FHIR Bundle of a Patient resource, either one or two FHIR Immunization resources, or an Observation resource representing a lab result.

This is an example of the SMART Health Card header describing an immunization card, issued on April 20, 2021.

{
	"iss": "https://vs-icx18-28.epic.com/Interconnect-HDR1035915-CDE/api/epic/2021/Security/Open/EcKeys/32001/SHC",
	"nbf": 1618933658,
	"type": ["https://smarthealth.cards#health-card", "https://smarthealth.cards#covid19", "https://smarthealth.cards#immunization"],
	"vc": {
  "credentialSubject": {
      //payload removed for readability
		}
	}
}

FHIR Bundle

The credentialSubject element from the SMART Health Card header defines the version of FHIR and contains a Bundle of FHIR resources.

  • fhirVersion – Version of FHIR resources contained in this SMART Health Card. Epic generated SMART Cards will always use FHIR R4 resources, represented in this field as “4.0.1”.
  • fhirBundle – A Bundle of type collection containing two to three FHIR resources. Each entry in the Bundle is a distinct FHIR resource.

Here's an example of a credentialSubject containing a fhirBundle:

{
	"credentialSubject": {
		"fhirVersion": "4.0.1",
		"fhirBundle": {
			"resourceType": "Bundle",
			"type": "collection",
			"entry": [
        { //FHIR resource },
				{ //FHIR resource #2 },
				{ //another FHIR resource! },
			}
		}
	}

FHIR Patient

SMART Health Cards always contain a FHIR Patient resource. Unlike a typical Patient resource, the only information in the resource is the patient's given and family name, and date of birth.

When consuming a SMART Health Card, the demographics in this Patient resource are verified against the user's identity.

{
	"resource": {
		"resourceType": "Patient",
		"name": [{
			"family": "McCall",
			"given": ["Table"]
		}],
		"birthDate": "2000-04-15"
	}
}

Considerations For Patient Identity

Importantly, possession of a SMART Health Card does not prove a patient's identity. In fact, patient caregivers, for example: parents, may access and download the SMART Health Cards of their wards. Pass apps and verifiers must verify identity.

When a patient shares a SMART Health Card with a verifier, the verifier is expected to validate the patient's identity independently. For example, if a patient presents their driver's license along with their SMART Health Card, the verifier should confirm that the demographics within the SMART Health Card (specifically, the name and date of birth), match those on the driver's license.

FHIR Immunization

A SMART Health Card documenting a vaccination contains one or two FHIR Immunization resources. The below example describes a  Pfizer immunizations completed on February 1.

  • status – contains the value “completed”
  • vaccineCode – contains a CVX code identifying the given vaccine.
  • occurrenceDateTime – the date on which the vaccine was administered
  • lotNumber – the product lot number, further identifying the vaccine
  • performer – contains the name of the organization at which the vaccine was administered. Will not be present if administering organization is unknown.
Example Immunization
{
	"resource": {
		"resourceType": "Immunization",
		"status": "completed",
		"vaccineCode": {
			"coding": [{
				"system": "http://hl7.org/fhir/sid/cvx",
				"code": "208"
			}]
		},
		"patient": {
			"reference": "Patient/resource:0"
		},
		"occurrenceDateTime": "2021-02-01",
		"lotNumber": "1234",
		"performer": [{
			"actor": {
				"display": "Current Development Environment (CDE)"
			}
		}]
	}
} 

FHIR Observation

A SMART Health Card documenting a lab result contains one FHIR Observation resource. The below example describes a negative lab test resulted on November 23, 202.

  • status – contains the value "final"
  • code – contains a LOINC code identifying the lab test, if known
  • effectiveDateTime – the time at which the lab test was resulted
  • performer – contains the name of the organization at which the test was performed.
  • value – the result of a COVID-19 lab test may be represented as a SNOMED code, a quantity, or a string.
Example Observation
{
 	"resource": {
 		"resourceType": "Observation",
 		"status": "final",
 		"code": {
 			"coding": [{
 				"system": "http://loinc.org",
 				"code": "882-1"
 			}]
 		},
 		"subject": {
 			"reference": "Patient/resource:0"
 		},
 		"effectiveDateTime": "2020-11-23T06:00:00Z",
 		"performer": [{
 			"display": "Health system"
 		}],
 		"valueString": "Negative"
 	}
 }

SMART Health Cards & Trust

A SMART Health Card is verifiable because its signed payload can be verified against a trusted public key. Each issuer publishes a JSON Web Key Set (JWKS) containing at least one public key. Typically, each healthcare organization that uses Epic acts as a distinct issuer.

A verifier must determine which issuers to trust. Issuers participating in the Vaccine Credential Initiative have shared their publicly accessible JSON Web Key Set URLs in this directory:

  • https://raw.githubusercontent.com/the-commons-project/vci-directory/main/vci-issuers.json

Verifiers should consider trusting and caching all of the public keys provided in the key sets from this directory.

Verify the Signed Web Token

An issuer's key set is discoverable during verification by appending "/.well-known/jwks.json" to a SMART Health Card's iss value. For example, iss value in the above SMART Health Cards is an inaccessiable, url internal to Epic: https://vs-icx18-28.epic.com/Interconnect-HDR1035915-CDE/api/epic/2021/Security/Open/EcKeys/32001/SHC

HTTP Get https://vs-icx18-28.epic.com/Interconnect-HDR1035915-CDE/api/epic/2021/Security/Open/EcKeys/32001/SHC/.well-known/jwks.json

{
  "keys": [{
    "x": "gfoeoAb2l-hqEHXyZIBqa1OObZ7Tunx5hlYRXtnPJwg",
    "y": "HcBgiLJ53BFqOp5lxiMUz5Jsik9DtM286pyWv2hVKNA",
    "kid": "fhkvi0GVzKPv2iHtzaF-Xqbq6PTaDqWGIwwsdP6vqOM",
    "use": "sig",
    "kty": "EC",
    "alg": "ES256",
    "crv": "P-256"
  }]
}

The key identifier from the JWKS's kid element corresponds to kid element in the header of the SMART Health Card's JWS. Epic issued SMART Health Cards are signed using ECDSA P-256 SHA-256.

Verifiers should use standard security libraries to verify SMART Health Card signatures. Refer to Appendix A.3 of RFC 7515 to understand how Epic’s signature is calculated and formatted.

Open Source Libraries & References

While it is not an exhaustive list, nor maintained or affiliated with Epic, the following resources might be helpful:

  • Reference implementation: https://c19.cards/venue and open source code: https://github.com/smart-on-fhir/health-cards-tests
  • Microsoft-developed SMART Health Card validation SDK: https://github.com/microsoft/health-cards-validation-SDK
  • MITRE-developed SMART Health Cards tutorial Jupyter Notebook: https://github.com/dvci/health-cards-walkthrough/blob/main/SMART%20Health%20Cards.ipynb

FHIR Bulk Data Access Tutorial

FHIR Bulk Data Access Tutorial

Contents

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

Background

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

Important Considerations for Using Bulk Data

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

Technical Constraints

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

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

When to Use Bulk Data

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

Good Use Cases for Bulk Data

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

Poor Use Cases for Bulk Data

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

Best Practices

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

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

Epic's Implementation of the Bulk Data Standard

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

Using the Bulk Data APIs

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

Client Configuration

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.

Kicking Off the Bulk Data Request

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

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

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

Use the Group FHIR ID to call the group export operation:

https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/Group/eIscQb2HmqkT.aPxBKDR1mIj3721CpVk1suC7rlu3yX83/$export

The request above returns results for a default set of R4 resources in the ndjson format. The default set includes the resources in the U.S. Core Data for Interoperability (USCDI) data classes, resources from the patient compartment of the bulk data access specification, and additional supporting resources outside of the patient compartment. Starting in the November 2021 version of Epic (and in the August 2021 version with a special update), Provenance is also in the default set of resources.

Epic also supports two parameters for the group export operation: _type and includeAssociatedData.

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

When you have compiled all components of the request URL, you can submit the GET request to kick-off the bulk data workflow. Note that by default, a client can request a specific group of patients only once in a twenty-four-hour period. If you need to request bulk data more frequently, work with the Epic organization you're integrating with to configure an appropriate request window.

Your request must include the following headers:

  • Accept: application/fhir+json
  • Prefer: respond-async

Checking the Status of the Request

After kicking off the bulk data request, use the status API to track the progress of the request. In the response headers of the kick-off request, the value of the “Content-Location” header is the status URL for this bulk data request. Each bulk data request has a unique identifier used to request the status of a specific bulk data request.

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

If the bulk data request has not finished processing, the response body is empty. An approximate measure of progress is available in the "X-Progress" response header. The value of this header is "Searched X of Y patients" where "Y" is the number of patients in the group and "X" is the number of patients processed so far. This is only an approximate measure of progress because there is additional processing required after all patients have been searched, so it's possible for the header to be set to "Searched 100 of 100 patients" with no response body returned.

Epic recommends pinging for the request status every ten minutes for groups with a hundred or fewer patients, every thirty minutes for groups over a hundred, or using exponential backoff as described in the bulk data export specification.

After the request is completed, the status API returns the URLs for the resource files. For more information on the structure of the response, reference the API details.

{
    "transactionTime": "2021-06-23T16:39:52Z",
    "request": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/Group/eIscQb2HmqkT.aPxBKDR1mIj3721CpVk1suC7rlu3yX83/$export?_type=patient,encounter,condition",
    "requiresAccessToken": "true",
    "output": [
        {
            "type": "Patient",
            "url": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/BulkRequest/9ED3042CD44111EB84F2D2068206269D/e19upATM-PTKGZuHsy04IUQ3"
        },
        {
            "type": "Condition",
            "url": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/BulkRequest/9ED3042CD44111EB84F2D2068206269D/e8D8N1JwB3qCiHJkmeV.98w3"
        }
    ],
    "error": [
        {
            "type": "OperationOutcome",
            "url": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/BulkRequest/9ED3042CD44111EB84F2D2068206269D/eKbGgVw9pUn6xIiB8kZ756w3"
        }
    ]
}

Viewing Resource Files

Bulk data generates a separate file for each resource type. The "output" elements in the status API response lists each resource type and the corresponding file request URL. If a single resource has a very large number of results, the data is split into multiple files, all linked in the response. There is a maximum of three thousand resource instances per file. 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.

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. For more information, refer to the ndjson specification. Resource instances are included in the bulk data file through a search by Patient ID or through a reference in a previously gathered resource instance. The files do not differentiate by patient, so results for all patients are included in each resource file.

Viewing Request Errors

In addition to the resource files, if there are any request-level errors, the "error" element in the status API response includes the URL for the OperationOutcome file. This file includes only request-level errors. Bulk data does not return resource-level errors.

Error Code

Error Text

Notes

59130

"Some information was not or may not have been returned due to business rules, consent or privacy rules, or access permission constraints."

This error is logged when a resource is requested that the client is not authorized for. OperationOutcome.issue.diagnostics lists the resource.

59100

"Content invalid against the specification or a profile."

This error is logged when a parameter is included in the request that Epic doesn't support in bulk data. OperationOutcome.issue.diagnostics lists the parameter.

59136

"The resource or profile is not supported."

This error is logged when a resource is requested that Epic doesn't support in bulk data. OperationOutcome.issue.diagnostics lists the resource.

59176

"Error Converting FHIR Resource to ndjson. Resource: <resource type> FHIR ID: <resource FHIR ID>"

This error is logged when a resource instance cannot be included in the resource file because of its size. The resource instance can be requested separately using the resource's read interaction.

This error is logged starting in the November 2021 version of Epic and in August 2021 by special update.

Deleting Requests

If you no longer want to run a request after starting it, or no longer need the data stored, the bulk data delete API is available. This API uses the same URL as the status API, but uses the DELETE HTTP method rather than GET.

DELETE https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/BulkRequest/B0F84FB8D37411EB92726C04221B350C

If you delete a request before it is completed (the status API hasn’t returned any content), the request does not finish processing and is marked as deleted in the receiving system. The deleted request does not count towards the one request per group per client per day 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.

ID Types for FHIR APIs

ID Types for APIs

Table of Contents

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

FHIR API IDs and HL7 OIDs

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

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

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

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

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

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

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

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

OID Segment

Meaning

1.2.840.114350

Epic’s root OID

.1

Indicates that this is an application-type ID

.13

The application ID. Some common application IDs include:

  • 13: EDI
  • 72: Care Everywhere

.<CID>

The unique Epic organization ID

.<EnvType>

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

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

.7

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

.<Value>

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

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

Identifier Type

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

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

Examples

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

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

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

Here is a similar example in a FHIR format:

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

Identifier System

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

In Epic, the HL7 identifier system is stored in the “HL7 Root” field of the ID Type record. In some cases, if this value is not specified in the IIT record, Epic falls back to using an auto-generated OID that is specific to the current environment.

The meaning of the “HL7 Root” field

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

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

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

Organization-Specific IDs and Value Sets

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

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

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

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

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

Troubleshooting

Web App and Web Service Troubleshooting

Important note on making changes to your application:

Whenever you make changes to your app on the website, they may take up to 1 hour to sync with the Sandbox, and 12 hours to sync at an Epic organization's Epic instance. Keep this in mind for changes you make to endpoint uris, public keys and selected APIs.

Contents

  • OAuth 2.0 Errors
  • API Call Errors
  • Hyperspace/MyChart Embedding Errors
  • Web Page Errors
  • OAuth 2.0 Errors

    There are two types of OAuth 2.0 apps, launched apps and backend apps. Backend apps interact with only Epic's /token endpoint, while launched apps interact with the /token AND /authorize endpoints. We've broken out the error messages below by endpoint:

    Authorize Endpoint

    "Something went wrong trying to authorize the client. Please try logging in again."

    • Check that that redirect URI in your request matches one of the redirect URIs registered on the app. This includes the following:
      • If you've edited your app's URIs, wait for the client sync before testing
      • URIs are case sensitive
      • URIs must be exact matches. For instance "/" will not match "/index.html", even though browsers treat them the same
    • Check that the client ID in the request matches the client ID of the app, and that the response_type is code.
    • If you have a backend app encountering this error code, make sure the URL you're hitting ends in "/token" instead of "/authorize".
    • Make sure you're including a proper "aud" parameter in the request.
    • If using a POST request, make sure your body is of Content-Type: application/x-www-form-urlencoded instead of application/json, and make sure your request data is in the body instead of the query string (vice versa for GET requests).

    "invalid_launch_token"

    • The client ID in the FDI must match the client ID of the app. Confirm the client ID in the app on Epic on FHIR, E0E, FDI and authorization code request match exactly.
    • If you are redeeming a launch token at the wrong Epic instance (example: production vs non-production) this can occur.

    404 or 500 error

    • Request URL might be too long. Shorten your state parameter.

    I'm being asked to log in, but I'm launching from Epic/the Hyperspace Simulator

    • If you're launching from Epic (EHR launch) and still being asked to log in, have the app request the "launch" scope.

    Token Endpoint

    400 error:

    • "invalid_request" - Formatting of the HTTP Request can cause this issue. Try out the following:
      • Use the POST verb, not GET
      • Content-Type should be "application/x-www-form-urlencoded".
        • Do not send JSON, for example.
      • Content should be in the body of the request, not the querystring
        • There should be no "?" after "/token"
    • "invalid_client" - Your client authentication may be invalid. Try the following:
      • For apps using JWT authentication, refer to the linked JWT Auth Troubleshooting resource below.
      • If you're still having issues, try re-uploading your client authentication (client secret or public key) and waiting for the sync.

    400 error: "unauthorized_client"

    • Make sure the "Use OAuth 2.0" checkbox is checked on your app listing
    • If using a Backend OAuth 2.0 integration, there is required community member setup that if not complete will throw this error.

    API Call Errors

    This section assumes your app has obtained an access token and is having issues making API calls using it. If you have not received an access token yet, refer to the OAuth 2.0 errors section above.

    401 errors

    • 400 usually means you had the right security, but the data in your request was incorrect somehow.
    • Check that you're putting data in the right place (body vs querystring).
    • Look for error messages in the response body to assist. Here is a common one:
      • NO-PATIENT-FOUND - When searching using an ID such as MRN, the ID Type in Epic needs to have a descriptor. Make sure the descriptor (I IIT 600) is populated for the target ID Type, and distribute to the app for searching.
    • It's helpful if your app is able to log the response body, since Epic returns detailed error messages for most APIs.

    403 errors

    • For these errors, Epic identified who the client and user were, but denied access.
    • If the issue is with the client, we generally don't return a response body, just 403 Forbidden
      • In this case, check the API you are calling is attached to your client ID. Wait for the client to sync before testing any changes
      • For scope errors, the WWW-Authenticate HTTP Response header should say this:
        • Bearer error="insufficient_scope", error_description="The access token provided is valid, but is not authorized for this service"
    • If the issue is with the user, we generally return an error message describing why, such as:
      • "The authenticated user is not authorized to view the requested data" is common for FHIR calls. In this case, try the following:
        • Make sure you are handling access tokens correctly. If you mistakenly use one person's access token on another person's behalf, you'll see this error.
        • Organizations set up user security checks that also apply to access tokens. These security checks, if failed, produce this error.
        • If using a backend services app, these security checks are applied to the background user configured for your client ID. Otherwise, they are applied to the user who authenticated into your app.

    Hyperspace/MyChart Embedding Errors

    Many EHR Launched apps attempt to embed in Epic workflows, and rely on Epic build to do so. Below are common issues you might see when configuring one of these launches.

    Attempted to embed in Hyperspace, but the window popped out to a new browser:

    • Check the FDI record has the correct launch type.
    • There are 2 types of FDIs and 2 types of launch activities, they need to match to respect launch settings:
      • PACS FDIs - Need RIS_PACS_REDIRECTOR
      • Web FDIs - Need MR_CLINKB_* (Active Guidelines)

    Hyperspace launch displays a green/white spinning circle indefinitely (AGL spinner).

    • Confirm that all domains in the login workflow are added to the Epic Browser Allowlist.
    • Confirm that the workspace build points at the right FDI. Consider Active Guidelines debug mode to troubleshoot (shows URL navigated to).

    App launches externally in MyChart mobile when launchtype specifies embedded launch.

    • Confirm that all the app domains are allowlisted in the MyChart Mobile FDI record.

    Web Page Errors

    OAuth involves several web servers taking control of a browser in a sequence/handshake. The following errors could come from EITHER the Interconnect/Authorization server or the third-party app's server when they are trying to display HTML.

    This content cannot be displayed in a frame.

    • This could be hiding an OAuth 2.0 error, or an accidental login page.
      • Interconnect cannot be iframed, and is trying to show an error page or login page.
      • Will appear in trace logs, OR you can pop out the launch with FDI settings for "external window" workspace type.
      • Refer to these sections above for scenarios where Interconnect returns HTML:
        • "Something went wrong trying to authorize the client. Please try logging in again."
        • "I'm being asked to login, but I'm launching from Epic"
    • Otherwise, the third-party app is likely blocking the iframe.
      • The app needs to adjust the X-Frame-Options response header. The website can omit the "X-Frame-Options” header or set it to "ALLOW-FROM https://domain”. The domain should be that of the Hyperspace web server for the organization.

    Content was blocked because it was not signed by a valid security certificate.

    • Accompanied by pink background
    • Implies a web site's certificate is invalid
    • Check the third-party server and Interconnect server for expired or invalid certificates.

    This page can't be displayed

    • Error normally calls out the domain/server being requested
    • One cause is the browser being unable to resolve the website's IP address. Make sure the website's domain is publicly available, or at least available from the Hyperspace server for provider-facing embedded launches
    • If you can resolve the IP address, another cause is the server/website hanging up during the TLS handshake. Try using SSL Labs to diagnose why the server is hanging up. "Handshake Simulation" is especially useful since it calls out compatability with IE-11 and various windows versions
    JWT Auth Troubleshooting

    CDS Hooks Tutorial

    CDS Hooks Tutorial

    Introduction

    Clinical Decision Support (CDS) Hooks is an HL7 standard for in-workflow decision support integrations between electronic health record systems and remote, real-time, provider-facing decision support services. This tutorial describes Epic’s support for this standard.

    Epic’s support for CDS Hooks is built on a workflow engine and CDS rule engine. Organizations using Epic can configure native CDS alerts by defining the workflow point at which they are triggered, known as the hook, and the criteria to evaluate when they are triggered. These criteria determine whether a CDS alert appears to the clinician and, if it does, what information it contains. Epic’s implementation of CDS Hooks enables native criteria to call a remote CDS Hooks API when evaluating whether to show a CDS alert. In Epic, the native CDS alert is referred to as a BestPractice Advisory (BPA).

    Guidance to CDS Developers

    CDS Hooks is a powerful integration technology because it directly interacts with clinician workflows. Treat the ability to interact with clinicians carefully. As a CDS developer, you now share responsibility for a positive user experience. Your CDS service must be fast, must avoid alert fatigue, and should improve over time.

    Hooks

    As of Epic version May 2021, Epic primarily supports three standard hooks built on pre-existing CDS alert workflow triggers. Note that Epic supports many more native workflow triggers than CDS Hooks has standardly defined.

    patient-view

    The patient-view hook is triggered in two scenarios:
    1. When a patient’s chart is opened. The native Epic trigger is named Open Patient Chart. Epic recommends against firing a CDS Hooks request every time a patient chart is opened because it results in a poor user experience. Instead, work with the health system to use this trigger in combination with more specific, native business criteria in Epic to limit the number of CDS Hooks requests.
    2. When a native Epic trigger calls a CDS Hooks service that is not standardized by CDS Hooks. For example, Enter Allergy is a native Epic workflow trigger that an organization can configure to show a BPA, including evaluating criteria that calls a CDS Hooks service. Because there is no industry standard hook for the Enter Allergy workflow step, we reuse the patient-view hook. The Epic-specific "com.epic.cdshooks.request.bpa-trigger-action" extension (see below) can be used to differentiate between native Epic triggers.

    order-select

    Epic supports the order-select hook in workflows when a clinician enters orders for a patient. In addition to the new selection of one or more orders (which triggers the order-select event), the clinician might have already selected other orders that have not yet been signed. The draftOrders JSON object contains a bundle of both the newly selected orders and the previously selected, unsigned orders. The "selections" array of FHIR identifiers identifies which of the unsigned orders from the draftOrders bundle are newly selected. Decision support using this hook should focus on the newly selected orders.

    The draftOrders bundle can contain the MedicationRequest, ServiceRequest, or ProcedureRequest resource.

    order-sign

    The order-sign hook is triggered in Epic as the final step in the ordering process. It occurs after a clinician has clicked the Sign Orders button, but before the system has finalized the order. Decision support from this hook is the final chance for the clinician to revise the order. This hook allows for collection of all unsigned order details to send in the CDS Hooks call as MedicationRequest, ServiceRequest, or ProcedureRequest resources.

    CDS Hooks Request

    Prefetch

    Prefetch can be a valuable method for optimizing the performance of your CDS service. Evaluate whether you can collect the data needed to inform your CDS service using the CDS Hooks prefetch model.

    Prefetch within Epic can be configured in your CDS Hooks service’s BestPractice Advisory record. Any resource that you’d like to prefetch must be appropriately scoped to your client and match the client’s primary FHIR version. Epic does not support automatically retrieving the contents of a CDS Hooks service’s discovery endpoint.

    Here is an example of how prefetch might be configured for a CDS service:

    Prefetch Property Name FHIR Query
    patientPatient/{{context.patientId}}
    medicationsMedicationRequest?patient={{context.patientId}}&status=active
    encounterDxCondition?patient={{context.patientId}}&encounter={{context.encounterId}}&category=encounter-diagnosis
    user{{context.userId}}

    Note that the {{context.patientId}}, {{context.encounterId}}, and {{context.userId}} prefetch tokens are all used here. These IDs are returned by default in the CDS Hooks request context field and are also available to use in constructing contextual read/search FHIR requests that are included in the initial request to the CDS Hooks service. In contrast to the FHIR IDs, which are always sent in the context, the patient prefetch property defined above returns the entire patient resource within the prefetch. For searches, the medications and encounterDx prefetch properties above show how to use prefetch tokens as query parameters when implementing your CDS Hooks service.

    The {{context.userId}} prefetch token is unique in that it returns both a FHIR resource name and FHIR resource ID. If the clinician triggering the CDS Hooks request has a provider record in Epic, the request returns "PractitionerRole/". If the clinician does not have a provider record, the request returns "Practitioner/". Additionally, the {{userPractitionerId}} and {{userPractitionerRoleId}} prefetch tokens can be used in prefetch to return the FHIR resource ID for their respective resources.

    Input Fields for JSON POST Body

    Field

    Description

    hook

    Epic supports the following hooks:

    • order-select
    • order-sign
    • patient-view

    hookInstance

    A universally unique identifier (UUID) for each hook call.

    fhirServer

    The base FHIR URL for the health system.

    fhirAuthorization

    A structure holding an OAuth 2.0 bearer access token, which can be used to authenticate to Epic FHIR APIs for a short period of time.

    context

    The CDS Hooks specification (for example, patient-view) defines the context elements associated with each hook. Epic always sends patientId and userId.
    If the hook is initiated from an encounter context, encounterId is sent.
    For the order-select and order-sign hooks, draftOrders is always sent. Specific to order-select, the selections array identifies which orders are newly selected.

    prefetch

    Resources sent in the prefetch are configured by the health system and can include FHIR resources that are already supported by Epic.

    Epic Extensions

    Extension

    Description

    com.epic.cdshooks.request.bpa-trigger-action

    The specific trigger action in Epic that is mapped to the hook. Common values include, but are not limited to:

    • 5 (General BPA section)
    • 6 (Enter problem)
    • 7 (Enter diagnosis)
    • 18 (Enter order)
    • 23 (Sign orders)
    • 26 (IP Admission BPA section)
    • 27 (IP Discharge BPA section)
    • 29 (IP Transfer BPA section)
    • 60 (Open patient chart)

    com.epic.cdshooks.request.cds-hooks-specification-version

    The CDS hooks specification version used by Epic.

    com.epic.cdshooks.request.fhir-version

    The primary FHIR version of the CDS service as specified during OAuth registration.

    com.epic.cdshooks.request.criteria-id

    The ID of the BPA criteria record in Epic. This value can be helpful during troubleshooting.

    com.epic.cdshooks.request.epic-version

    The version of Epic that the health system is currently using.

    com.epic.cdshooks.request.cds-hooks-implementation-version

    The internal version Epic assigns to CDS Hooks implementations. Can be used to determine what features are supported.

    Example HTTP Request

    {
      "hookInstance": "f399c67c-c703-11ea-af16-460231621f93",
      "fhirServer": "https://example.com/interconnect-instance-oauth/api/FHIR/R4",
      "hook": "patient-view",
      "fhirAuthorization": {
        "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ1cm46ZXBpYzpjZWMuY2RlIiwiY2xpZW50X2lkIjoiYWVhYzNkYTctNjQ4MS00NWFmLWFkNDEtNDJlNWU3MDExNDFkIiwiZXBpYy5lY2kiOiJ1cm46ZXBpYzpDdXJyZW50LURldmVsb3BtZW50LUVudmlyb25tZW50IiwiZXBpYy5tZXRhZGF0YSI6Ik4yTDdoSFE3clNxLXdQbkstcVMyZlBTSjY1Q1EzS1ZOVHhhTUlBZGRVTllwemQyOEE0Q2d4ZHA0SVg0NUI5YmE4TVVJdEl1NkEzYjBkYmU1c1ZlY093V1ZZN0tTd2VZb09KZDR5R2hmblcxdk9yOENoX195N2NrT1VtV005cnVhIiwiZXBpYy50b2tlbnR5cGUiOiJhY2Nlc3MiLCJleHAiOjE1OTQ4NjMzMzcsImlhdCI6MTU5NDg2MzAzNywiaXNzIjoidXJuOmVwaWM6Y2VjLmNkZSIsImp0aSI6ImU4MjFhMDA5LTgzNjAtNDRkYy1iZmMwLTEyZmU4ODA3MjQyYSIsIm5iZiI6MTU5NDg2MzAzNywic3ViIjoiZUJxZ0Foc2pVcHF3a3lJWm9LZU1jVUEzIn0.hF2Ir9717QxJ0TgiFXKN1bhRdicgUHEf_0tjZ7qiy2Enzsn7_dW4HaueJqg22vcnrEayLxlW4h6Q6J5He3-U7VXmGBFd5AKXxVLbTQZupN6x4owt5n_N1OJKDu5UVJdlWoq-fBVn2sEyDfvITQ-BK21MWXxGaEcnttJNxS0Mk847JnxBby2oLzAd5NnIbdT733S2nL36ViK_mVnkucLu4vxwVRcaec2zCpvqF8Pn2Crl3yKaPKQbRqc6y12hprdUeB2VxOm3H5KxJQKYnEGTKNCmCy5w3Yaz86KtP9OlX1e3n6Km3TSCtZbj0Gm7QIVyDARAlREPA_XzbgQ3ItJang",
        "token_type": "Bearer",
        "expires_in": 300,
        "scope": "ALLERGYINTOLERANCE.READ ALLERGYINTOLERANCE.SEARCH Observation.Read (Smoking History) Observation.Read (Labs) (R4) Observation.Read (Vitals) Observation.Read (Vitals) (R4) Observation.Search (Labs) (R4) Observation.Search (Smoking History) Observation.Search (Vitals) Observation.Search (Vitals) (R4) Practitioner.Read (R4) Practitioner.Search (DSTU2) PROCEDURE.READ PROCEDURE.SEARCH MedicationRequest.Read (R4) MedicationRequest.Search (R4) CONDITION.READ CONDITION.SEARCH Condition.Read (R4) Condition.Search (R4) PATIENT.READ PATIENT.SEARCH PRACTITIONER.READ PRACTITIONER.SEARCH OBSERVATION.READ OBSERVATION.SEARCH urn:Epic-com:Informatics.2014.Services.Order.AnnotateOrders AllergyIntolerance.Search (R4) AllergyIntolerance.Read (R4) Patient.Read (R4) Patient.Search (R4) Location.Read (R4) Encounter.Read (R4) Encounter.Search (R4) urn:Epic-com:Core.2016.Services.DataUtility.GetImportDataLog Condition.Create (R4) Medication.Read (R4) MEDICATION.READ (DSTU2) MEDICATION.READ MEDICATIONORDER.READ MEDICATIONORDER.READ (DSTU2) MEDICATIONORDER.SEARCH MEDICATION.SEARCH MedicationStatement.Read (R4) MEDICATIONSTATEMENT.READ (DSTU2) MEDICATIONSTATEMENT.READ MEDICATIONSTATEMENT.SEARCH MedicationStatement.Search (R4) Condition.Search (R4) Patient.Read (R4) Practitioner.Read (R4) Patient.Search (R4) Practitioner.Search (R4) Condition.Read (R4) Condition.Create (R4) AllergyIntolerance.Read (R4) AllergyIntolerance.Search (R4) Observation.Read (R4) Observation.Search (R4) Practitioner.Search (R4) PractitionerRole.Search (R4) PractitionerRole.Search (R4) medication medication.create Encounter.Read (R4) Encounter.Search (R4) ProcedureRequest.Read (R4) ProcedureRequest.Search (R4) EPIC.FHIR.R4.SERVICES.MEDICATIONREQUEST.CREATE EPIC.FHIR.R4.SERVICES.MEDICATIONREQUEST.CREATE Condition.Create (Encounter Diagnosis) (R4) Condition.Create (Problems) (R4) EPIC.FHIR.R4.SERVICES.PROCEDUREREQUEST.CREATE EPIC.FHIR.R4.SERVICES.SERVICEREQUEST.CREATE Condition.Read (Encounter Diagnosis) (R4) Condition.Read (Health Concern) (R4) Condition.Read (Problems) (R4) Condition.Search (Encounter Diagnosis) (R4) Condition.Search (Health Concern) (R4) Condition.Search (Problems) (R4) Observation.Read (Core Characteristics) (R4) Observation.Read (Labs) (R4) Observation.Read (LDA-W) (R4) Observation.Read (Obstetric Details) (R4) Observation.Read (Smoking History) (R4) Observation.Read (Vitals) (R4) Observation.Search (Core Characteristics) (R4) Observation.Search (Labs) (R4) Observation.Search (LDA-W) (R4) Observation.Search (Obstetric Details) (R4) Observation.Search (Smoking History) (R4) Observation.Search (Vitals) (R4) MedicationRequest.Read (R4) MedicationRequest.Search (R4) condition.create(diagn Condition.Create Problem (R4) PractitionerRole.Read (R4) PractitionerRole.Read (R4)",
        "subject": "aeac3da7-6481-45af-ad41-42e5e701141d"
      },
      "context": {
        "patientId": "eXoGxqgBaJuNkuahMYmiDhg3",
        "encounterId": "eFyoeOuWgXtlQmOQzPdkQWwy3s8a49yrUc-LtjwhWT6g3",
        "userId": "PractitionerRole/e-QokEGUJIzyynNdkCFrs9w3"
      },
      "prefetch": {
        "-user": {
          "resourceType": "PractitionerRole",
          "id": "e-QokEGUJIzyynNdkCFrs9w3",
          "active": true,
          "practitioner": {
            "reference": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/Practitioner/eqZb1t82mP4YeBjiLldSFPQ3",
            "display": "Family Medicine Physician"
          },
          "code": [
            {
              "coding": [
                {
                  "system": "urn:oid:1.2.840.114350.1.13.861.1.7.10.836982.1040",
                  "code": "1",
                  "display": "Physician"
                }
              ],
              "text": "Physician"
            }
          ],
          "specialty": [
            {
              "coding": [
                {
                  "system": "urn:oid:1.2.840.114350.1.72.1.7.7.10.688867.4160",
                  "code": "10",
                  "display": "Cardiology"
                }
              ],
              "text": "Cardiology"
            },
            {
              "coding": [
                {
                  "system": "urn:oid:1.2.840.114350.1.72.1.7.7.10.688867.4160",
                  "code": "5",
                  "display": "Anesthesiology"
                }
              ],
              "text": "Anesthesiology"
            },
            {
              "coding": [
                {
                  "system": "urn:oid:1.2.840.114350.1.72.1.7.7.10.688867.4160",
                  "code": "15",
                  "display": "Dermatology"
                }
              ],
              "text": "Dermatology"
            },
            {
              "coding": [
                {
                  "system": "urn:oid:1.2.840.114350.1.72.1.7.7.10.688867.4160",
                  "code": "18",
                  "display": "Endocrinology"
                }
              ],
              "text": "Endocrinology"
            }
          ]
        },
        "patient": {
          "resourceType": "Patient",
          "id": "eXoGxqgBaJuNkuahMYmiDhg3",
          "extension": [
            {
              "extension": [
                {
                  "valueCoding": {
                    "system": "http://hl7.org/fhir/us/core/ValueSet/omb-race-category",
                    "code": "UNK",
                    "display": "Unknown"
                  },
                  "url": "ombCategory"
                },
                {
                  "valueString": "Unknown",
                  "url": "text"
                }
              ],
              "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race"
            },
            {
              "extension": [
                {
                  "valueCoding": {
                    "system": "http://hl7.org/fhir/us/core/ValueSet/omb-ethnicity-category",
                    "code": "UNK",
                    "display": "Unknown"
                  },
                  "url": "ombCategory"
                },
                {
                  "valueString": "Unknown",
                  "url": "text"
                }
              ],
              "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity"
            },
            {
              "valueCode": "M",
              "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex"
            }
          ],
          "identifier": [
            {
              "use": "usual",
              "type": {
                "text": "EPI"
              },
              "system": "urn:oid:1.2.840.114350.1.1",
              "value": "113822"
            },
            {
              "use": "usual",
              "type": {
                "text": "EXTERNAL"
              },
              "value": "Z16282"
            },
            {
              "use": "usual",
              "type": {
                "text": "FHIR"
              },
              "value": "TDjw8.slp7UtBKr3jNvYPsSGUb0qoZMC5OEhD5iu5ticB"
            },
            {
              "use": "usual",
              "type": {
                "text": "FHIR R4"
              },
              "value": "eXoGxqgBaJuNkuahMYmiDhg3"
            },
            {
              "use": "usual",
              "type": {
                "text": "INTERNAL"
              },
              "value": "    Z16282"
            },
            {
              "use": "usual",
              "system": "urn:oid:2.16.840.1.113883.4.1"
            }
          ],
          "active": true,
          "name": [
            {
              "use": "official",
              "text": "Family Medicine Physician",
              "family": "Physician",
              "given": [
                "Family Medicine"
              ]
            },
            {
              "use": "usual",
              "text": "Family Medicine Physician",
              "family": "Physician",
              "given": [
                "Family Medicine"
              ]
            }
          ],
          "gender": "male",
          "birthDate": "1981-06-24",
          "deceasedBoolean": false,
          "managingOrganization": {
            "reference": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/Organization/e1aU.Joccsz0IPnyexqMjXw3",
            "display": "EXAMPLE ORGANIZATION"
          }
        },
        "conditions": {
          "resourceType": "Bundle",
          "type": "searchset",
          "total": 0,
          "link": [
            {
              "relation": "self",
              "url": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/Condition?patient=eXoGxqgBaJuNkuahMYmiDhg3&_include=Condition:patient&_include=Condition:patient:organization"
            }
          ],
          "entry": [
            {
              "resource": {
                "resourceType": "OperationOutcome",
                "issue": [
                  {
                    "severity": "warning",
                    "code": "processing",
                    "details": {
                      "coding": [
                        {
                          "system": "urn:oid:1.2.840.114350.1.13.861.1.7.2.657369",
                          "code": "4101",
                          "display": "Resource request returns no results."
                        }
                      ],
                      "text": "Resource request returns no results."
                    }
                  }
                ]
              },
              "search": {
                "mode": "outcome"
              }
            }
          ]
        },
        "medications": {
          "resourceType": "Bundle",
          "type": "searchset",
          "total": 2,
          "link": [
            {
              "relation": "self",
              "url": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/MedicationRequest?patient=eXoGxqgBaJuNkuahMYmiDhg3"
            }
          ],
          "entry": [
            {
              "link": [
                {
                  "relation": "self",
                  "url": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/MedicationRequest/e4jAeTRRFdgN-QzpjNRbwQkDnp1BQlqrukNdC6BvYzU43"
                }
              ],
              "fullUrl": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/MedicationRequest/e4jAeTRRFdgN-QzpjNRbwQkDnp1BQlqrukNdC6BvYzU43",
              "resource": {
                "resourceType": "MedicationRequest",
                "id": "e4jAeTRRFdgN-QzpjNRbwQkDnp1BQlqrukNdC6BvYzU43",
                "identifier": [
                  {
                    "use": "usual",
                    "system": "urn:oid:1.2.840.114350.1.13.861.1.7.2.798268",
                    "value": "1000182255"
                  }
                ],
                "status": "active",
                "intent": "order",
                "category": {
                  "coding": [
                    {
                      "system": "http://hl7.org/fhir/medication-request-category",
                      "code": "inpatient",
                      "display": "Inpatient"
                    }
                  ],
                  "text": "Inpatient"
                },
                "medicationReference": {
                  "reference": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/Medication/ehjM3OtjgdW8Bea3jIQgMFVhzPMcMELCBS5v775xzbLetNaOP5vZRG-7BUwFy1hYFzJ7rXwELBf63P6zbB4tI56DXbUTYtEZEWyFIzxmyONE3",
                  "display": "IBUPROFEN 200 MG PO TABS"
                },
                "subject": {
                  "reference": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/Patient/eXoGxqgBaJuNkuahMYmiDhg3",
                  "display": "Physician, Family Medicine"
                },
                "authoredOn": "2020-07-06T20:25:56Z",
                "recorder": {
                  "reference": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/Practitioner/eqZb1t82mP4YeBjiLldSFPQ3",
                  "display": "Family Medicine Physician"
                },
                "dosageInstruction": [
                  {
                    "extension": [
                      {
                        "valueQuantity": {
                          "value": 2,
                          "unit": "tablet",
                          "system": "http://unitsofmeasure.org",
                          "code": "{tbl}"
                        },
                        "url": "https://open.epic.com/fhir/extensions/admin-amount"
                      },
                      {
                        "valueQuantity": {
                          "value": 400,
                          "unit": "mg",
                          "system": "http://unitsofmeasure.org",
                          "code": "mg"
                        },
                        "url": "https://open.epic.com/fhir/extensions/ordered-dose"
                      }
                    ],
                    "timing": {
                      "repeat": {
                        "boundsPeriod": {
                          "start": "2020-07-06T20:25:32Z"
                        },
                        "frequency": 1,
                        "period": 6,
                        "periodUnit": "h"
                      },
                      "code": {
                        "text": "Q6H PRN"
                      }
                    },
                    "asNeededBoolean": true,
                    "route": {
                      "coding": [
                        {
                          "system": "urn:oid:1.2.840.114350.1.13.861.1.7.4.798268.7025",
                          "code": "15",
                          "display": "Oral"
                        },
                        {
                          "system": "http://snomed.info/sct",
                          "code": "260548002",
                          "display": "Oral"
                        }
                      ],
                      "text": "Oral"
                    },
                    "doseQuantity": {
                      "value": 400,
                      "unit": "mg",
                      "system": "http://unitsofmeasure.org",
                      "code": "mg"
                    }
                  }
                ]
              },
              "search": {
                "mode": "match"
              }
            },
            {
              "link": [
                {
                  "relation": "self",
                  "url": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/MedicationRequest/es5s.w.VqTbI-3sTtgDY0f.oJSZ8leYTXykPvcBCZrWQ3"
                }
              ],
              "fullUrl": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/MedicationRequest/es5s.w.VqTbI-3sTtgDY0f.oJSZ8leYTXykPvcBCZrWQ3",
              "resource": {
                "resourceType": "MedicationRequest",
                "id": "es5s.w.VqTbI-3sTtgDY0f.oJSZ8leYTXykPvcBCZrWQ3",
                "identifier": [
                  {
                    "use": "usual",
                    "system": "urn:oid:1.2.840.114350.1.13.861.1.7.2.798268",
                    "value": "1000182291"
                  }
                ],
                "status": "active",
                "intent": "order",
                "category": {
                  "coding": [
                    {
                      "system": "http://hl7.org/fhir/medication-request-category",
                      "code": "inpatient",
                      "display": "Inpatient"
                    }
                  ],
                  "text": "Inpatient"
                },
                "medicationReference": {
                  "reference": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/Medication/eIBy3xFZsuXmpUpNLdojkXC2jhRNQyq0NyjvMBlrASIS4dl.7I3naftOtzE1KVToZwnuJ5ty7Zonwkhzz9kHIeEYjUV102TvDEv7zaGJFkKQ3",
                  "display": "MITOMYCIN 40 MG IV SOLR"
                },
                "subject": {
                  "reference": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/Patient/eXoGxqgBaJuNkuahMYmiDhg3",
                  "display": "Physician, Family Medicine"
                },
                "authoredOn": "2020-07-06T20:40:24Z",
                "recorder": {
                  "reference": "https://example.com/interconnect-instance-oauth/api/FHIR/R4/Practitioner/eqZb1t82mP4YeBjiLldSFPQ3",
                  "display": "Family Medicine Physician"
                },
                "dosageInstruction": [
                  {
                    "extension": [
                      {
                        "valueQuantity": {
                          "value": 10,
                          "unit": "mg",
                          "system": "http://unitsofmeasure.org",
                          "code": "mg"
                        },
                        "url": "https://open.epic.com/fhir/extensions/ordered-dose"
                      }
                    ],
                    "timing": {
                      "repeat": {
                        "boundsPeriod": {
                          "start": "2020-07-06T23:00:00Z"
                        },
                        "frequency": 2,
                        "period": 1,
                        "periodUnit": "d"
                      },
                      "code": {
                        "text": "0900 & 1800"
                      }
                    },
                    "asNeededBoolean": false,
                    "route": {
                      "coding": [
                        {
                          "system": "urn:oid:1.2.840.114350.1.13.861.1.7.4.798268.7025",
                          "code": "11",
                          "display": "Intravenous"
                        }
                      ],
                      "text": "Intravenous"
                    }
                  }
                ]
              },
              "search": {
                "mode": "match"
              }
            }
          ]
        }
      },
      "extension": {
        "com.epic.cdshooks.request.bpa-trigger-action": "18",
        "com.epic.cdshooks.request.cds-hooks-specification-version": "1.0",
        "com.epic.cdshooks.request.fhir-version": "R4",
        "com.epic.cdshooks.request.criteria-id": "2852",
        "com.epic.cdshooks.request.epic-version": "9.4",
        "com.epic.cdshooks.request.cds-hooks-implementation-version": "1.0",
        "com.epic.cdshooks.request.cds-hooks-su-version": "0",
        "internal.epic.cdshooks.request.epic-emp-id": "17805"
      }
    }
    

    CDS Hooks Response

    Upon receiving a CDS Hooks request from Epic, a CDS Service should quickly and synchronously respond with a CDS Hooks response. Optionally, with the OAuth 2.0 access token provided in the fhirAuthorization attribute in the request, the CDS Service may query Epic's FHIR server to learn additional information before responding.

    Upon evaluating the CDS Hooks request (including the relevant information contained in the request, and any data retrieved) according to your app’s business logic, your application responds. This response determines whether content is displayed to end users and what that content should be. Possible types of content include one or more of:

    • textual information
    • suggest actions to be taken such as adding diagnoses
    • links to your application or other web sites

    Suggestion or app launch?

    Generally, launching and interacting with an app is more time-consuming and therefore more disruptive to a user's workflow than returning a suggestion. If your CDS recommendations can be fully determined with only the information provided in the CDS Hooks request and other APIs, you should return these suggestions in the CDS Hooks response.

    If you are unable to determine recommendations with only the information from the CDS Hooks request and other APIs, a link to launch an app can enable additional user interaction. This 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.

    In the future, Epic may enable 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. This auto-launch feature is not currently supported by Epic's CDS Hooks implementation.

    Actions

    In the CDS Hooks response, a CDS Service can suggest suggestions, which include one or more actions. An action creates or deletes a FHIR resource. For example, a CDS Service can recommend that a problem be added to the current patient’s problem list by returning a FHIR Condition resource and a card.suggestion.action.type of “create”. The list of suggestions supported by Epic are listed in the API library: https://fhir.epic.com/Specifications (search for CDS Hooks).

    If using the MedicationRequest, ServiceRequest, or ProcedureRequest resources to create an unsigned order in Epic, order overrides are NOT set with information from the service response, even if additional order details are specified. All order details come from the default values defined on the medication record, procedure record, or preference list. To use these CREATE resources to specify a preference list item, specify the system as "urn:com.epic.cdshooks.action.code.system.preference-list-item". The code, assigned while building the preference list, will be the corresponding key used to identifty a particular order on the preference list.

    HTTP Response Fields

    Field

    Description

    cards

    An array of cards that provide any of the following:

    • Information
      • Text to be shown to the user, optionally formatted as GitHub-flavored markdown, or with html markup if also providing an Epic-specific extension defining the content type.
    • Suggestions
    • Work with the health system to map the problem list items, encounter diagnoses, medications, procedures and/or multi-order sets.

      • Containing CREATE actions for Condition resources, specifically Problems, or Encounter Diagnosis.
      • Medication single order follow-up suggestions with CREATE actions using MedicationRequest resources
      • Medication single order follow-up suggestions with DELETE actions using MedicationRequest resources
      • Procedure single order follow-up suggestions with CREATE actions using ProcedureRequest (STU3) or ServiceRequest (R4) resources
      • Procedure single order follow-up suggestions with DELETE actions using ProcedureRequest (STU3) or ServiceRequest (R4) resources
      • Order Set, SmartSet, Pathway or Express Lane multiple order follow-up suggestions with CREATE actions using ProcedureRequest (STU3) or ServiceRequest (R4) resources
    • Links
      • Reference links do not require any additional configuration
      • SMART App links need to be added to the allowlist in Epic

    systemActions

    Epic does not currently support system actions

    Card Attributes

    Epic-specific nuances regarding Card attributes:

    Field

    Description

    indicator

    The info, warning, and critical values can be mapped to Epic-specific values by the Epic application team for display.

    selectionBehavior

    Only the value "any" is currently supported.

    detail

    A CDS Service may return content as mere plain text, or as GitHub flavored markdown.

    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. You should always populate this field with a unique identifier (such as an alphanumeric guid).

    Suggestion Attributes

    Epic-specific nuances regarding Suggestions and Action attributes:

    Field

    Description

    label

    This field is not used, but is required.

    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 BPA. 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.

    Supported Extensions

    Extension

    Description

    com.epic.cdshooks.card.detail.content-type

    If this is not set, or set to "text/markdown" or "text/markdown; variant=GitHub", Epic will treat it as GitHub Flavored Markdown
    Other options are "text/plain", "text/html", and "text/markdown; variant=CommonMark".

    Basic Example HTTP Response with Card

    {
      "cards": [
        {
          "summary": "Testing Informational Card",
          "detail": "* Suspected colorectal cancer. \n\n* Please add to patient's diagnoses.\n\nSuspected Bladder cancer. Please add to patient's diagnoses.",
    	  "indicator": "critical",
          "source": {
            "topic": {
              "code": "examplecode1"
            }
          },	  
        },]
    }
    

    Example HTTP Response for no decision support needed

    {
      "cards": []
    }
    

    Example HTTP Response with Links, Override Reasons, and Suggestions

    {  
       "cards":[  
    	  {  
             "summary":"Example",
             "indicator":"info",
             "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"
    					}
    				  },
                   },
                ]}            
    		]
          },
    ]
    }
    

    Feedback

    When configuring your CDS Hooks service during implementation, you can choose to receive feedback immediately or to receive it in a batch. If you use the batch format, the interval at which you receive feedback is defined through configuration at the implementing organization. By default, feedback is sent to your CDS Hooks service endpoint with “/feedback” appended.

    A UUID must be sent in the CDS Hooks service response to uniquely identify cards or suggestions. If a UUID is not sent for these fields, the CDS Hooks client does not trigger feedback because the feedback can't be associated with a particular card or suggestion the CDS Hooks service provided.

    Feedback Message Demonstrating Overridden Card

    {"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 Accepted Suggestion

    {"feedback": [{
                "acceptedSuggestions": [{
                        "id": "replaceWithGUID"
                    }
                ],
                "card": "123456",
                "outcome": "accepted",
                "outcomeTimestamp": "2022-05-19T19:48:34Z"}]}

    Gotchas

    Event Notification Streaming

    CDS Hooks should be used only to trigger real-time, clinician-facing decision support. If your service never returns clinician-facing content, you should not use CDS Hooks.

    If your use case requires notifications when an event occurs, you should use an event-based interface. For more information, refer to the following topic: https://open.epic.com/Interface/HL7v2. If your use case requires context synchronization, you should use FHIRcast.

    FHIR Version

    When you register your CDS service with Epic’s authorization server infrastructure, the default FHIR version that you specify determines the FHIR version of resources in the CDS Hooks request, such as those in context or prefetch, as well as the specific fhirServer URL sent in the prefetch.

    Required OAuth 2.0 Scopes

    • Context.draftOrders is only sent to a CDS service that’s authorized for the OAuth 2.0 scopes corresponding to the CDS Hooks-specific FHIR order resource (for example, MedicationRequest (Unsigned Order)).
    • Similarly, a CDS service must be authorized for scopes corresponding to any resources requested as part of prefetch.

    Pre-Registration of Links and SMART Apps

    As a security mechanism, SMART app link URLs returned within CDS Hooks cards must be pre-registered with the CDS client. SMART app URLs not pre-registered won’t appear to the user.

    User Context

    Application User Context

    Epic defines three different user contexts for an application:

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

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

    Choosing a User Context

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

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

    Multi-Context Applications

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

    Limit APIs to the Required Application

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

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

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

    Limit Use of Backend Apps

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

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

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

    Use Cases for Backend Apps

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

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

    Creating a Multi-Context App with Epic on FHIR

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

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

    Epic Community Member Downloads

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

    API Supported User Types

    Overview

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

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

    User Type Validation

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

    When Does an API Support a Given User Type?

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

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

    API and User Type in Your Application

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

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

    Should I Edit My App's User Context?

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

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

    Additional User Security Validation

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

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

    We use cookies from Google Analytics to improve our website. By accepting, you will receive these cookies from Epic on FHIR. Decline if you wish to use Epic on FHIR without these cookies. Read our privacy policy here.

    Frequently Asked Questions (FAQs)
    Contact
    Terms of Use
    Privacy Policy
    1979 Milky Way, Verona, WI 53593
    © 1979-2023 Epic Systems Corporation. All rights reserved. Protected by U.S. patents. For details visit www.epic.com/patents.
    HL7®, FHIR® and the FLAME mark are the registered trademarks of HL7 and are used with the permission of HL7. The use of these trademarks does not constitute a product endorsement by HL7.
    Epic, open.epic, and Epic on FHIR are trademarks or registered trademarks of Epic Systems Corporation.

    Due to inactivity you will be logged out in 60 seconds.

    Saving
    Loading
    False