Nirmitee.io
InteroperabilityFHIR

Designing for Per-Payer FHIR Variation: Why One Integration Is Never Enough

August 5, 20267 min readUpdated Sep 26, 2026
Written by
Jitendra Choudhary
Jitendra Choudhary

CTO & Co-Founder

CTO & Co-Founder at Nirmitee.io. Architects healthcare integrations across FHIR, SMART on FHIR, ABDM and NHCX, writing from production experience taking hospital software from sandbox to go-live.

Designing for Per-Payer FHIR Variation: Why One Integration Is Never Enough

The most expensive assumption a team makes about CMS-0057-F is that a mandated API is a uniform API.

It is not. The rule specifies which endpoints must exist, which profiles they must conform to, and by when. It does not produce identical implementations, and it was never going to. Every impacted payer publishes its own base URL, runs its own developer portal, and makes its own choices inside the space the specification leaves open — which is a much larger space than most people expect.

If you are planning this for production, our FHIR integration team can build it with you.

The pattern is consistent enough to predict. A team integrates the first payer, then the second, then the third, and the code holds. Somewhere around payer six to eight, the accumulated special cases stop being special cases, and the integration layer gets rewritten. The rewrite is not a failure of engineering skill. It is the natural consequence of a design that treated variation as exceptional rather than as the baseline condition.

What actually varies

All seven of these can differ while every payer remains fully compliant.

Search parameter support

Conformance requires a defined set. It does not require the optional ones, and it does not require the combinations you want. A payer may support date on ExplanationOfBenefit but not the _include you were counting on to pull the referenced provider in one round trip. The difference between one query and three is not academic when you are running a panel-level pull.

Pagination behaviour

Page size caps vary. Some servers use offset-style paging, some use opaque cursors. Link relations are sometimes incomplete — a next without a self, or a last that is absent on large result sets. A client that assumes a full link set will silently truncate against a server that does not provide one.

Bulk export maturity

This is the widest variance, and the one that hurts most. FHIR Bulk Data Access is the intended pattern for Provider Access panel pulls, but export kickoff behaviour, poll intervals, file partitioning, compression and expiry windows are all implementation choices. Some payers will handle a 20,000-member group cleanly. Some will time out.

Extension usage

Payers carry data the base profiles have nowhere to put, and they will use extensions to do it. You will encounter extensions you have never seen, some documented on the payer's portal and some not. The design question is what you do with an extension you do not recognise, and the answer is almost always "preserve it, do not surface it, do not drop it."

Profile strictness

How aggressively does the server validate? Some reject a bundle over a cardinality issue that another accepts without comment. This matters on the submission path more than the read path, and it is a common source of "works against payer A, fails against payer B" tickets that look like your bug and are not.

Auth and registration

Each payer runs its own client registration. Scopes granted, token lifetimes, refresh behaviour, review timelines, and required attestations all differ. Registration lead time is frequently the long pole in onboarding a new payer, and it is not an engineering task — which means it does not show up on the engineering plan and then blocks it.

Rate limits and quotas

Often undocumented. Discovered under load, usually in production, usually during a panel refresh.

The design principle: read, do not assume

FHIR already gives you the mechanism. Every conformant server exposes a CapabilityStatement at /metadata describing which resources it supports, which interactions, which search parameters, and which operations. The durable pattern is to fetch it, cache it, version it, and let it drive behaviour at runtime.

That sounds obvious written down. In practice, most integrations read the CapabilityStatement once during development, use it to inform hard-coded assumptions, and never look at it again — which converts a runtime signal into a build-time one and reintroduces the exact problem it was meant to solve.

Two caveats worth stating honestly. First, CapabilityStatements are not always complete; a server may advertise support it does not really have, or omit something it does. You will still need a probe layer for the handful of behaviours that matter most. Second, they change. Version the cached copy and detect drift, or you are back to build-time assumptions with extra steps.

A shape that holds

Product layer

Your features. It should not be able to name a payer. If a feature has a branch on "if payer is X", the abstraction below it has failed, and the failure will spread.

Canonical model

One internal shape that all payer data lands in. This is where normalisation happens, and it is the layer that makes payer count cheap instead of linear.

Capability registry

The piece most teams do not build until the rewrite. A per-payer record of what that payer supports, sourced from the CapabilityStatement plus your own probe results, cached with a version and a fetch timestamp. Features query the registry rather than a config file. When a payer ships a bulk export, you update a registry entry, not a code path.

Payer adapters

Thin by design. Only the deltas — the auth quirk, the pagination style, the extension mapping. If an adapter grows business logic, that logic belongs in the canonical layer.

Degrade, do not fail

When a payer cannot do something, the product should narrow its offerings rather than throw an error at a clinician. Each row below is a decision you will make anyway; making it deliberately is the difference.

The last one deserves emphasis. If a payer endpoint is down and you serve cached data, the freshness has to be visible. A clinician looking at a payer view has no way of knowing whether it is current, and the cost of assuming it is current is clinical, not technical. This is the same discipline as representing attribution incompleteness honestly, and it is the part of the design most likely to get cut under deadline pressure.

What this costs up front

Being straight about the trade: the layered shape is more work in month one. A single hard-coded client against your first payer will ship faster and will be the right call if you are genuinely only ever integrating one.

The break-even is somewhere around the third or fourth payer, and it arrives sooner than teams expect because the second payer is usually chosen to be similar to the first. Payer three is where the assumptions start showing. If your roadmap has more than three payers on it — and under CMS-0057-F most provider-facing roadmaps eventually do — the abstraction pays for itself well before the rewrite would have happened.

Related, but different

Worth separating from a neighbouring problem: variation across EHR vendors — Epic, Oracle Health, athenahealth — is a different surface with different quirks. The architectural instinct is the same; the specifics are not, and a team integrating both will need two adapter families rather than one.

Where Nirmitee.io fits

Healthcare-only, FHIR R4 native, with production EHR integrations delivered for healthtech companies and an open-source Headless EHR covering 28 clinical domains and 70+ FHIR R4 resources. The capability registry and the canonical model are the two decisions that are expensive to reverse once features have been built on top of them. Usually a 15-minute architecture conversation is enough to know whether your current shape will hold.

Sources

Ready to scale?

Talk to our healthcare engineering team about building, integrating, and shipping faster.

Frequently Asked Questions

Why do conformant payer FHIR APIs behave differently?

CMS-0057-F specifies which endpoints must exist and which profiles they must conform to, not how every optional behaviour is implemented. Search parameter support, pagination style, bulk export maturity, extension usage, profile strictness, auth flows and rate limits all remain implementation choices.

What is runtime capability detection in FHIR?

Fetching each server's CapabilityStatement from its /metadata endpoint, caching and versioning it, and letting it drive feature behaviour at runtime rather than hard-coding assumptions at build time. It also needs a small probe layer, because advertised capability and actual capability sometimes differ.

How many payers before a hard-coded integration needs rewriting?

Typically around payer six to eight. The second payer is usually chosen to resemble the first, so assumptions hold longer than they should; payer three onwards is where accumulated special cases start to dominate the codebase.

What should a product do when a payer does not support bulk export?

Degrade rather than fail: fall back to paged per-patient reads on a scheduled job, and record the capability gap in the registry so the fallback disappears automatically when the payer ships bulk support.
Share