# 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.

[Production-grade access is free with an account.](https://finchnode.com/signup) The $0/month plan includes 100 connected patient-months, 100,000 production API calls per month, one production application, and an unlimited synthetic sandbox.

- Author: [FinchNode Engineering](https://finchnode.com/authors/finchnode-engineering)

- Published: 2026-08-25

- Last reviewed: 2026-08-25

- Canonical URL: https://finchnode.com/blog/fhir-api-errors-retries-rate-limits

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

## 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.

## Start with an actionable error taxonomy

Representative FHIR integration outcomes

| Outcome | Likely interpretation | Default action |
| --- | --- | --- |
| 200 with zero entries | Successful search with no matches | Record empty result and freshness |
| 400 + OperationOutcome | Invalid or unsupported request | Do not retry unchanged; inspect issue details |
| 401 | Missing or expired authorization | Refresh once or require reauthorization |
| 403 | Granted access does not permit request | Mark category unavailable; do not loop |
| 404 | Resource or endpoint unsupported/not found | Check capability and route |
| 429 | Rate limited | Honor Retry-After and retry budget |
| 5xx / timeout | Transient source or network failure | Bounded retry with jitter |

## Use a bounded retry helper

```typescript
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

- [FHIR R4 HTTP behavior and errors](https://hl7.org/fhir/R4/http.html)
- [FHIR R4 OperationOutcome](https://hl7.org/fhir/R4/operationoutcome.html)

## 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.