
Standing up a FHIR API sounds like picking a server and turning on endpoints. Seven design choices made upfront prevent 6-12 months of downstream rework.
1. Version strategy: R4 with R5 preparation. US Core, CMS-0057, and most Da Vinci IGs reference R4. Ship R4 endpoints, but structure the codebase to add R5 later. See the FHIR R5 spec for what changes.
2. Profile support at the endpoint level. Every read endpoint should be conformant to a named profile (US Core Patient, Da Vinci PDex Coverage). Write endpoints should validate against profiles at write time via $validate.
3. Search parameter completeness. Chained (Patient?general-practitioner.name=Smith), reverse-chained (Patient?_has:Observation:...), and _include/_revinclude support is where second-tier servers fail. Verify against your intended query patterns.
4. Auth server selection. SMART on FHIR launch spec requires an authorization server. Ship SMART v2 scope support (Observation.rs grammar), not just v1 (Observation.read).
5. Rate limiting design. Per-token, per-scope rate limits are the FHIR-native approach. Application-specific limits with escape hatches for internal traffic prevent app-level DoS.
6. Async operations as first-class. Bulk Data IG $export requires async job management, output storage, and expiry cleanup. Design this in from day one; retrofitting it is painful.
7. Observability at the resource-type level. Per-endpoint metrics separated by resource type are the operational baseline. Ship these on day one.
Rework cost by decision (empirical)
| Design decision | Rework cost if wrong |
|---|---|
| Version strategy | 6-12 months migration |
| Profile conformance | 3-6 months profile alignment |
| Search parameters | 2-4 months custom queries |
| Auth server | 4-8 months auth refactor |
| Async operations | 6+ months if bolted on later |
FHIR API design gets shipped once. Get the seven decisions above right the first time and everything after is incremental improvement.

