Six Ways Your FHIR API Is Drifting From Spec Right Now

Six Ways Your FHIR API Is Drifting From Spec Right Now

A FHIR API that passed certification in March is not the same FHIR API in November. Profiles get regenerated, search-parameter handlers get refactored, auth servers get upgraded, and Implementation Guide versions creep forward. The team rarely notices because everyone’s tests still pass — they are testing what they wrote, not what the spec requires.

This post walks through the six canonical drift categories Tessara’s Conformance Comparator classifies. Each section includes a plausible real-world example. The examples are illustrative — fabricated for clarity, not pulled from any specific deployment — but every shape is one we have seen in published Inferno reports, FHIR connectathon write-ups, or community bug threads.

If you read all six and recognize zero of them in your own deployment, congratulations: you are running an unusually disciplined shop, and you should still run a fresh conformance check this week. Drift accumulates between checks, not at them.

The full taxonomy lives at /blog/drift-taxonomy; this post is the field guide.

Category 1 — Mandatory Element Removal (CRITICAL)

Trigger: An element flagged mustSupport: true with min >= 1 in the IG is absent from the observed structure.

Illustrative example. A regional Medicaid MCO certifies its CARIN Blue Button 2.1.0 Patient Access API in Q1. Coverage.subscriberId is mustSupport: true and required in the IG. Six months later, an engineer refactors the eligibility-data mapping layer to support a new state-level program. The mapping function for Coverage now omits subscriberId when it is absent from the upstream system, on the reasonable theory that “missing data shouldn’t be invented.”

The CapabilityStatement still declares Coverage is supported. The profile reference still points at CARIN BB 2.1.0. The Inferno test that ran in Q1 was against a cohort of test patients all of whom had subscriber IDs. Nothing alerts.

The first failure is silent: a third-party app querying for Coverage for a real member without a subscriber ID throws a deserialization error. The second failure is loud: the app developer files a CMS-portal complaint.

Why it matters. This is the category that maps most directly to a CMS or OCR finding. The fix is technical, but the audit exposure is regulatory.

Category 2 — Type or Cardinality Change (CRITICAL / HIGH)

Trigger: A required element’s type, min, or max differs between the spec and the observed API.

Illustrative example. A national payer’s Claim.created field is declared as dateTime in CARIN Blue Button. Their underlying claims warehouse stores everything as date. The original FHIR mapper appended T00:00:00Z to satisfy the type. A subsequent refactor — driven by a daylight-saving-time bug — switched the mapper to emit raw date values, on the theory that “the time portion is meaningless anyway.”

The observed API now returns Claim.created: "2026-04-15" instead of Claim.created: "2026-04-15T00:00:00Z". Strict-validating clients break. Permissive clients silently coerce, with subtly different results across vendors.

A subtler variant: cardinality drift. Patient.identifier declared as 1..* in US Core but observed as 0..1 because the API filters out duplicate identifiers as a “cleanup” pass. The spec explicitly permits — and many state Medicaid programs require — multiple identifiers per patient. The cleanup pass is removing data the spec mandates be present.

Why it matters. Type drift corrupts deserialization across every client. Cardinality drift permits the API to return shapes the spec explicitly forbids — strictly worse than declaring an incompatible type, because the contract violation is not visible in the schema declaration alone.

Category 3 — Structural Extension (INFO)

Trigger: The observed structure contains elements not present in the spec, via extensions, profiles, or custom fields.

Illustrative example. A payer adds an _priorAuthorizationStatus extension on ExplanationOfBenefit to support a downstream provider portal. The extension is useful, internally well-documented, and not declared in the advertised CARIN BB profile.

This is the most ambiguous category. Structural extensions are how the FHIR ecosystem evolves. An undeclared extension is not by itself a compliance failure — most clients will ignore it. But it represents data the payer is emitting that has not been reviewed for privacy, conformance, or specification alignment. An OCR auditor reading the production responses for the first time will ask why patient-status data is appearing in a field they have never approved.

Structural Extension drift is INFO severity by default. It is the only category where the appropriate response is “log it and decide” rather than “fix it now.”

Why it matters. Audit surface, not immediate compliance failure. The category exists so that the audit trail captures what actually shipped, even when the divergence is benign.

Category 4 — Auth Deviation (HIGH)

