Executive Summary
A healthcare data platform company needed to integrate with Epic, Cerner (Oracle Health), and athenahealth — covering 95% of the US EHR market — to provide unified patient data access for their clinical analytics product. Building direct integrations with each EHR meant navigating three different FHIR implementations, three authentication protocols, incompatible data models, and resource coverage gaps that varied dramatically across vendors.
We built a FHIR Facade layer that presents a single, clean FHIR R4 API to client applications while handling the complexity of communicating with three different EHR backends. The facade normalizes data representations, translates authentication flows, fills resource coverage gaps, manages pagination differences, and provides a unified developer experience through a single SDK and documentation portal.
The result: 3 EHRs behind one API, covering 95% of the US EHR installed base. Integration time for new client applications dropped from 3 months per EHR to 2 weeks total. The facade handles 1.2 million API calls per month with 142ms average latency and 99.94% uptime.
The Problem: Three EHRs, Three Different Worlds
On paper, all three EHRs support FHIR R4. In practice, the differences are substantial and create enormous integration friction for any application that needs to work across multiple EHR platforms.
Authentication Fragmentation
Each EHR implements authentication differently, and the differences go far beyond surface-level protocol variations:
- Epic: SMART on FHIR with JWT-based backend services authentication. Requires app registration through Epic's App Orchard marketplace, separate credentials per healthcare organization, and supports both public and confidential client flows. Token refresh behavior varies by Epic version.
- Cerner (Oracle Health): OAuth 2.0 with custom extensions. Authorization and token endpoints are organization-specific, not discoverable via well-known endpoints in older implementations. Requires registration through Cerner's Code console with a separate approval process that takes 2-4 weeks per organization.
- athenahealth: API key-based authentication with a proprietary token exchange mechanism. Uses a custom authorization flow that does not conform to SMART on FHIR. Rate limiting is per-key rather than per-session, creating challenges for multi-tenant deployments.
For a client application needing to connect to all three, this means implementing three separate authentication flows, managing three credential stores, and handling three different token lifecycle patterns.
Data Model Inconsistencies
While FHIR R4 defines a standard resource model, each EHR's implementation diverges in significant ways:
- Patient resource: Epic returns names as an array of HumanName objects with separate family/given fields. Cerner sometimes returns a formatted name string instead. athenahealth returns firstName/lastName in a proprietary wrapper that requires extraction before FHIR mapping.
- Observation resource: Vital signs coding varies — Epic uses LOINC codes consistently, Cerner mixes LOINC with proprietary codes, and athenahealth often omits code systems entirely, relying on display text.
- Encounter resource: Status values differ (Epic uses the FHIR standard value set, Cerner adds custom statuses, athenahealth uses a different status taxonomy), and the level of detail in encounter reasons varies dramatically.
- Extensions: Each EHR adds proprietary extensions. Epic adds 15+ custom extensions to the Patient resource alone. Cerner and athena have their own extension sets. Client apps that hard-code to one EHR's extension pattern break on others.
Resource Coverage Gaps
Not all EHRs expose all FHIR resources, and the depth of support varies:
| FHIR Resource | Epic | Cerner | athenahealth |
|---|---|---|---|
| Patient | Full | Full | Full |
| Encounter | Full | Full | Partial (no reason) |
| Observation (Vitals) | Full | Full | Partial (limited codes) |
| Condition | Full | Full | Partial (active only) |
| Procedure | Full | Full | None (proprietary API) |
| MedicationRequest | Full | Partial | Partial |
| AllergyIntolerance | Full | Full | Full |
| DiagnosticReport | Full | Partial | None |
| Immunization | Full | Full | Partial |
| DocumentReference | Full | Full | None |
| CarePlan | Full | Partial | None |
| Goal | Full | None | None |
Without a facade, client applications must handle 12 different "if EHR is X, then..." branches for every resource access — creating fragile, hard-to-maintain code.
Solution: The FHIR Facade Architecture
The FHIR Facade is a middleware layer that presents a single, standards-compliant FHIR R4 API surface while internally routing, translating, and normalizing requests across the three EHR backends.
Unified API Surface
Client applications interact with exactly one API — the Facade. Every FHIR resource is accessible via standard RESTful endpoints regardless of which EHR holds the data:
- Single authentication: SMART on FHIR for all clients, regardless of which backend EHR they need. The Facade handles credential translation and token management for each EHR behind the scenes.
- Consistent data model: Every response conforms to FHIR R4 with consistent use of code systems, name formats, and extension handling. No EHR-specific quirks leak through.
- Full resource coverage: All 12 core FHIR resources are available for all three EHRs. Where an EHR lacks native FHIR support, the Facade bridges the gap using the EHR's proprietary APIs.
- Standard pagination: Consistent _count and _offset behavior, abstracting Epic's Bundle-based pagination, Cerner's cursor-based approach, and athena's page-number system.
EHR-Specific Adapters
Each EHR backend is abstracted behind a dedicated adapter that handles the vendor-specific logic:
Epic Adapter: Implements SMART on FHIR backend services auth with JWT assertion. Handles Epic's custom search parameters, maps Epic-specific extensions to standard fields where possible, and manages Epic's bulk data export for large dataset access. Supports both Epic on FHIR (production) and Epic Sandbox endpoints.
Cerner Adapter: Manages OAuth2 token exchange with Cerner's organization-specific authorization servers. Translates Cerner's custom status codes to FHIR standard value sets, handles Cerner's proprietary code systems by mapping to LOINC/SNOMED where lookup tables exist, and works around known Cerner FHIR API limitations.
athenahealth Adapter: Bridges athena's proprietary REST API to FHIR R4 resources. This adapter does the heaviest lifting — converting athena's flat JSON responses into nested FHIR resource structures, mapping athena's proprietary identifiers to FHIR identifier systems, and synthesizing FHIR resources (Procedure, DiagnosticReport, DocumentReference) from athena's proprietary endpoints that have no FHIR equivalent.
Data Normalization Engine
The normalization engine is the core of the Facade's value proposition. It ensures that regardless of which EHR sourced the data, the output is indistinguishable:
- Code system translation: Maps vendor-specific codes to standard terminologies (LOINC, SNOMED CT, RxNorm, ICD-10-CM). Maintains a translation table of 4,700+ code mappings updated quarterly.
- Name and address normalization: Standardizes patient demographics across EHR format variations. Handles edge cases like hyphenated names, suffixes, and international address formats.
- Temporal normalization: Standardizes timestamps across EHRs (Epic uses UTC, Cerner uses local time with timezone offset, athena uses local time without offset). All output timestamps are ISO 8601 with UTC offset.
- Extension handling: Strips vendor-specific extensions by default, maps commonly-used extensions (race, ethnicity, birth sex) to US Core profile extensions, and provides an opt-in mechanism for clients that need raw vendor extensions.
Architecture and Technical Implementation
Technology Stack
| Component | Technology | Purpose |
|---|---|---|
| API Gateway | Kong + custom plugins | Rate limiting, auth, routing, request logging |
| Facade Service | Node.js 20, TypeScript | FHIR resource routing and orchestration |
| EHR Adapters | TypeScript, Axios | Vendor-specific API integration |
| Normalization | Custom FHIR mapper (TS) | Data translation and standardization |
| Cache Layer | Redis Cluster | Response caching, token caching, rate limit tracking |
| Database | PostgreSQL 15 | Connection configs, code mappings, audit logs |
| Search | Elasticsearch 8 | Cross-EHR patient matching, full-text search |
| Auth | SMART on FHIR (Facade-issued) | Unified client authentication |
| Testing | Inferno FHIR testing suite | FHIR conformance validation |
| Infrastructure | Kubernetes (EKS), Terraform | Auto-scaling, infrastructure-as-code |
Request Flow
A typical API request flows through the Facade in the following sequence:
- Client request: GET /fhir/Patient?name=Smith&_count=10 with SMART on FHIR bearer token
- API Gateway: Validates token, checks rate limits, logs request, routes to Facade service
- Router: Identifies target EHR(s) based on client configuration. Can route to a single EHR or fan out to all three for cross-EHR patient matching.
- Cache check: Redis lookup for recent identical queries. Cache hit returns in under 5ms. Cache TTL varies by resource type (Patient: 5min, Observation: 1min, Encounter: 2min).
- Adapter dispatch: The appropriate EHR adapter translates the FHIR request to the vendor-specific format, manages authentication, and sends the request.
- Response normalization: The raw EHR response is mapped through the normalization engine, producing a clean FHIR R4 resource.
- Response: Standard FHIR Bundle returned to client with consistent structure, pagination, and metadata.
Handling Resource Coverage Gaps
Where an EHR lacks native FHIR support for a resource, the adapter synthesizes it from proprietary APIs:
- athena Procedure: Constructed from athena's /chart/procedures proprietary endpoint. Maps athena's procedure codes to SNOMED CT, extracts dates and performer information, and assembles a valid FHIR Procedure resource.
- athena DiagnosticReport: Built from athena's /chart/results endpoint. Aggregates lab results into FHIR DiagnosticReport resources with proper category coding and reference to underlying Observation resources.
- Cerner CarePlan: Assembled from Cerner's proprietary care planning APIs. Maps care plan activities to FHIR CarePlan.activity structures with proper status and category coding.
- Cerner/athena Goal: Neither exposes Goal natively. The Facade returns a 404 with an OperationOutcome explaining the limitation, rather than silently returning empty results.
Results: Unified Access at Scale
Key Performance Metrics
| Metric | Before (Direct) | After (Facade) | Improvement |
|---|---|---|---|
| Integration Time per EHR | 3 months | 2 weeks (total) | -94% |
| US EHR Market Coverage | 33% (1 EHR) | 95% (3 EHRs) | 3x coverage |
| APIs to Learn | 3 different APIs | 1 unified API | -67% |
| Auth Flows to Implement | 3 different flows | 1 SMART on FHIR | -67% |
| Avg API Latency | 280ms (direct) | 142ms (cached) | -49% |
| Monthly API Calls | N/A | 1.2M | Scalable platform |
| Uptime | Varies by EHR | 99.94% | Consistent SLA |
| Integration Cost per Client | $60K per EHR | $12K total | -93% |
| Code Mapping Coverage | Manual per integration | 4,700+ standard mappings | Reusable |
Developer Experience Impact
Before the Facade, every client application team needed at least one developer with deep EHR integration expertise — understanding SMART on FHIR, OAuth2 nuances, FHIR resource variations, and vendor-specific quirks. This expertise was expensive ($180-220K/year for senior EHR integration engineers) and scarce.
With the Facade, any developer familiar with REST APIs can integrate in 2 weeks using standard FHIR documentation. The SDK provides type-safe clients in Python, JavaScript, Java, and C#. The sandbox environment includes synthetic patient data across all three EHRs for testing without needing actual EHR access.
Client Deployment Scale
Within 8 months of the Facade going live, 14 client applications were connected — clinical analytics dashboards, population health tools, patient engagement apps, and care coordination platforms. Each integration took an average of 11 days, compared to the previous 3-month timeline for direct EHR integrations.
Implementation Timeline
| Phase | Duration | Key Activities | Milestone |
|---|---|---|---|
| Architecture and Design | Weeks 1-3 | FHIR R4 conformance analysis, adapter interface design, normalization rules | Technical design approved |
| Epic Adapter | Weeks 3-7 | SMART on FHIR auth, all 12 resources, Epic sandbox testing | Inferno test suite passing |
| Cerner Adapter | Weeks 5-9 | OAuth2 integration, code mapping, coverage gap handling | 11/12 resources operational |
| athena Adapter | Weeks 7-12 | Proprietary API bridging, FHIR resource synthesis, heavy normalization | 7/12 resources synthesized |
| Normalization Engine | Weeks 4-11 | Code system mappings, temporal normalization, extension handling | 4,700+ code mappings |
| Developer Portal | Weeks 10-13 | API docs, SDK generation, sandbox environment, onboarding guides | Portal live with sandbox |
| Production Launch | Weeks 13-16 | Load testing, security audit, monitoring, first client onboarding | Production go-live |
Lessons Learned
1. athenahealth is the Hardest to Facade
Epic and Cerner both have reasonably mature FHIR implementations. athenahealth's FHIR support is significantly thinner, requiring the most adapter code — roughly 60% of total adapter development effort went into athena despite it being the smallest EHR of the three. Budget extra time for vendors with limited FHIR maturity.
2. Code System Translation is Never "Done"
Our initial code mapping covered 2,800 entries. Within 3 months of production use, we discovered 1,900 additional unmapped codes from real clinical data. We built an automated pipeline that flags unmapped codes, queues them for manual review, and deploys new mappings weekly. Code translation is an ongoing operational task, not a one-time development effort.
3. Caching Strategy Must Be Resource-Aware
Aggressive caching (30-minute TTL) worked well for Patient demographics but caused stale data issues for Observations and Encounters that change frequently. We implemented resource-specific TTLs: Patient (5min), Encounter (2min), Observation (1min), and made TTLs configurable per client based on their freshness requirements.
4. Build the Developer Portal First, Not Last
We initially planned the developer portal as the final phase. Moving it earlier would have caught API design issues sooner — when we documented the API formally, we discovered inconsistencies in error response formats and search parameter behavior that required refactoring.
5. EHR Version Drift is a Real Problem
EHRs update their FHIR implementations quarterly. Epic's February 2024 update changed extension namespaces, breaking our extension stripping logic. We now run automated regression tests against all three EHR sandboxes weekly and subscribe to vendor release notes for early warning.
Frequently Asked Questions
Does the FHIR Facade support write operations, or is it read-only?
The Facade supports both read and write operations for resources where the underlying EHR allows it. Write support includes creating and updating Patient, Encounter, Observation, Condition, and MedicationRequest resources. Each write operation is translated to the target EHR's specific format and validated against the EHR's business rules before submission. Write operations that are not supported by a specific EHR (such as creating a Procedure in athenahealth via FHIR) return a clear OperationOutcome explaining the limitation.
How does the Facade handle patient matching across multiple EHRs?
Cross-EHR patient matching uses a probabilistic matching algorithm that compares demographics (name, DOB, gender, address) and identifiers (MRN, SSN last-4 where available). The matcher runs Elasticsearch queries against a unified patient index and returns confidence scores. Matches above 95% confidence are auto-linked; matches between 80-95% are flagged for manual review. The system does not create a master patient index — it maintains a lightweight linking table that maps EHR-specific patient IDs.
What happens when one EHR is down but others are available?
The Facade implements circuit breaker patterns per EHR connection. When an EHR backend becomes unavailable, the circuit breaker opens after 5 consecutive failures, and requests to that EHR return cached data (if available within TTL) or a clear error response indicating the specific EHR is unavailable. Other EHR backends continue operating normally. The circuit breaker automatically retries every 30 seconds and closes when the EHR recovers. Client applications receive degraded-mode headers indicating which EHRs are currently available.
Can the Facade be extended to support additional EHRs beyond Epic, Cerner, and athena?
Yes — the adapter pattern was designed for extensibility. Adding a new EHR requires implementing the adapter interface (approximately 4-6 weeks for an EHR with mature FHIR support, 8-12 weeks for proprietary API bridging), creating code mappings for the new vendor's terminology, and testing with the Inferno FHIR conformance suite. The normalization engine and API surface remain unchanged. We have preliminary adapters in development for MEDITECH Expanse and eClinicalWorks.



