OperationOutcome Design for Validator Responses

The OperationOutcome a validator returns is the sender's only tool for fixing the payload. A well-designed outcome tells them what is wrong, where in the resource it is wrong, how to fix it, and how to correlate it with logs if support gets involved. A poorly-designed outcome sends the sender back to guessing. Getting this right is small work at emission time and permanent savings at support time. The site's R4 resource vetter emits FHIR-conformant OperationOutcomes for every finding. For the wider FHIR framing, more on payer-side FHIR has more.

Every Issue Has Four Fields

  • severity — fatal, error, warning, information
  • code — categorical reason (structure, invalid, required, code-invalid, invariant)
  • expression — FHIRPath pointer to the offending element
  • diagnostics — short, actionable, stable text

Every finding your validator emits should populate all four. Skipping any of them makes the sender's job harder.

Severity Matters More Than It Looks

Fatal stops the operation. Error rejects the resource. Warning notes something worth attention but does not reject. Information is context.

A validator that treats every finding as an error is too strict. A validator that treats structural spec violations as warnings is too lax. Match severity to what the receiver actually does with the resource:

  • Structural spec violation → error (the receiver cannot process this)
  • Profile violation on a mustSupport element → error (the profile contract broken)
  • Advisory-binding value not in the value set → warning (the code is likely fine)
  • Missing optional element noted for review → information

code Categorizes The Fix

The FHIR issue-type value set gives you the vocabulary. Use it as-is. Do not invent new top-level codes.

  • structure — payload shape wrong
  • required — required element missing
  • invalid — value violates a constraint
  • code-invalid — coded value outside a bound value set
  • invariant — profile invariant expression returned false
  • not-supported — the operation is not implemented

Consumers already dispatch on these. For the enumeration and their intent as consumed by support, meta.profile: what it claims and what it costs to enforce touches on how they land in workflows.

expression Is The Pointer, Not location

Use issue.expression with a FHIRPath string. Patient.name[0].family beats Patient.name[0]/family and beats a legacy location array. For Bundle validators, include the Bundle path: Bundle.entry[3].resource.name[0].family.

Consumers can resolve expression against the payload. Skip location.

diagnostics Should Be Short And Actionable

Bad: "Validation failed for element."

Good: "Patient.gender must be one of male, female, other, unknown."

The difference is actionability. The good version tells the sender exactly what to send. The bad version invites a support ticket.

Stable Templates Beat Inline Interpolation

Use templates with slot placeholders. "Patient.{field} must be one of {allowed}." Fill the slots at emission time.

Consumers string-match on the template hash for triage. Drifting text breaks their tooling. Every incident where "this used to look different" happens is one where the template drifted.

Include A Correlation Id

Set Resource.id on the OperationOutcome. Include a unique per-emission identifier. Support engineers correlate outcomes with server logs using it.

Without a correlation id, "this specific incident" becomes "some incident that day."

Emit One Issue Per Distinct Problem

Do not aggregate. Two missing required elements are two issues. Two invariant failures are two issues. Consumers can render, count, and route each individually.

For the batch-pipeline case where aggregation happens downstream, resource validation in a batch pipeline covers the aggregation pattern.

Return HTTP Status That Matches

  • 422 Unprocessable Entity for structural or invariant failures
  • 400 Bad Request for malformed input
  • 403 Forbidden for authorization failures on validation
  • 200 OK for warning-only or information-only outcomes

Match the code to the reason. Do not blanket-500 on any failure.

The Short Version

Every finding gets severity, code, expression, diagnostics. Match severity to what the receiver does. Use FHIR-standard codes. Expression as FHIRPath. Diagnostics short and templated. Include correlation id. One issue per problem. Match HTTP status.

Editorial-gouache diagram of an OperationOutcome emission structure with severity, code, expression, and diagnostics annotated, with correlation id and HTTP status pathways, in soft coral gouache tones on cream paper

Sources