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.
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.
| APPLICATION_AUDIENCE | Backend Systems (Epic-registered Backend Services app) |
| PRIMARY_OAUTH_MODEL | client_credentials + JWT client assertion to Epic token URL; PKCE does not apply |
| USER_SMART_LAUNCH | Not 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_VERSION | R4 |
| USE_CASE | Clinical trial eligibility screening, evidence retrieval, cohort review, audit reconstruction |
| AUTH_STANDARD | SMART on FHIR Backend Services (RFC 7523) |
| AUTH_METHOD | JWT client assertion (urn:ietf:params:oauth:client-assertion-type:jwt-bearer) |
| JWT_ALGORITHM | RS384 (override via DAMAROS_EPIC_JWT_ALGORITHM if Epic requires) |
| KEY_SIZE | RSA-2048 (or per Epic registration) |
| TOKEN_TTL | Assertion exp within 300 seconds of iat; access token TTL from Epic response; in-memory cache only |
| DEFAULT_ACCESS | Read-only FHIR; no EHR write-back unless explicit env gates below |
| TOKEN_ENDPOINT | DAMAROS_EPIC_TOKEN_URL (alias: DAMAROS_EPIC_OAUTH_TOKEN_URL) |
| FHIR_BASE | DAMAROS_EPIC_FHIR_BASE (alias: DAMAROS_EPIC_FHIR_BASE_URL) |
| NON_PRODUCTION | Separate Epic sandbox / non-prod client ID, signing keypair, and JWKS URL |
| PRODUCTION | Independent Epic production client ID, signing keypair, and JWKS URL |
| JWKS_HOSTING | Public 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_NONPROD | https://www.damaros.ai/.well-known/jwks-nonprod.json — public keys only; non-production Epic client kid alignment |
| JWKS_STATIC_PROD | https://www.damaros.ai/.well-known/jwks-prod.json — public keys only; production Epic client kid alignment |
| JWKS_KID_PARITY | The 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_ROTATION | Publish 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_OWNERSHIP | Customer-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_API | Optional: 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_STORAGE | Private signing PEM from secret manager or secure mount; never in git or client bundles |
| WRITE_ENV_NONPROD | DAMAROS_FHIR_WRITE_ENABLED=1 required for any FHIR POST/PUT helper path |
| WRITE_ENV_PRODUCTION | Requires 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) |
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_TYPE | client_credentials |
| ASSERTION_TYPE | urn:ietf:params:oauth:client-assertion-type:jwt-bearer |
| TOKEN_ENDPOINT | Epic-issued URL (DAMAROS_EPIC_TOKEN_URL) |
| JWT_ISS / JWT_SUB | Epic client ID for the active environment |
| JWT_AUD | Token endpoint URL (per Epic / RFC 7523) |
| JWT_JTI | UUID per assertion |
| TOKEN_STORAGE | In-process only; not written to disk, DB, or browser |
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.read | Patient resource for cohort binding and demographics used in deterministic rules |
system/Group.read | Resolve cohort membership (patient ids) for approved trial lists |
system/Observation.read | Laboratory observations per Epic-tuned searches (not full-chart extract) |
system/Condition.read | Active/problem list context for inclusion or exclusion criteria |
system/MedicationRequest.read | Outpatient medication orders for protocol-relevant criteria |
system/Procedure.read | Historical procedures referenced by criteria |
system/AllergyIntolerance.read | Allergies and intolerances for safety-related criteria |
system/DocumentReference.read | Document metadata (pagination); eligibility authority remains deterministic engine output, not generative text |
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.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).
GET Patient/{id}GET Group/{id} for member patient IDs (separate cohort sync path)patient=Patient/{id}, paginatedpatient=Patient/{id}, paginatedDAMAROS_EPIC_FETCH_MEDICATION_STATEMENT=1 opt-inpatient=Patient/{id}, paginatedpatient=Patient/{id}, paginatedpatient=Patient/{id}, paginatedBulk 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.
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.
DAMAROS_FHIR_WRITE_ENABLED + production attestation; not activated in initial submissionThe 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.
fetch_patient_bundleNetworkPolicy 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.evaluation_as_of.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.cohort_sync_run_id carried into screening and review rows; connectivity or no-op checks are logged without creating downstream cohort mutation records.subject, role, organization scope, and trial scope; administrative and export actions remain RBAC-gated; web sessions are TTL-bound.cohort_sync_run_id—together they answer “which institution, which ingest, which human action” without conflating the two layers.DAMAROS_FHIR_WRITE_ENABLED=1 only after explicit site policy may enable write helpers for sandbox testing.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.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.
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.
{
"keys": [{
"kty": "RSA",
"use": "sig",
"alg": "RS384",
"kid": "example-hospital-prod-2026",
"n": "w9zY…BASE64URL_TRUNCATED…q8Q",
"e": "AQAB"
}]
}
# 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
}
{"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.