Skip to main content

Epic Registration

Epic's developer sandbox is free and requires no approval process. You can have a working non-production client ID in about 15 minutes โ€” the only wait is a 1-hour sync window after creating your app.


What you get for freeโ€‹

  • A non-production client ID accepted by Epic's sandbox FHIR server
  • Access to Epic's non-production FHIR R4 API at fhir.epic.com
  • Pre-loaded synthetic test patients (no real PHI)
  • Epic LaunchPad โ€” a browser tool that simulates Hyperspace clicking your app
  • Unlimited sandbox API calls at no cost
Production is separate

The non-production client ID only works against Epic's sandbox servers. Going live in a real hospital requires App Orchard registration and hospital IT approval โ€” a separate process covered in the Production Checklist.


Step 1 โ€” Create a developer accountโ€‹

  1. Go to fhir.epic.com
  2. Click Sign In โ†’ Sign Up
  3. Fill in your name, email, and organisation
  4. Check your inbox and click the verification link

The account is free and does not require any company affiliation.


Step 2 โ€” Create your appโ€‹

  1. Sign in to fhir.epic.com
  2. Click Build Apps in the top navigation
  3. Click Create (top right)

Fill in the form:

FieldValue
Application NameSMART FHIR Client (or your own name)
Application AudienceClinicians or Administrative Users
Application TypePatient Facing or Clinician Facing โ€” choose Clinician Facing for EHR launch
SMART on FHIR VersionSMART App Launch 2.0
Redirect URIshttp://localhost:8080/callback

Click Save.

Add multiple redirect URIs now

You can register several URIs at once โ€” add both your local and any staging redirect URIs before saving:

http://localhost:8080/callback
https://staging.yourapp.com/callback

Step 3 โ€” Note your client IDsโ€‹

After saving, the page shows two client IDs:

Non-Production Client ID:  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Production Client ID: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy

Copy the Non-Production Client ID. This is the value for EPIC_CLIENT_ID.

Keep these safe

Client IDs are not secret (they appear in browser network traffic), but treat the non-production ID as environment-specific config โ€” not hardcoded in source files. Use environment variables or a secrets manager.


Step 4 โ€” Wait for sandbox syncโ€‹

After creating an app or changing its configuration, Epic's sandbox takes up to 1 hour to propagate the changes. During this window, authorisation requests will fail with invalid_client.

One-time wait

This sync only happens after you create or update the app. Subsequent test launches work immediately once the first sync completes.

You can check sync status by attempting a discovery request:

curl "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/.well-known/smart-configuration"

If it returns a JSON document with authorization_endpoint, the server is ready.


Step 5 โ€” Configure the appโ€‹

Export your client ID and run with the Epic profile:

export EPIC_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

mvn spring-boot:run -Dspring-boot.run.profiles=epic

Confirm the app started and config was accepted:

curl http://localhost:8080/health
# โ†’ {"status":"UP","app":"smart-fhir-client","epicClientId":"xxxxxxxx..."}

Step 6 โ€” Test with LaunchPadโ€‹

Epic LaunchPad simulates a clinician clicking your app inside Hyperspace. It generates a real launch token and redirects your browser to your app's /launch endpoint, completing the full SMART EHR launch flow.

  1. Go to open.epic.com/launchpad
  2. Select the SMART on FHIR App Launch tab
  3. In Launch URL, enter: http://localhost:8080/launch
  4. In FHIR Server, select R4 and choose any sandbox patient
  5. Click Launch

Your browser will:

  1. Be redirected to http://localhost:8080/launch?iss=...&launch=...
  2. Be redirected to Epic's authorisation page (auto-approved in sandbox)
  3. Return to http://localhost:8080/callback?code=...&state=...
  4. Land on http://localhost:8080/ โ€” the dashboard with real sandbox FHIR data

What the logs show during a successful launchโ€‹

