The short answer
HAPI FHIR has two ways to validate writes. The request validating interceptor checks resources on their way into the server (creates, updates, operations, transactions) and can fail the request with HTTP 422.1 The repository validating interceptor, for the JPA server, checks resources just before they’re stored, using storage pointcuts, so it also covers writes made through internal Java APIs; creates and updates that don’t conform to the rules are rejected and rolled back by default.2
For an AI agent writing to a FHIR server, prefer the repository interceptor. HAPI’s own docs list what request validation misses: changes made through Java API calls or other interceptors, and the results of FHIR PATCH, since a patch can pass validation and still produce an invalid resource.2
This is about data integrity in records systems, not clinical judgment: validation checks shape and profile conformance, not whether the content is medically right.
Deedglass
The server said “saved”. Deedglass looked.
Deedglass reads it back
Two places to validate
| Request validating interceptor | Repository validating interceptor | |
|---|---|---|
| Runs | As requests arrive (with a response twin for outgoing data)1 | Just before storage, on JPA storage pointcuts2 |
| Covers | Incoming HTTP payloads | HTTP and internal Java API writes2 |
| PATCH results | Not validated2 | Validated before saving2 |
| On failure | Can return HTTP 4221 | Rejects and rolls back by default; or tag instead with neverReject()2 |
| Configure | Failure severity, response headers1 | Rules per resource type and profile2 |
| Needs | Any HAPI server | The JPA server (or a server that calls the same pointcuts)2 |
HAPI describes the repository interceptor as “a different and potentially more powerful way of validating data” when you use the JPA server.1
Why PATCH is the deciding case
Agents like PATCH. It’s the natural way to say “change this one field,” and it’s smaller and cheaper than reading a resource, editing it and writing it back. But a PATCH document is not a resource. Request validation sees the patch instructions, which can be perfectly valid, and never sees the resource they produce.2
So an agent’s PATCH that removes a required element, or sets a code that breaks the profile, can pass request validation and be stored. The repository interceptor validates the result of the patch before it’s saved.2
Our FHIR writes that said success post shows the wider version of this problem: a 200 or 201 from a FHIR server means accepted, not necessarily stored as meant.
Building repository rules
The docs’ pattern, in outline: get a RepositoryValidatingRuleBuilder from the Spring context, then build rules per resource type and pass them to the interceptor’s constructor.2 For example, rules that say: Patient resources must declare at least a given profile, and must validate against the profiles they declare.
Code What the screen or file shows, copied as is.
List<IRepositoryValidatingRule> rules = ruleBuilder
.forResourcesOfType("Patient")
.requireAtLeastProfile("http://example.org/StructureDefinition/my-patient")
.and()
.requireValidationToDeclaredProfiles()
.build();
interceptorService.registerInterceptor(new RepositoryValidatingInterceptor(myFhirContext, rules));
That’s a sketch following the documented builder methods; check exact signatures for your HAPI version. By default, ERROR and FATAL validation messages cause rejection; rejectOnSeverity(...) lowers the bar to WARNING.2
During a rollout, neverReject() with tagOnSeverity(...) stores the change and tags it instead of refusing it.2 That’s useful for a week of measuring how many of an agent’s writes would fail. It’s the wrong setting to leave on: tagged-but-stored is still stored.
What validation doesn’t tell you
Validation answers “is this resource well-formed against these profiles?” It doesn’t answer:
- Whether it’s the right resource. A valid Observation on the wrong patient passes.
- Whether the server stored exactly what was sent. Some servers drop or rewrite fields they don’t support. A read-back check after the write is how you find out.
- Whether the write should have happened. That’s a policy and approval question. Our post on AI agents writing to patient records covers approvals and records for those writes.
Measuring before you enforce
Turning on strict validation against an existing server can break integrations that have been sending slightly-off resources for years. A safer rollout, using the options above:
- Week one: tag, don’t reject. Use
neverReject()withtagOnSeverity(...)so failing writes are stored but marked.2 - Count tagged writes by client. Separate the agent’s writes from your existing integrations; you may find the agent is cleaner than the legacy feed, or the opposite.
- Fix or exempt. Correct the agent’s tool so it sends conforming resources, and decide what to do about older clients.
- Week two: reject at ERROR for the agent’s resource types, and keep watching the counts.
The goal is a server where an agent’s malformed write fails loudly at the moment it happens, with an error the agent has to report, rather than a record that quietly fails validation months later in someone else’s system.
A setup for agent writes
- Repository validation on, rejecting at ERROR, with rules for every resource type the agent writes.
- Profiles declared and required, so “valid” means “valid against your implementation guide,” not just base FHIR.
- PATCH allowed only where repository validation runs. On a plain server without storage pointcuts, have the agent send full resources instead.
- A read-back after each write to confirm what was stored.
- Validation failures surfaced to the agent as errors it must report, not retry blindly.
- Writes go through an approval step where a person needs to agree, as covered in the post linked above.
A checklist
-
RepositoryValidatingInterceptorregistered on the JPA server. - Rules for each resource type the agent can write.
- Failure severity set deliberately;
neverReject()not left on. - PATCH only where storage-level validation runs.
- Read-back after writes.
- Validation errors logged with the agent session that caused them.
For the basics of the standard, see the FHIR glossary entry. To see what the agents configured on your machine could write, paste the config into the check page or run npx deedglass scan (how the grade works); to try tool-call rules in your browser, open the playground.
Written with AI assistance and checked against our claims register.