Skip to content

Onboarding a new EHR

This playbook is distilled from connecting to more than a dozen EHRs. Follow it in order — each step is cheap, and skipping one is how weeks get lost.

  • Find the vendor’s developer portal (or, for vendors without one, the support or partner channel that enables API access).
  • Register your JWKS URL and record the client id, token URL, FHIR base URL and granted scopes.
  • Find out whether there is a sandbox. Several vendors offer none, which means every endpoint you test against is production — treat it that way.

Get a token, call GET /metadata, then GET /Patient?_count=1. Decode the token and check any practice or tenant claim: credentials are sometimes issued for a different practice from the one you asked for. An Organization query settles it in minutes.

3. Check that the practice has actually enabled you

Section titled “3. Check that the practice has actually enabled you”

Registering an app is not the same as getting data. Several vendors need a second, per-practice enablement step. The tell-tale sign: count queries succeed but record reads return HTTP 200 with an empty searchset. Build a probe that tells “this practice refuses reads” apart from “this practice genuinely has no data”.

4. Probe what data exists before building anything

Section titled “4. Probe what data exists before building anything”

For every resource type your measures need, run GET /<Type>?_count=1 and read total (where the server reports it honestly — see extraction patterns).

A real example from a specialty practice: about 20,000 patients and 136,000 encounters, but only about 380 observations — no blood pressures, no A1c, no depression screens — and Procedure.code always empty. No transport can extract data the practice never charts. Tell stakeholders before they expect scores.

Then trace one patient end to end, and separate clean zeros from HTTP 400s caused by the shape of your query.

  1. Test $export with a tiny _type first (for example Practitioner). It proves kickoff, status polling, the manifest shape, requiresAccessToken, and whether _type is honoured.
  2. Don’t trust a 202. One vendor accepted every kickoff, then took 34 minutes to reach 1%, while its status URL dropped into multi-minute 5xx windows.
  3. If bulk is broken, pull each type with paginated search and stream each page straight to NDJSON on disk. One tenant of about 190,000 resources pulled this way in under two hours.
  4. Use a per-patient cascade only where forced — some types reject tenant-wide search (“patient or _id must be supplied”), and some servers reject comma-separated patient lists. Scope the cascade to patients with an encounter in the measurement year; in one case that cut 20,700 patients to about 1,000.

Assume every new EHR needs a normalization pass. The most common problems:

  • References without type (subject: { reference: "Patient/123" } only). A parser that relies on type gets empty patient ids — ingest counts look healthy and every measure scores zero. Inject type from the reference prefix.
  • Encounter status that does not mean what it says — completed visits arriving as in-progress or unknown.
  • Attribution missing from the data — TIN is never in FHIR, and NPI is often absent. Plan for a mapping you maintain yourself.

See extraction patterns for the full list.

  • Retry status 0, 429 and 5xx with capped exponential backoff and honour Retry-After. Fail fast on other 4xx. Tune the retry budget per vendor — some outage windows last ten minutes.
  • Only failures on the cohort-defining types (Patient, Encounter) should stop the run. Other types should warn and continue, and mark affected patients “incomplete” rather than “no data”.
  • Exit non-zero on failure so schedulers notice.
  • Archive the raw NDJSON and support “reprocess from archive”, so a change to measure logic never requires another export.
  • Log export and status URLs at kickoff so a stuck job can be inspected or resumed.
  • Never log full FHIR payloads. They contain PHI.

Use Synthea patients and the vendor’s sandbox where one exists to exercise the pipeline without PHI.