Trigger: The observed security scheme — grant types, scopes, token endpoint auth methods, or SMART capabilities — differs from what the IG requires.

Illustrative example. The Da Vinci PAS Implementation Guide requires private_key_jwt for Backend Services authentication. A payer’s /.well-known/smart-configuration endpoint advertises client_secret_basic because the deployed auth server has not been upgraded to the SMART v2 spec. Provider-side client implementations using private_key_jwt are rejected with invalid_client. Provider-side implementations using client_secret_basic work, but they are out of conformance themselves.

A more common variant: scope-string drift. The IG specifies system/Claim.read. The auth server normalizes the scope to system.Claim.read (period instead of slash) at issuance time, breaking the SMART v2 grammar. Clients that strict-parse the returned access-token introspection fail; clients that lenient-parse pass — but the spec does not permit lenient parsing.

Why it matters. This is the category regulators notice fastest, because it directly affects whether a third-party app can legally access the API. SMART App Launch compliance is a gatekeeper for both CMS-0057-F and the 21st Century Cures information-blocking provisions. Civil money penalties under information-blocking are non-trivial.

Category 5 — Endpoint Behavioral Change (MEDIUM)

Trigger: The structural declaration matches the spec, but the endpoint’s runtime behavior has shifted.

Illustrative example. A payer’s Practitioner?name= search parameter is advertised in the CapabilityStatement and accepts the parameter without error. A refactor of the search-handler dispatcher accidentally drops the name filter from the query plan. Subsequent queries return the full Practitioner result set regardless of the supplied name. The CapabilityStatement still declares the parameter is supported. The endpoint still returns 200. The result set just does not match the query.

A schema-only validator misses this entirely. So does the certification test that ran six months ago, because it queried for name=Smith against a test patient whose only practitioner was named Smith — the test passed because every result was Smith, not because the filter worked.

The drift is detectable only by cross-checking declared capabilities against actual runtime behavior, which means at least one minimal functional probe.

Why it matters. Behavioral drift is the category most easily missed by schema-only validators. It is also the category that produces the most third-party-app developer complaints, because the breakage is invisible until a real query fails to filter.

Category 6 — Specification Version Mismatch (HIGH)

Trigger: The API’s self-reported FHIR version (fhirVersion in CapabilityStatement, or the IG version declared in profile references) does not match the mandated baseline.

Illustrative example. A payer certifies against CARIN Blue Button 2.1.0. Six weeks later, a build engineer updates the profile-package dependency to 2.2.0 because “it had a fix we needed.” The CapabilityStatement now references hl7.fhir.us.carin-bb@2.2.0 profiles that the compliance team has never reviewed and the certification environment has never tested.

The deeper variant: a staged rollout that never completed. The east-region production environment runs 2.2.0. The west-region production environment, behind a different load balancer, still runs 2.1.0 because the rollout stalled when QA flagged a regression. The same payer is now serving two different conformance contracts depending on which region the request lands on. Every downstream conformance test is validating against the wrong contract — for half the traffic.

Why it matters. Spec-version drift means every downstream conformance test is validating against the wrong contract. It is almost always the result of a staged rollout that never completed, and it persists silently because each individual region looks internally consistent.

All six are detected by the same pipeline

The categories exist as a taxonomy because they require different treatment. Mandatory Element Removal is a fix-this-week event. Structural Extension is a log-and-decide event. They have to be classified before they can be triaged.

Tessara’s Conformance Comparator runs the classification automatically against any FHIR API that exposes a public /metadata endpoint. The mechanics are public: structural contract models built from the IG and the observed CapabilityStatement, canonicalized under RFC 8785 JCS, hashed into a four-level Merkle tree, and diff-walked when the root hashes disagree. See our evidence chain post for the full pipeline.

The first run usually finds something. The second run, a month later, almost always finds something different — drift accumulates between checks, not at them.

What we built

Tessara is a productized version of this six-category comparator with an audit-grade evidence chain attached. Continuous, payload-free, signed. See pricing for design-partner slots, or contact us if you want us to run an initial scan of your existing deployment.


References: HL7 FHIR R4 specification, US Core Implementation Guide, CARIN Blue Button IG, Da Vinci PAS IG, SMART App Launch v2, Inferno (ONC FHIR test harness).