Base URL: https://sandbox.fhir.et · validates against the Ethiopia Base IG (et.fhir.core) · ← dashboard
This sandbox checks whether FHIR resources conform to Ethiopia's official FHIR
profiles. Send a resource (or a transaction bundle) and get back an OperationOutcome
with any conformance errors. There are three ways in: a raw $validate call, a
pass-through interceptor, and a batch test harness.
meta.profile. If a resource declares no profile, it's validated against base FHIR R4.
Running locally instead of deployed? Use http://localhost:8095.The whole stack — HAPI (the validation engine, with the ET IGs loaded), the always-on conformance interceptor, and the gateway (the single entrypoint) — comes up with one command:
cd hapi-sandbox docker compose up -d # start HAPI + interceptor + gateway docker compose ps # wait for hapi-sandbox + hapi-interceptor -> (healthy)
Then open http://localhost:8095/docs/ (this dashboard) or
http://localhost:8095/sim/simulator.html (the simulator), and POST to
http://localhost:8095/intercept. Locally the base URL is
http://localhost:8095; deployed it is https://sandbox.fhir.et (TLS handled
by the host's reverse proxy, which forwards to the gateway). After rebuilding an IG, force a fresh
install with docker compose down && rm -rf data && docker compose up -d.
POST the resource to $validate for its type. Use ?profile= to assert a
specific Ethiopian profile (or rely on the resource's meta.profile).
# Conformant ETPatient -> 200, no error/fatal issues
curl -X POST "https://sandbox.fhir.et/fhir/Patient/$validate?profile=http://fhir.et/core/StructureDefinition/ETPatient" \
-H "Content-Type: application/fhir+json" \
-d '{"resourceType":"Patient","meta":{"profile":["http://fhir.et/core/StructureDefinition/ETPatient"]},
"identifier":[{"system":"http://id.gov.et/nationalidentifier","value":"ET-1"}],
"name":[{"family":"Challa"}],"gender":"male"}'
A non-conformant resource (e.g. a marital status outside the required Ethiopian value set) comes back HTTP 422 with the failing issues listed in the OperationOutcome.
POST the whole bundle to Bundle/$validate; each entry is checked against its own
meta.profile.
curl -X POST "https://sandbox.fhir.et/fhir/Bundle/$validate" \ -H "Content-Type: application/fhir+json" -d @bundle.json
The server validates on write. POSTing a resource that declares an Ethiopian
meta.profile but doesn't conform is rejected (422); a
conformant one is stored (201).
curl -X POST "https://sandbox.fhir.et/fhir/Patient" \ -H "Content-Type: application/fhir+json" -d @patient.json
POST any resource or bundle to /intercept. It validates against the Ethiopian
profiles, returns the verdict in response headers, and forwards to the server — so an existing
system can be pointed at it unchanged.
curl -i -X POST "https://sandbox.fhir.et/intercept" \
-H "Content-Type: application/fhir+json" -d @bundle.json
# response headers:
X-ET-Validation: 0 error(s) in Bundle (3 entries)
X-ET-Validation-Report: <url-encoded JSON of the issues>
Prefer a UI? Open the actor simulator, pick a payload (e.g. the medication-dispenser bundle), and submit — you'll see the verdict and per-issue detail.
| URL | What it does |
|---|---|
/fhir/* | Full FHIR API (CRUD, search, $validate, /metadata) |
/fhir/{Type}/$validate | Validate a resource, get an OperationOutcome |
/intercept | Validate → return verdict headers → forward (conformance proxy) |
/sim/simulator.html | Browser actor simulator |
/docs/ | Dashboard · /docs/postman/… Postman collection |
Import the collection
and set the host variable to https://sandbox.fhir.et. It has ready-made
$validate (good/bad), create-with-validation, and interceptor requests.
A session captures a window of traffic, scores every request, audits what was stored, and produces an HTML report — the format used for the showcase and for onboarding a real system.
/intercept). The non-live battery instead replays a fixed set of curated
good/bad fixtures with no live traffic — deterministic regression, runs from a laptop or CI against
any server:cd harness && SHR_URL=https://sandbox.fhir.et/fhir ./run-tests.shharness/target/.)Step 1 — start it (sandbox operators, in the repo):
cd harness sdk env # Java 21 (per .sdkmanrc) ./test-session.sh # or: LABEL=hiv-demo ./test-session.sh
It restarts the interceptor for a clean session and starts a live feed.
Step 2 — send traffic. Point a real system's FHIR base URL at
https://sandbox.fhir.et/intercept and use it normally, or open the
simulator and submit. Each request prints a live
✓/✗ line. The interceptor records every verdict to
harness/target/sessions/validation-reports.json and the patients it saw to
patients.txt.
Step 3 — finish and read the report. Press Ctrl+C. The session then produces:
| Output | What it contains |
|---|---|
| terminal summary | ✓ N conformant / ✗ N with findings across the session |
| stored-data audit | re-validates everything stored for each seen patient via instance-level $validate |
harness/target/runs/<timestamp>/session.html | the report — auto-opens in your browser: a verdict table (click a row for its OperationOutcome issues) beside the embedded stored-data audit report |
Already have session data and just want to rebuild the report? ./test-session.sh report.
/intercept returns its
verdict inline in the X-ET-Validation / X-ET-Validation-Report response
headers (what the simulator shows per submit). The session.html dashboard is the
per-session rollup of all those calls plus the stored-data audit.Validation rules come from the official Ethiopia IGs loaded into the sandbox:
et.fhir.core (ETBase, the base),
et.fhir.surveillance, and
et.fhir.hiv — with more added as
they're published. A resource's meta.profile selects which profile it's checked against
(e.g. an HIV dispense → hiv-medication-dispense, which builds on surveillance → core).
The et.fhir.core.test IG
holds the curated valid fixtures the non-live battery validates; the intentionally-invalid
(negative) fixtures live with the harness as raw JSON (they can't be IG examples — SUSHI fails the
build on them).
Deployed sessions. The interceptor is a Karate mock server and the
non-live battery + auditor are Karate test runs. Today a live session against
sandbox.fhir.et is driven by an operator running test-session.sh on the
host; a hosted /session view (watch a live session in the browser, no local tooling) is
the planned next step.