Damaros
clinical investigation, rewired.
secure session© 2026 Damaros
Epic FHIR Integration

Production Integration Documentation

Deployment-facing technical reference aligned to the Damaros product codebase and Epic SMART on FHIR Backend Services (server-to-server). Covers authentication, JWKS publication, least-privilege reads, deterministic eligibility on ingested FHIR only (no protocol-text or external assistance on that execution path), optional protocol-text tooling documented separately under data-flow controls, access control, and why Epic write scopes are not requested at initial go-live. Cohort ingest and screening use Backend Services only; end-user SMART on FHIR EHR launch is a separate integration pattern and is outside this submission’s connector scope.

Platform Overview

Damaros is a clinical trial execution platform that performs deterministic, protocol-driven eligibility evaluation against Electronic Health Record data.

The system converts trial protocols into structured evaluation logic, screens approved cohorts against inclusion and exclusion criteria, and produces criterion-level PASS, REVIEW, or FAIL outcomes with evidence for coordinator review, institutional oversight, and audit reconstruction.

The production Epic integration path is SMART on FHIR Backend Services: the API/worker exchanges a signed JWT for an access token (client_credentials + private_key_jwt, RFC 7523). Access is read-only by default. Epic access tokens are not issued to browsers and are not stored in the web session.

Integration Architecture

APPLICATION_AUDIENCEBackend Systems (Epic-registered Backend Services app)
PRIMARY_OAUTH_MODELclient_credentials + JWT client assertion to Epic token URL; PKCE does not apply
USER_SMART_LAUNCHNot used for cohort sync or screening ingest; out of scope for the Backend Services submission described here (repository contains a non-production reference stub only)
FHIR_VERSIONR4
USE_CASEClinical trial eligibility screening, evidence retrieval, cohort review, audit reconstruction
AUTH_STANDARDSMART on FHIR Backend Services (RFC 7523)
AUTH_METHODJWT client assertion (urn:ietf:params:oauth:client-assertion-type:jwt-bearer)
JWT_ALGORITHMRS384 (override via DAMAROS_EPIC_JWT_ALGORITHM if Epic requires)
KEY_SIZERSA-2048 (or per Epic registration)
TOKEN_TTLAssertion exp within 300 seconds of iat; access token TTL from Epic response; in-memory cache only
DEFAULT_ACCESSRead-only FHIR; no EHR write-back unless explicit env gates below
TOKEN_ENDPOINTDAMAROS_EPIC_TOKEN_URL (alias: DAMAROS_EPIC_OAUTH_TOKEN_URL)
FHIR_BASEDAMAROS_EPIC_FHIR_BASE (alias: DAMAROS_EPIC_FHIR_BASE_URL)

Environment Separation & JWKS

