SMART functional conformance passes. Inferno is green functionally; HTTPS/TLS closure is the remaining SMART gate. View the roadmap

Why Ilavrita Refuses Unsupported FHIR Search Parameters

Ilavrita Engineering · September 21, 2026

Why Ilavrita Refuses Unsupported FHIR Search Parameters

Version: dev
Source revision: 946221398bc8fa8731838e98e3d040c102df7b63
Last verified: September 21, 2026
Data in examples: synthetic only

A FHIR search can fail in a dangerous way without returning an error.

Ask for:

http
GET /fhir/R4/Observation?status=final

and a server that silently ignores status can return every Observation it is otherwise allowed to return. The HTTP request succeeded. The response is a valid search Bundle. The client may still have received a materially wider answer than it asked for.

Ilavrita chooses a stricter contract: if this build cannot execute a search parameter or modifier with the semantics it claims, it refuses the search instead of approximating it or silently dropping it.

That choice is stricter than FHIR R4's general recommendation for unknown or unsupported parameters, which says servers SHOULD usually ignore them and lets clients request strict handling with Prefer: handling=strict. R4 is stronger for unsupported modifiers: a server SHALL reject a modifier it does not support for that parameter.

This post explains why Ilavrita deliberately chooses the fail-closed side of that trade-off, what the current search surface actually implements, and where the limit remains.

The important distinction: valid HTTP is not necessarily the answer requested

Search is unusual because failure can look exactly like success.

If a server does not understand a create request, it can fail the write. If it cannot read a resource, it can return an error. But when a search criterion is ignored, the remaining query can still produce a perfectly plausible result set.

Consider a synthetic Project containing these Observations:

text
obs-1  status=final
obs-2  status=preliminary
obs-3  status=amended

A client asks:

http
GET /fhir/R4/Observation?status=final

The intended answer is obs-1.

If status is silently discarded, the server can answer obs-1, obs-2, and obs-3. Nothing about those resources is malformed. The failure is semantic: the server answered a different question.

That is the class of bug Ilavrita's search boundary is designed to make visible.

What FHIR R4 actually says

The distinction matters because “FHIR requires servers to reject every unsupported search parameter” would be an overclaim.

It does not.

FHIR R4 says servers can receive unknown or unsupported parameters and in general SHOULD ignore them. It also defines:

http
Prefer: handling=strict

for a client that wants an error instead, and:

http
Prefer: handling=lenient

for a client explicitly asking the server to ignore unsupported input.

R4 also requires a search Bundle's self link to identify the parameters actually used, so a careful client can detect that a filter was not applied.

Modifiers are different. If a server supports a parameter but not the modifier attached to it, R4 says the server SHALL reject that search. For example, supporting name does not imply supporting name:exact.

Ilavrita's policy is therefore an implementation choice on top of the R4 baseline: we prefer an obvious refusal over a successful response whose scope changed underneath the caller.

Primary specification: FHIR R4 Search.

The registry is the contract

Ilavrita does not attempt to implement every search parameter defined by R4.

The current dev documentation describes a deliberately bounded registry. Search parameters are projections maintained when resources are written and predicates compiled when resources are read. Supporting a parameter therefore means more than recognizing its name.

For a parameter to be part of the current contract, the server needs to know how to:

  1. extract the relevant value from the FHIR resource;
  2. index it with the correct FHIR search semantics;
  3. parse the client's input;
  4. compile the corresponding predicate;
  5. keep Project and authorization restrictions in the same query; and
  6. advertise the support accurately through the CapabilityStatement.

The current synced documentation records four implemented parameter families:

  • token
  • string
  • reference
  • date

It does not currently claim indexing for number, quantity, uri, composite, or special parameter types.

That is a useful boundary because the alternative is easy to get wrong: accept the parameter because R4 defines it, then execute only the subset of its semantics the storage layer happens to understand.

Ilavrita refuses that shortcut.

Supported does not mean every modifier is supported

The same boundary exists one level below parameter type.

The synced dev documentation records these implemented modifiers:

  • :missing
  • :exact
  • :contains
  • :not

Other modifiers are refused rather than approximated.

This matters because a modifier changes the question.

name=Ward and name:exact=Ward are not equivalent

A bare FHIR string search normally behaves like a case-insensitive starts-with search. An exact search asks for the whole value with exact semantics.

If a server receives:

http
GET /fhir/R4/Patient?name:exact=Ward

and silently executes:

http
GET /fhir/R4/Patient?name=Ward

it has not degraded gracefully. It has changed the predicate.

FHIR R4 explicitly requires unsupported modifiers to be rejected, which aligns directly with Ilavrita's broader refusal rule.

:not is about the resource, not an individual index row

Negation is another place where an implementation can look plausible and still be wrong.

Suppose a resource has multiple token values and one of them matches the excluded value. Implementing :not as a simple SQL != over index rows can return the resource because another row differs from the excluded value.

The intended question is whether the resource has no matching value.

That difference is why search behavior belongs behind explicit implementations and tests rather than generic query-string plumbing.

Unsupported result controls are refused too

The current boundary also excludes several result-shaping features:

text
_include
_revinclude
_has
_sort
_summary
_elements

They are not silently ignored.

These parameters are particularly important because some do more than narrow a result. _include and _revinclude, for example, can add related resources to the Bundle. They also introduce another authorization question: permission to search one resource type cannot automatically become permission to return a second type.

