FHIR access basics
Authentication: SMART Backend Services
Section titled “Authentication: SMART Backend Services”Almost every EHR in this cookbook uses SMART Backend Services for system-to-system access:
- You publish a JWKS (your public keys) at a stable URL and register it with the vendor. Most vendors give you back a client id; there is usually no client secret.
- To get a token, sign a short-lived JWT (5 minutes is typical) with
issandsubset to your client id,audset to the token URL, plusjti,iatandexp. - POST it to the token URL as a
client_assertionwithclient_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearerandgrant_type=client_credentials. - Reuse the token until shortly before
expires_in(if the server sends none, assume 5 minutes). Refresh once on a 401, and re-mint during long downloads.
Variations you will meet:
| Variation | Seen on |
|---|---|
| RS384 signing is the default; ES384 is required | ModMed, Meditab |
system/*.read rejected — every scope must be listed |
Oracle Health (Cerner) |
| Server grants only the intersection of what you ask for and what was provisioned | eClinicalWorks, Oracle Health |
SMART v2 scope form (system/Observation.rs) |
ModMed |
Token parameter named scopes, not scope |
NextGen |
| Client credentials with a client secret instead of a JWT | athenahealth, Elation |
| Client id and secret sent as POST parameters alongside the JWT | Amazing Charts |
How tenants are modelled
Section titled “How tenants are modelled”Ask on day one how the vendor separates practices, because it decides where you store credentials:
- One app, per-practice base URL — Epic (each health system has its own endpoint), ModMed, Practice Fusion, MeldRx.
- Per-practice client id on a shared host — Nextech, Compulink. The token carries a practice claim.
- One app plus a per-practice parameter — athenahealth (
ah-practice), NextGen (site_id), eClinicalWorks (endpoint code plus group id). - Separate registrations for bulk and REST — eClinicalWorks: a bulk client id gets 401 on every REST endpoint.
Three extraction styles
Section titled “Three extraction styles”1. Bulk Data export ($export)
Section titled “1. Bulk Data export ($export)”Kick off an export, poll the status URL, download NDJSON files from the manifest. In theory the best fit for measuring a whole population; in practice it is rarely usable exactly as advertised.
_sincefilters on when a resource was last updated, not on the date of service. Decide measurement-year membership yourself fromperiod.startoreffective[x].- Never apply
_sinceto reference data such asPractitioner, or older but still-active providers disappear. - Check
requiresAccessTokenin the manifest: some vendors need a bearer token on every file download.
2. Paginated search, one resource type at a time
Section titled “2. Paginated search, one resource type at a time”Pull each resource type for the whole tenant with ordinary search and follow link.next. Where
bulk is broken but population search is allowed, this is often faster and more reliable than bulk.
3. Per-patient cascade
Section titled “3. Per-patient cascade”For each patient in your cohort, search each resource type with patient=<id>. This is the only
option on EHRs that forbid population-level search, and the slowest. Keep the cohort small: limit
it to patients with an encounter in the measurement year rather than every patient on file.
Most production integrations combine them: one bulk or search pass to find the cohort, then a per-patient cascade for the clinical detail.