NON_PRODUCTIONSeparate Epic sandbox / non-prod client ID, signing keypair, and JWKS URL
PRODUCTIONIndependent Epic production client ID, signing keypair, and JWKS URL
JWKS_HOSTINGPublic JWKS documents are served over HTTPS from www.damaros.ai (Damaros-controlled DNS and TLS). Epic registers these URLs; private signing material never appears in JWKS JSON.
JWKS_STATIC_NONPRODhttps://www.damaros.ai/.well-known/jwks-nonprod.json — public keys only; non-production Epic client kid alignment
JWKS_STATIC_PRODhttps://www.damaros.ai/.well-known/jwks-prod.json — public keys only; production Epic client kid alignment
JWKS_KID_PARITYThe kid in the hosted JWKS must match DAMAROS_EPIC_JWT_KID for the environment that signs client assertions—Epic rejects assertions when header kid does not resolve to a published JWK. Non-prod and prod each use their own keypair + kid + JWKS URL.
JWKS_KEY_ROTATIONPublish the next public key in JWKS alongside the incumbent so Epic always has overlapping validity during rotation—no authentication gap. Signing switches to the new private key only after Epic app registration confirms the new kid; then update DAMAROS_EPIC_JWT_PRIVATE_KEY_PATH + DAMAROS_EPIC_JWT_KID and retire the old public key when safe. Cadence follows change control, not an in-app timer.
JWKS_KEY_ROTATION_OWNERSHIPCustomer-operated deploy (on-prem / customer cloud): customer PKI or secret-manager owners execute rotation with Damaros runbook; Damaros does not hold production private keys unless contracted as operator. Damaros-operated managed service: Damaros platform SRE performs rotation in agreed windows, updates customer-visible JWKS, and notifies security contacts—customer retains Epic app registration approval authority.
JWKS_VIA_APIOptional: set DAMAROS_EPIC_JWKS_SERVE_ENABLED=1 on the API; Epic may register {DAMAROS_PUBLIC_BASE_URL}/v1/meta/epic_jwks.json (same PEM + kid as token signing). Use static URLs or API URL—never both for the same key without coordination.
SECRET_STORAGEPrivate signing PEM from secret manager or secure mount; never in git or client bundles
WRITE_ENV_NONPRODDAMAROS_FHIR_WRITE_ENABLED=1 required for any FHIR POST/PUT helper path
WRITE_ENV_PRODUCTIONRequires DAMAROS_FHIR_WRITE_ENABLED=1 and DAMAROS_FHIR_PRODUCTION_WRITE_ATTESTED=1 when DAMAROS_ENV is production or prod (two-step gate for hospital review)
JWKS URLs above are the canonical Epic registration endpoints. This repository may mirror the same public key material for CI or review; private keys exist only in the customer deployment or Damaros-operated secret store (never in git or browser bundles).

Authentication Flow (Backend Services)

The API builds a signed JWT (iss/sub = Epic client id, aud = token URL, jti = UUID, bounded exp), POSTs to the Epic token endpoint with grant_type=client_credentials, and caches the access token in process memory for FHIR HTTP clients. Failures surface to operators; there is no silent fallback to unauthenticated reads.

GRANT_TYPEclient_credentials
ASSERTION_TYPEurn:ietf:params:oauth:client-assertion-type:jwt-bearer
TOKEN_ENDPOINTEpic-issued URL (DAMAROS_EPIC_TOKEN_URL)
JWT_ISS / JWT_SUBEpic client ID for the active environment
JWT_AUDToken endpoint URL (per Epic / RFC 7523)
JWT_JTIUUID per assertion
TOKEN_STORAGEIn-process only; not written to disk, DB, or browser
FHIR requests use bounded retry and backoff on 429/5xx per connector configuration.

Initial requested scopes (App Orchard submission)

This table is the exact initial read scope set requested for production eligibility screening when DAMAROS_EPIC_FETCH_MEDICATION_STATEMENT is not enabled (default). It matches EpicFhirClient.fetch_patient_bundle + GET Group/{id} in §03—no write scopes, no bulk scopes, no §03A resources. Epic’s canonical scope strings may use alternate spellings (e.g. .r vs .read); the worksheet finalizes literals; DAMAROS_EPIC_SCOPE passes the agreed list at token time.

Not requested at initial submission: system/MedicationStatement.read (requires §03 opt-in flag + amendment), any §03A resource, Bulk Data / $export scopes (§03B), all write scopes (§03C).

