A pre-flight validator for FHIR REST search queries. Given a URL like Patient?gender=male, it checks syntax and semantics against a FHIR server's declared capabilities before you send the request.
FHIR servers expose what they support via a CapabilityStatement (fetched from /metadata). Search parameters, modifiers, and comparators vary by server. This tool validates a query URL against that metadata so you catch unsupported resource types, unknown search params, invalid modifiers, and bad values early.
flowchart TB
subgraph entry [EntryPoints]
CLI[scripts/run_validator.py]
Notebook[examples/notebooks]
Lib[Python_import]
end
subgraph services [services]
ValidatorService[FhirValidatorService]
end
subgraph config [config]
Settings[settings.py]
end
subgraph core [core]
Parser[query_parser]
Validator[validator]
Codesets[codeset_validator]
end
subgraph infra [infrastructure]
CapIndex[capability_index]
end
subgraph external [External]
FHIRServer[FHIR_Server_metadata]
end
CLI --> ValidatorService
Notebook --> ValidatorService
Lib --> ValidatorService
ValidatorService --> Settings
ValidatorService --> CapIndex
ValidatorService --> Validator
Validator --> Parser
Validator --> Codesets
CapIndex --> FHIRServer
- Parse the query URL into resource type and query parameters
- Load the server's CapabilityStatement from
/metadata - Validate against allowed resource types, search params, modifiers, and comparators
- Apply extra rules for known value sets and Patient identifiers
| Check | Description | Example error |
|---|---|---|
| Resource type | Must be supported by the server | Resource type 'Foo' is not supported. |
| Search parameters | Must exist for that resource type | Search param 'foo' not allowed for resource |
| Modifiers / comparators | e.g. :exact, :gt must be allowed for that param |
Modifier/comparator 'missing' not allowed for param 'gender' |
| Static value sets | Known coded values for specific params | Value 'fe' for 'Patient.gender' is not allowed. |
| Patient identifiers | Numeric, 8–10 digits, not all identical | Patient.identifier '11111111' invalid: Identifier cannot be all identical digits. |
Patient.gender—male,female,other,unknownAllergyIntolerance.verification-status—unconfirmed,confirmed,refuted,entered-in-errorAllergyIntolerance.clinical-status—active,inactive,resolved
Built-in public test servers require no authentication — suitable for local dev, integration tests, and manual validation. Registry informed by the HL7 Public Test Servers list.
Select via FHIR_DEFAULT_SERVER_KEY in config/.env.local:
| Key | Server | Base URL | Auth |
|---|---|---|---|
hapi (default) |
HAPI FHIR Reference Server (R4) | https://hapi.fhir.org/baseR4 |
None |
firely |
Firely Public Server (R4) | https://server.fire.ly |
None |
spark |
Spark FHIR Reference Server (R4) | https://spark.incendi.no/fhir |
None |
wildfhir |
AEGIS WildFHIR Community (R4) | https://wildfhir.wildfhir.org/r4 |
None |
Full details: docs/public-test-servers.md
# Use Firely instead of HAPI
export FHIR_DEFAULT_SERVER_KEY=firely
fhir-validate "https://server.fire.ly/Patient?gender=male"
# Test all public servers
python3 scripts/run_all_tests.pyOverride with FHIR_METADATA_URL and FHIR_SERVER_BASE for custom (possibly authenticated) servers.
fhir_query_validator-DS-fhir/
├── config/
│ └── .env.example # Environment variable template
├── docs/
│ └── adr/ # Architectural decision records
├── examples/
│ └── notebooks/ # Demo Jupyter notebook
├── planning/ # Sprint notes and roadmap
├── scripts/
│ ├── run_validator.py # CLI entry point
│ └── run_all_tests.py # Multi-server integration script
├── src/
│ └── fhir_validator_agent/
│ ├── config/ # Environment and server settings
│ ├── core/ # Parsing and validation rules
│ ├── infrastructure/ # HTTP calls (CapabilityStatement, OAuth)
│ └── services/ # Application orchestration
└── tests/
├── unit/ # Fast, offline unit tests
├── regression/ # JSON-driven regression cases
└── integration/ # Live FHIR server tests (optional)
Get validating in under two minutes. No configuration required — defaults target the public HAPI FHIR R4 server.
- Python 3.11+
pipand a virtual environment (recommended)
git clone <repo-url>
cd fhir_query_validator
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev,notebook]"fhir-validate "https://hapi.fhir.org/baseR4/Patient?gender=male"Expected output:
Valid: True
Try an invalid query to see structured errors:
fhir-validate "https://hapi.fhir.org/baseR4/Patient?gender=fe"Valid: False
Errors:
- Value 'fe' for 'Patient.gender' is not allowed. Allowed values: {'male', 'female', 'other', 'unknown'}
from fhir_validator_agent import FhirValidatorService
service = FhirValidatorService.from_env()
result = service.validate_query("https://hapi.fhir.org/baseR4/Patient?gender=male")
print(result)
# {"valid": True, "errors": []}
FhirValidatorAgentis a backward-compatible alias forFhirValidatorService.
Defaults use HAPI R4. To switch servers or enable OAuth:
cp config/.env.example config/.env.localEdit config/.env.local — for example, to use Spark (no auth):
FHIR_DEFAULT_SERVER_KEY=spark
FHIR_USE_AUTH=falseSee Configuration for all variables.
| Entry point | Command |
|---|---|
| CLI (installed) | fhir-validate "<url>" |
| Script wrapper | python3 scripts/run_validator.py "<url>" |
| Demo notebook | jupyter notebook examples/notebooks/FHIR_search_validator_demo.ipynb |
| Multi-server script | python3 scripts/run_all_tests.py |
The test suite uses pytest. All offline tests (unit + regression) run without network access and are suitable for CI.
Install dev dependencies (included if you followed Quick Start):
pip install -e ".[dev]"| What you want | Command | Network required? |
|---|---|---|
| Run all offline tests | pytest -m "not integration" |
No |
| Unit tests only | make test-unit |
No |
| Regression suite only | make test-regression |
No |
| Coverage report (80% gate) | make test-cov |
No |
| Integration tests (live servers) | make test-integration |
Yes |
| Everything | pytest |
Partial |
All offline tests (recommended before every commit):
pytest -m "not integration" -vUnit tests — parser, validator, config, CLI, service (mocked):
make test-unit
# equivalent: pytest tests/unit -vRegression tests — fixed scenarios in tests/regression/cases.json that guard known good/bad behavior:
make test-regression
# equivalent: pytest tests/regression -m regression -vCoverage — reports coverage on core/ and services/ with an 80% minimum:
make test-cov
# equivalent: pytest -m "not integration" --cov=fhir_validator_agent --cov-report=term-missingIntegration tests — hits live HAPI FHIR server (skippable if offline):
make test-integration
# equivalent: pytest tests/integration -m integration -vMulti-server manual script — HAPI and Firely positive/negative scenarios:
python3 scripts/run_all_tests.pytests/
├── conftest.py # Shared fixtures (CapabilityStatement, validator)
├── unit/ # Isolated unit tests per module
├── regression/
│ ├── cases.json # Fixed regression scenarios
│ └── test_regression.py # Parametrized regression runner
└── integration/ # Live FHIR server tests
| Directory | Purpose | Speed |
|---|---|---|
tests/unit/ |
Module-level tests (parser, validator, settings, CLI) | Fast |
tests/regression/ |
JSON-driven cases — prevents validation regressions | Fast |
tests/integration/ |
End-to-end against public FHIR servers | Slow (network) |
Append an entry to tests/regression/cases.json:
{
"id": "patient_gender_unknown_value",
"description": "Reject unknown gender coded value",
"query": "https://hapi.fhir.org/baseR4/Patient?gender=fe",
"expected_valid": false,
"expected_error_substrings": ["Patient.gender", "fe"]
}Then run:
make test-regression| Marker | Meaning |
|---|---|
integration |
Calls a live FHIR server |
regression |
Fixed case from cases.json |
Exclude integration tests: pytest -m "not integration"
Run only regression: pytest -m regression
| Variable | Default | Description |
|---|---|---|
FHIR_DEFAULT_SERVER_KEY |
hapi |
Preset key: hapi, firely, spark, wildfhir |
FHIR_METADATA_URL |
(from preset) | Override CapabilityStatement URL |
FHIR_SERVER_BASE |
(from preset) | Override FHIR server base URL |
FHIR_USE_AUTH |
false |
Enable OAuth client-credentials flow |
TOKEN_URL |
— | OAuth token endpoint |
CLIENT_ID |
— | OAuth client ID |
CLIENT_SECRET |
— | OAuth client secret |
Copy config/.env.example to config/.env.local and edit. Never commit .env.local — it is gitignored.
Edit src/fhir_validator_agent/core/codeset_validator.py and add an entry to STATIC_VALUESETS:
STATIC_VALUESETS = {
"Patient.gender": {"male", "female", "other", "unknown"},
# Add new entries as "ResourceType.param_name": {allowed, values}
}python scripts/run_all_tests.py| Doc | Description |
|---|---|
| Specification (SDD) | Authoritative implementation spec for agents and developers |
| PRD | Problem statement, approach, in/out of scope |
| ADR 001 | Architecture and design decisions |
| Configuration | Environment variables and troubleshooting |
| Development | Local setup, project layout, adding tests |
| API Reference | FhirValidatorService, CLI, registry |
| Public Test Servers | No-auth FHIR sandboxes for testing |
| Sample Output | Captured CLI, API, and test results |
| E2E Checklist | Release sign-off checklist |
| CONTRIBUTING | How to contribute |
| CHANGELOG | Release history |
| Utility Guide (Word) | Introduction, scope, tests, configuration appendix |
See the 3-Week Implementation Plan for the full delivery schedule (delivery date: May 15, 2026):
| Week | Dates | Focus | Plan |
|---|---|---|---|
| Week 1 | Apr 24 – Apr 30 | Foundation — package, CLI, unit tests, PRD/ADR | week-1-foundation.md |
| Week 2 | May 1 – May 7 | Implementation — hardening, integration, E2E tests | week-2-implementation.md |
| Week 3 | May 8 – May 15 | Documentation & release — guides, CI, v0.1.0 | week-3-documentation-release.md |