The short answer
Check an agent-built FHIR resource against your profiles before you write it, and have the server check again when it stores it. The FHIR specification lists what validation can check: structure, cardinality, value domains, coding and CodeableConcept bindings, invariants, profiles, Questionnaire responses and business rules (the last defined outside the specification).1 You can run those checks with the HL7 validator jar, with a server’s $validate operation, or with rules on the server, such as HAPI’s repository validating interceptor.13
Business rules are the exception: the specification says they’re made outside it, such as checking for duplicates, checking that references resolve and checking that a user is authorized to do what they want.1 Those are yours to write. An agent can produce a perfectly valid resource that shouldn’t exist.
This post is about records and data quality, not clinical content: a valid resource can still be clinically wrong, and that’s not something a validator judges.
Deedglass
The server said “saved”. Deedglass looked.
Deedglass reads it back
What FHIR validation checks, and what it doesn’t
| Checked by validation1 | Example agent mistake it catches |
|---|---|
| Structure | An element in the wrong place, an unknown element |
| Cardinality | A required element missing, or repeated when it shouldn’t be |
| Value domains | A date in the wrong format |
| Coding bindings | A code not from the required value set |
| Invariants | A rule like “either a value or a reason it’s absent” broken |
| Profiles | Your implementation guide’s extra constraints not met |
| Business rules: made outside the specification1 | Example |
|---|---|
| Duplicate detection | A second Patient for the same person |
| Authorization | A write the agent wasn’t allowed to make |
| References that resolve | A reference to a Patient that doesn’t exist |
| Your workflow’s own rules | An Observation dated in the future when your workflow forbids it |
Three places to validate
| HL7 validator jar | $validate on the server |
Server-side rules | |
|---|---|---|---|
| Runs | Anywhere Java runs: CI, a sidecar, the agent’s tool | On the FHIR server, on request | On the server, on every write |
| Stores anything | No | No | Only if valid |
| Knows your IG | If you load it (-ig)1 |
If the server has the profiles1 | If rules reference them3 |
| Catches agent mistakes before the write | Yes | Yes | At the write |
| Can be skipped by the agent | Yes, if it’s a separate step | Yes | No |
The last row is the reason to use server-side rules as well: anything the agent’s tool does voluntarily, a buggy or steered agent can skip.
Option 1: the HL7 validator jar
The validator is a Java jar provided as part of the specification.1 The current build is validator_cli.jar, which the core library’s README says can be downloaded from the project’s GitHub releases.2 The specification’s example runs it against a file with a FHIR version, and describes -profile to check a specific profile and -ig to load an implementation guide.1 By default it uses a terminology server for code checks.1
In practice, the jar fits two places in an agent setup:
- In CI, against example resources your agent’s tools produce, so a change to the tool that breaks conformance fails the build.
- In the write path, as a check the tool runs before sending, for teams who can afford the extra time per write.
For the exact flags of the version you run, use the validator’s own help output and the HL7 Confluence page the README points to.2
Option 2: the $validate operation
Servers can check a resource against a profile when you POST it to $validate with a profile parameter, and return an OperationOutcome listing the issues. The specification’s example:
Code What the screen or file shows, copied as is.
POST [base]/Observation/$validate?profile=http://hl7.org/fhir/StructureDefinition/heartrate
Not every server supports it, and many validate only against profiles already registered with them.1 When it works, it’s the simplest check for an agent’s tool: validate, read the OperationOutcome, and only write if there are no errors. Feed the issues back to the agent as an error to fix, not a reason to retry the same resource.
Option 3: rules on the server
Server-side validation is the check the agent can’t skip. On a HAPI FHIR JPA server, the repository validating interceptor validates resources before storage, for writes from the HTTP API and internal Java calls, and rejects non-conforming creates and updates by default.3 It also validates the result of a PATCH, which request-time validation can’t. Our HAPI validation post compares the two HAPI options in detail.
Putting it together for agent writes
A layered setup:
- The agent’s tool validates first with
$validateor the jar, and refuses to send a resource with errors. - The server validates again at storage, so a tool bug or a steered agent can’t store a non-conforming resource.
- A read-back after the write confirms what was stored; our post on FHIR writes that said success shows why that’s needed even after validation passes.
- Duplicate checks (conditional create, or a search first) and reference checks, the business rules you own.
- A person approves writes that need one, as described in AI agents writing to patient records.
A checklist
- Your implementation guide’s profiles are what you validate against, not just base FHIR.
- The agent’s tool validates before writing and reports issues instead of retrying.
- The server validates at storage and rejects errors.
- CI runs the validator against sample outputs of the agent’s tools.
- A terminology setup that matches your code systems, or a documented decision to skip code checks.
- Duplicate detection, reference checks and authorization written as your own business rules.
For the basics, 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.