Scope–call parity (operational): this document is the contract; deployment must keep DAMAROS_EPIC_SCOPE aligned with actual traffic—no unused scopes (reviewers compare token grants to fetch_patient_bundle + Group reads). With DAMAROS_EPIC_FETCH_MEDICATION_STATEMENT unset or 0, fetch_patient_bundle performs only GET Patient/{id}, lab-tuned Observation searches, and paginated searches for Condition, MedicationRequest, Procedure, AllergyIntolerance, and DocumentReference—exactly the eight system/*.read rows above; cohort resolution uses GET Group/{id} (system/Group.read). When the flag is 1, add only system/MedicationStatement.read to the token and the bundle. Validate in sandbox against Epic audit / proxy logs before production.

system/Patient.readPatient resource for cohort binding and demographics used in deterministic rules
system/Group.readResolve cohort membership (patient ids) for approved trial lists
system/Observation.readLaboratory observations per Epic-tuned searches (not full-chart extract)
system/Condition.readActive/problem list context for inclusion or exclusion criteria
system/MedicationRequest.readOutpatient medication orders for protocol-relevant criteria
system/Procedure.readHistorical procedures referenced by criteria
system/AllergyIntolerance.readAllergies and intolerances for safety-related criteria
system/DocumentReference.readDocument metadata (pagination); eligibility authority remains deterministic engine output, not generative text
Add-on scope (later Epic amendment only): system/MedicationStatement.read when DAMAROS_EPIC_FETCH_MEDICATION_STATEMENT=1 and clinical informatics sign-off—still read-only; not part of the initial eight-scope package.

Default Patient FHIR Reads (code-aligned)

The live patient bundle pull used for eligibility ingest includes the following R4 reads/searches. This list matches the current open-source EpicFhirClient.fetch_patient_bundle behavior and must stay in lockstep with §02C scopes and flags (zero drift).

Patient & cohort
  • Patient.ReadGET Patient/{id}
  • Group.ReadGET Group/{id} for member patient IDs (separate cohort sync path)
Observations
  • Observation.Search (laboratory)Lab-focused search with Epic-tuned parameter variants; not a full chart extract
Conditions, meds, allergies, procedures, documents
  • Condition.Searchpatient=Patient/{id}, paginated
  • MedicationRequest.Searchpatient=Patient/{id}, paginated
  • MedicationStatement.SearchOnly if DAMAROS_EPIC_FETCH_MEDICATION_STATEMENT=1 opt-in
  • Procedure.Searchpatient=Patient/{id}, paginated
  • AllergyIntolerance.Searchpatient=Patient/{id}, paginated
  • DocumentReference.SearchMetadata; patient=Patient/{id}, paginated

Bulk FHIR & other APIs

Bulk Data $export is disabled by default. Code exists for large-cohort refresh when a tenant explicitly opts in, Epic has granted the separate bulk / export scopes, and the health system documents approval. Until those gates are met, token requests do not include bulk scopes and operators do not invoke export workflows.

Bulk Data $export (off unless explicitly enabled)
  • Bulk kick-off / status / file downloadPer-tenant enablement + Epic bulk scopes + institutional sign-off; not part of initial least-privilege submission unless separately approved
Patient.$match
  • Patient.$match (POST)Available on client for site-approved payloads; not invoked by default screening hot path

Epic write scopes — not requested at initial deployment

Initial Epic registration and production go-live do not request FHIR write scopes. Eligibility screening, evidence display, and audit replay operate entirely on reads plus application database state. The product codebase retains optional POST helpers (e.g. Observation create, Subscription create) for rare site-specific workflows; those paths are hard-disabled unless multiple environment attestations are set and are out of scope for the first hospital deployment package.

  • Observation.Create (code only)Would require write scopes + DAMAROS_FHIR_WRITE_ENABLED + production attestation; not activated in initial submission
  • Subscription.Create (code only)Same write gates; not part of initial connector
A write without gates fails fast with a configuration error—never a silent no-op—so misconfiguration cannot accidentally mutate the EHR during review or pilot.

Future / site-specific scope requests (not default bundle)

The following may appear on an Epic scope worksheet for anticipated workflows. They are not part of the default patient bundle fetch today. Enable retrieval only when the protocol engine and site authorization require it.

Extended clinical context (examples)
  • Encounter, Immunization, FamilyMemberHistoryNot in default fetch_patient_bundle
  • ServiceRequest, DiagnosticReport, Specimen, BinaryNot in default bundle
  • CarePlan, CareTeamNot in default bundle
Research workflow (examples)
  • ResearchStudy, ResearchSubjectNot in default bundle

Clinical data boundary (enforcement, not policy-only)

  • Cohorts are defined by approved protocol logic or institutional configuration.
  • Patient data is retrieved through FHIR read APIs or, only when explicitly enabled, Bulk FHIR export—per site policy and §03B.
  • PHI is processed inside the institution-approved deployment boundary (VPC / tenant isolation per deploy contract).
  • Protocol assistance (outside the screening execution path): Criterion PASS / REVIEW / FAIL comes only from the deterministic engine on ingested FHIR—protocol assistance is not on that hot path. Where enabled at all, it is optional, uses non-patient protocol or synopsis text only, and sits behind deployment flags and pattern guards. Patient-facing narrative assistance stays off by default.
  • Technical controls (code-aligned): separation of protocol-facing modules from FHIR ingest paths; hard validation rejecting known PHI patterns in protocol-assistance payloads; structured audit signals on boundary violations.
  • Default egress posture for this Epic integration: no transmission of patient-identifiable data or cohort-row clinical payloads to third-party assistance endpoints for screening—the deterministic path never requires it. No patient identifiers and no cohort-linked fields are serialized into any optional external request path, even when protocol assistance is enabled—only free-standing protocol or synopsis text passes those guards. Optional outbound use of that non-patient text exists only where deployment policy explicitly enables it and network paths allow it; default hospital deployments keep it off.
  • Network enforcement (deploy-time): Helm ships deny-by-default API egress NetworkPolicy with explicit CIDR allowlists for Epic FHIR, OIDC, in-cluster Postgres/Redis, and DNS—see §08 sample. Customer CNI must enforce; IT validates with pod-level denial checks per deployment runbook.
  • Eligibility outputs are criterion-level PASS, REVIEW, or FAIL with evidence references where populated—never an ungrounded generative verdict.
  • Audit artifacts preserve source FHIR references where available for reconstruction.

Audit, Determinism, and Replay

  • Decisions are reproducible for fixed protocol version, cohort snapshot, evidence snapshot, and evaluation_as_of.
  • Every patient outcome is recorded at criterion level with PASS, FAIL, or REVIEW and rationale.
  • Protocol amendments link to versioned protocol IDs and engine versions.
  • Replay classifies agreement or divergence; no opaque “model decided” eligibility.

Identity, attribution, and access control

  • Epic layer: Backend Services uses a registered client credential (service identity). FHIR access is attributable to that Epic app registration and institutional consent—not to an individual end-user OAuth token on the screening hot path. There is no Epic “user impersonation” model on this connector: the app does not obtain on-behalf-of-clinician delegated scopes.
  • Application layer — who triggered the pull: interactive cohort / EHR sync is invoked via authenticated API routes (e.g. POST /v1/integrations/ehr/sync): the request is authorized with RBAC (workflow_update on the org) after principal_from_request resolves the caller’s subject and org. Scheduled automation may instead use an org-scoped DAMAROS_EHR_SYNC_TRIGGER_TOKEN bearer (no human session)—that path is explicitly service-to-service, not a hidden user identity. The token value lives only in a secret manager (or equivalent), is rotatable on demand, and each enqueue / trigger emits an auditable log_event line with org and job outcome. It grants no broader API privileges: it is accepted only as Bearer on POST /v1/integrations/ehr/sync and cannot enqueue unrelated jobs or call other routes.
  • Correlating Epic traffic to application audit: successful persisted ingests receive a durable cohort_sync_run_id carried into screening and review rows; connectivity or no-op checks are logged without creating downstream cohort mutation records.
  • Coordinators and staff use identity-backed sessions (JWT / OIDC per deployment). Principals carry subject, role, organization scope, and trial scope; administrative and export actions remain RBAC-gated; web sessions are TTL-bound.
  • Summary for IT: Epic audit trails reflect the Backend Services app; Damaros audit trails reflect the signed-in principal (or declared automation token) plus cohort_sync_run_id—together they answer “which institution, which ingest, which human action” without conflating the two layers.

Write-back controls (defense in depth)

  • Epic write scopes are not requested for initial deployment (see §03C).
  • Write helpers are off by default at the code level in every environment.
  • Non-production: DAMAROS_FHIR_WRITE_ENABLED=1 only after explicit site policy may enable write helpers for sandbox testing.
  • Production: requires DAMAROS_FHIR_WRITE_ENABLED=1 and DAMAROS_FHIR_PRODUCTION_WRITE_ATTESTED=1 when DAMAROS_ENV is production-class—two independent signals so a single typo cannot enable EHR mutation during audit.

Performance and API Usage

  • FHIR usage follows Epic operational guidance; iterative reads are the default. Bulk export is used only where §03B gates are satisfied.
  • Rate limiting and bounded exponential backoff on 429/5xx.
  • Per-resource failures in a bundle do not block other resource types in the same pull.

Error Handling and Resiliency

  • Missing clinical data surfaces as REVIEW where appropriate—not silent PASS.
  • Retry paths are idempotent while write-back remains disabled.

Security, privacy, and logging

  • Signing keys and Epic access tokens are server-side secrets; FHIR and browser traffic use TLS.
  • Logging posture: structured logs favor stable identifiers (request id, org id, trial id, non-PHI codes). Field-level exclusion lists omit free-text clinical narrative from default log templates; high-sensitivity routes use reduced payloads. Operators may attach SIEM classification tags (e.g. security vs. workflow) per deployment. Full request or response body logging for FHIR traffic is not a default product posture.
  • Data at rest encryption and retention follow deployment architecture and the customer BAA / DPA.

Review artifacts (non-production samples)

Representative examples for security / App Orchard review only—dummy or truncated values; production YAML, keys, CIDRs, and log schemas are environment-specific. Not a substitute for your tenant’s live config. Canonical live JWKS: jwks-prod.json / jwks-nonprod.json.

Illustrative NetworkPolicy YAML — fictitious CIDR; enforced shape matches Helm when policy enabled
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: damaros-api-egress
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/component: api
  policyTypes: [Egress]
  egress:
    - ports: [{port: 53, protocol: UDP}, {port: 53, protocol: TCP}]
      to:
        - namespaceSelector:
            matchLabels: {kubernetes.io/metadata.name: kube-system}
    - ports: [{port: 443, protocol: TCP}]
      to:
        - ipBlock: {cidr: 203.0.113.0/24}   # resolve & replace: Epic FHIR + token host CIDRs
    - ports: [{port: 5432, protocol: TCP}]
      to:
        - podSelector:
            matchLabels: {app.kubernetes.io/component: postgres}

This shape reflects the enforced default when the chart’s networkPolicy.enabled (and related egress blocks) are left on—IT should treat it as the shipped baseline, not decorative documentation. Full template: deploy/helm/damaros/templates/networkpolicy.yaml in the product repo—includes Redis, OIDC, and optional extra HTTPS CIDRs.

Fictitious JWKS shape only — not live keys; modulus truncated; do not use for auth
{
  "keys": [{
    "kty": "RSA",
    "use": "sig",
    "alg": "RS384",
    "kid": "example-hospital-prod-2026",
    "n": "w9zY…BASE64URL_TRUNCATED…q8Q",
    "e": "AQAB"
  }]
}
Fictitious JWT tutorial only — dummy client id / URLs; not signed; not for production
# header (base64url-decoded for readability)
{ "alg": "RS384", "typ": "JWT", "kid": "example-hospital-prod-2026" }

# claims
{
  "iss": "epic-backend-services-client-id-example",
  "sub": "epic-backend-services-client-id-example",
  "aud": "https://fhir.epic.com/interconnect-nonprod-oauth/oauth2/token",
  "jti": "2f5b0c1a-9e3d-4b7a-8f1c-0d2e4a6b8c0e",
  "exp": 1710000300,
  "iat": 1710000000
}
Illustrative redacted audit correlation (single JSON line; fields may span log + domain event)
{"ts":"2026-04-01T12:00:00.000Z","event":"integrations.ehr_sync.triggered","org_id":"org_***","principal_sub":"oidc|coordinator_***","damaros_role":"coordinator","epic_oauth_client_id":"epic-backend-services-client-id-***","cohort_sync_run_id":"csr_7f2a9b1c***","ok":true}

Each such record corresponds to one discrete EHR sync execution (coordinator UI action or automation token)—mappable to “who kicked off this pull” in plain language. Production lines follow your SIEM schema; Epic records the same Backend Services client_id on token issuance. The JSON above is a review packet composite (fields may span log_event + domain event)—not one verbatim syslog line.

Contact

Organization    Damaros, LLC
Email          anirudh@damaros.ai
Website        damaros.ai