Production-grade access is free with an account. · Synthetic demo · no account needed

FinchNode

Patient-authorized EHR integration

FHIR API error handling: retries, rate limits, and partial results.

A production integration needs a failure model that separates authentication, authorization, unsupported behavior, source outages, and incomplete synchronization.

Updated: 2026-08-25 · 10 min read

The bottom line

Retry only transient and safely repeatable work, honor server guidance, parse OperationOutcome when present, and expose partial sync state. An integration is more trustworthy when it reports incomplete data than when it silently returns an empty record.

Key takeaways

  • A 200 empty search, a 403 scope denial, a 404 unsupported resource, and a timeout require different product states.
  • Use exponential backoff with jitter and a retry budget; never create an unbounded retry loop.
  • Persist per-source and per-category progress so one failed query does not erase successful data.

Evidence boundary: Uses FHIR R4 HTTP and OperationOutcome semantics with conservative distributed-systems retry practices.

Author: FinchNode Engineering

Start with an actionable error taxonomy

Representative FHIR integration outcomes
OutcomeLikely interpretationDefault action
200 with zero entriesSuccessful search with no matchesRecord empty result and freshness
400 + OperationOutcomeInvalid or unsupported requestDo not retry unchanged; inspect issue details
401Missing or expired authorizationRefresh once or require reauthorization
403Granted access does not permit requestMark category unavailable; do not loop
404Resource or endpoint unsupported/not foundCheck capability and route
429Rate limitedHonor Retry-After and retry budget
5xx / timeoutTransient source or network failureBounded retry with jitter

Use a bounded retry helper

export async function withRetry<T>(operation: () => Promise<T>, attempts = 4) {
  let lastError: unknown;
  for (let attempt = 0; attempt < attempts; attempt += 1) {
    try {
      return await operation();
    } catch (error) {
      lastError = error;
      if (!isTransient(error) || attempt === attempts - 1) throw error;
      const base = Math.min(500 * 2 ** attempt, 8_000);
      const jitter = Math.floor(Math.random() * 250);
      await new Promise((resolve) => setTimeout(resolve, base + jitter));
    }
  }
  throw lastError;
}

Make partial synchronization visible

  • Track status for each connection and record category.
  • Return the latest successful data with its timestamp when policy permits stale reads.
  • Include a machine-readable reason for unavailable or delayed categories.
  • Emit lifecycle events when a connection requires user action.
  • Never replace previously successful data with an unexplained empty snapshot after a failed refresh.

Primary sources

More FinchNode interoperability guides

Frequently asked questions

Should a FHIR client retry every 500 response?

Only with a bounded policy and only when the operation is safe to repeat. Consider server guidance, idempotency, elapsed time, and an overall retry budget.

What is a FHIR OperationOutcome?

It is the standard FHIR resource for reporting issues, errors, warnings, and diagnostics. Parse it when present, but still handle non-FHIR error bodies safely.

Is an empty Bundle an error?

No. A searchset Bundle with zero matching entries is normally a successful empty result. Preserve the distinction between empty, unsupported, unauthorized, and failed.

How should an app show partial patient data?

Show which sources and categories succeeded, their freshness, and which remain unavailable or syncing. Do not present a partial record as complete.