Until those semantics and their authorization paths are implemented and tested, the honest answer is that the server does not support them.

See Known limitations for the current boundary.

Search runs inside authorization

Search is also where a tenant or authorization bug becomes a set disclosure rather than a one-resource disclosure.

Ilavrita's architecture therefore compiles authorization restrictions into the query rather than fetching a broad result set and filtering it afterwards.

Conceptually, a search is not:

text
find Observation where status = final
then remove rows this caller cannot see

It is closer to:

text
find Observation
where Project = this Project
  and authorization predicates hold
  and status = final

The distinction is deliberate. A row fetched into the wrong request path has already crossed a boundary, even if application code later removes it from the JSON response.

The current security documentation records Project isolation and authorization as enforced, with policy narrowing compiled into the query path. It also explicitly says this is a technical control, not a compliance certification.

See Security model and Architecture.

GET and POST _search are the same search contract

The current server exposes type search in both FHIR forms:

http
GET /fhir/R4/Observation?status=final

and:

http
POST /fhir/R4/Observation/_search
Content-Type: application/x-www-form-urlencoded

status=final

Those are two transports for the same search semantics. A parameter cannot be supported through GET and silently behave differently through POST _search.

The parser produces a search plan; the backend executes that plan. SQL stays in the storage backend rather than leaking upward into FHIR-facing packages.

That separation is part of a larger architecture decision: Ilavrita owns the FHIR contract even though its current runtime foundation is an embedded runtime and SQLite. The database is an implementation detail, not the API.

CapabilityStatement is where clients should discover the boundary

A client should not have to learn supported parameters by trial and error.

Ilavrita's GET /fhir/R4/metadata returns a CapabilityStatement generated from the running server's registered surface. The current documentation states that the declared resource entries carry their supported search parameters.

For example, the documented Patient entry includes parameters such as _id, _lastUpdated, identifier, family, given, gender, active, and birthdate.

The important rule is not that this particular list never changes. It will change.

The rule is that the server should advertise the list it can actually execute at that build.

That is why every post here carries a source revision.

Read the API documentation for the distinction between OpenAPI and CapabilityStatement: OpenAPI describes the HTTP surface for tooling; CapabilityStatement describes FHIR behavior for the running deployment.

Custom SearchParameters are deliberately constrained

The current build also lets a Project store a SearchParameter and search with it without restarting the server.

That does not mean arbitrary FHIRPath has become executable search logic.

The synced documentation states that the current custom-parameter implementation accepts expressions the build can actually apply and refuses expressions it cannot. Complex functions, unions, indexes, and unsupported parameter types are not approximated.

A newly defined parameter also creates an indexing problem: resources written yesterday were not indexed under a definition that did not exist yesterday.

Ilavrita handles that by enqueueing a reindex for the affected types. The documentation is explicit about the temporary state: until the backfill finishes, a search by the new parameter can find resources indexed since the definition was added while older resources are still being processed.

That is an important operational limit. “Custom search parameters work” without describing the backfill window would be an incomplete claim.

A 400 can be safer than a 200

For an integration engineer, a hard failure is often cheaper than a plausible wrong answer.

A 400 with an OperationOutcome can be handled immediately:

  • remove the unsupported criterion;
  • choose a server-supported query;
  • perform the missing filtering in a layer that can do so safely; or
  • decide that the server is not compatible with the integration.

A 200 containing a wider result set can survive tests, staging, and code review because every individual resource looks legitimate.

That does not mean strict refusal is universally the right FHIR server policy. R4's lenient default exists for interoperability reasons, including intermediaries that add parameters outside a client's control.

For Ilavrita's current developer-preview stage, we choose observability over leniency: if the server did not answer the filter, we want that fact to be impossible to miss.

What this post does not prove

This article is anchored to the Payload-synced dev source metadata at revision:

text
946221398bc8fa8731838e98e3d040c102df7b63

The synced documents describe the search registry, supported modifiers, authorization model, CapabilityStatement behavior, and refusal policy used above.

There is documentation drift in the current source set. In particular, parts of docs/known-limitations.md, ROADMAP.md, deployment documentation, and older design prose still contain statements that conflict with newer capability tables. For example, the same known-limitations document has current tables marking batch and conditional interactions as working while an older transaction subsection says they are refused. Those stale paragraphs are not used as evidence for the search claims in this post.

The connected GitHub file reader was not available in this publishing environment, so this run did not execute a clean-clone test suite or inspect current test files directly. The source revision comes from the live Payload docs metadata, and implementation claims are limited to the mutually consistent current synced documentation. That is weaker evidence than a clean-clone verification, and it is stated here rather than hidden.

This is also not a production-readiness claim. Ilavrita remains a developer preview. Use synthetic data only. The project has not completed an external security review and makes no HIPAA, GDPR, EHDS, or certification claim.

The rule we want contributors to challenge

The useful invariant is simple:

If Ilavrita accepts a search criterion, it should execute the criterion with the semantics it advertises. If it cannot, the failure should be visible.

That rule is testable.

If you find a parameter that is accepted but not applied, a modifier that changes meaning silently, a CapabilityStatement entry the server cannot execute, or an authorization predicate applied after retrieval rather than in the query, that is not an enhancement request.

It is a correctness bug.

Start with the documentation, check known limitations, and compare the current boundary with the roadmap.


Evidence and references

Back to blog

Why Ilavrita Refuses Unsupported FHIR Search Parameters · Ilavrita