INFO  SmartLaunchController  - EHR launch received โ€” iss=https://fhir.epic.com/...
INFO SmartDiscoveryService - Fetching SMART configuration from ISS: https://fhir.epic.com/...
INFO SmartDiscoveryService - SMART configuration cached โ€” authEndpoint=..., tokenEndpoint=...
DEBUG SmartAuthRequestBuilder - PKCE generated and stored in session โ€” challenge method: S256
INFO SmartLaunchController - Redirecting to authorize endpoint โ€” mode=ehr, state=...
INFO SmartCallbackController - SMART EHR launch complete โ€” patient=eXXXXX, encounter=eYYYYY, scope=launch openid...
INFO IdTokenValidator - id_token validated โ€” subject=eProvider123, fhirUser=Practitioner/eProvider123
INFO UiController - Fetching Patient/eXXXXX

Verifying FHIR data accessโ€‹

After a successful launch, test each API endpoint:

# Session info (always safe โ€” works even before a patient is selected)
curl http://localhost:8080/api/session

# Clinician profile (requires openid scope)
curl http://localhost:8080/api/me

# Patient demographics
curl http://localhost:8080/api/patient

# Active conditions
curl http://localhost:8080/api/conditions

# Active medications
curl http://localhost:8080/api/medications

# Combined summary
curl http://localhost:8080/api/summary

Or use the UI directly at http://localhost:8080/.


Epic sandbox test patientsโ€‹

Epic's sandbox includes several pre-loaded synthetic patients with realistic clinical data. The most commonly used for testing:

PatientFHIR IDNotable data
Camila LopezerXuFYUfucBZaryVksYEcMg3Conditions, medications
Derrick Lineq081-VQEgP8drUUqCWzHfw3Allergies, encounters
Jason ArgonautTbt3KuCY0B5PSrJvCu2j-PlKMinimal data โ€” good for blank-slate testing

You can find the full patient list at fhir.epic.com/Documentation#patientlist.


Registered scopes in App Orchardโ€‹

The scopes you configure in application.yml must also be registered in your app's App Orchard entry. If you request a scope that isn't registered, Epic silently drops it from the token response โ€” no error, just missing data.

To update registered scopes:

  1. Go to fhir.epic.com โ†’ Build Apps โ†’ your app
  2. Find the Clinical Data section
  3. Check each FHIR resource and its allowed operations

The scope names in App Orchard correspond to SMART v2 scope strings:

App Orchard resourceSMART v2 scope
Patient โ€” Readpatient/Patient.rs
Condition โ€” Readpatient/Condition.rs
MedicationRequest โ€” Readpatient/MedicationRequest.rs
OpenIDopenid
FHIR UserfhirUser

After updating scopes, wait up to 1 hour for the sandbox to sync.


Troubleshootingโ€‹

invalid_client error on the callbackโ€‹

Cause: The client ID in EPIC_CLIENT_ID doesn't match the registered app, or the sync hasn't completed yet.

Fix:

  • Verify EPIC_CLIENT_ID matches the Non-Production Client ID shown in fhir.epic.com โ†’ your app
  • If you recently created or updated the app, wait up to 1 hour for the sandbox sync

invalid_redirect_uri errorโ€‹

Cause: The redirect-uri in application.yml doesn't exactly match what's registered.

Fix: Check for trailing slashes, case differences, and port numbers. The comparison is character-for-character. Update fhir.epic.com โ†’ your app โ†’ Redirect URIs.

App redirects but patient is null in the token responseโ€‹

Cause: The launch scope was not registered in App Orchard, so Epic didn't include patient context in the token.

Fix: In App Orchard โ†’ your app โ†’ Clinical Data, ensure EHR Launch or the equivalent launch scope is checked.

id_token validation failed โ€” JWKS key not foundโ€‹

Cause: The JWKS endpoint derivation in IdTokenValidator couldn't locate the signing key.

Fix: This is a non-fatal warning โ€” FHIR data access still works. The validator logs a warning and continues. Full RS256 signature verification requires adding nimbus-jose-jwt โ€” see the OIDC docs for the upgrade path.

Session expires after launch but before the dashboard loadsโ€‹

Cause: Session timeout set too short, or the SMART flow took more than MAX_LAUNCH_AGE_SECONDS (300 seconds / 5 minutes) between the /launch and /callback steps.

Fix: Epic LaunchPad sometimes auto-approves quickly, but if you interact with it manually, it can take a few minutes. The 5-minute launch window is configurable in SmartLaunchController.MAX_LAUNCH_AGE_SECONDS.


Next stepsโ€‹