How to Handle Healthcare API Authentication, Token Expiration and Error Recovery in Production
A healthcare integration that works in a demo and one that survives production are usually separated by how well it handles the boring failure cases: an expired token, a rate limit, a vendor's maintenance window.
Token lifecycle: refresh before you need to, not after you fail
Most healthcare API access - SMART on FHIR interactive apps and SMART Backend Services alike - runs on OAuth 2.0 access tokens with a short lifetime (often 5-60 minutes) plus either a refresh token (interactive flows) or the ability to mint a new token directly (backend/client-credential flows). Track each token's expiration time explicitly and refresh proactively - a few minutes before expiry - rather than waiting for a request to fail with a 401 and reactively refreshing. Reactive-only refresh logic works fine until two requests hit an expired token simultaneously and you end up racing multiple refresh calls.
For backend/client-credential flows specifically, remember the assertion itself (the signed JWT proving your app's identity) typically has its own short expiry - independent of the access token's expiry - and needs to be freshly generated per token request, not cached and reused.
Building an error taxonomy across vendors
Different EHR vendors return different shapes for the same underlying problem, but they generally fall into a few categories worth handling distinctly: authentication failures (expired/invalid token - refresh and retry once), authorization failures (valid token, insufficient scope or the resource genuinely isn't permitted - don't retry, this needs a configuration fix), rate limiting (back off and retry with delay), transient server errors (5xx - retry with backoff), and semantically valid 'no data' responses (not an error at all, even though some vendors surface it via a warning-severity code that looks alarming in logs if you're not distinguishing severity).
That last category matters more than it sounds: naively treating every non-2xx or every OperationOutcome as a hard failure means real errors get lost in a flood of expected 'no data' noise, and your alerting becomes useless.
Retry and backoff strategy
Use exponential backoff with jitter for transient failures (5xx, rate limits) rather than fixed-interval retries - fixed intervals from multiple clients tend to synchronize and hit the vendor's API in bursts, making rate limiting worse. Cap total retry attempts and time, and make sure whatever's waiting on the result (a user-facing request, a background job) has a sensible timeout and fallback rather than hanging indefinitely.
Never blindly retry on 4xx errors other than a token-expiry 401 - a malformed request or a genuine authorization failure will fail identically on retry and just burns budget against rate limits without fixing anything.
Log enough to actually debug production issues
Log every outbound request's URL, status code, and a truncated response body (redacting PHI where the response itself might contain it) - not just failures. When something looks broken days later, 'it returned no data' is indistinguishable from 'it silently failed' unless the actual response is in your logs. This is also useful defensively: several real integration bugs are only found by comparing what a vendor's API actually returned against what the code assumed it would return.
FAQ