Documentation Index
Fetch the complete documentation index at: https://docs.omophub.com/llms.txt
Use this file to discover all available pages before exploring further.
1. Overview
HAPI FHIR can delegate terminology operations to a remote FHIR terminology server for code validation, concept lookup, and cross-vocabulary translation. Point it athttps://fhir.omophub.com/fhir/r4 and you get all 130+ OMOP vocabularies (SNOMED CT, LOINC, RxNorm, ICD-10, NDC, UCUM, …) without downloading ATHENA and managing a local PostgreSQL vocabulary database.
This guide covers two deployment shapes:
- HAPI FHIR JPA Starter (
hapiproject/hapi:latestDocker image) - the most common “just run HAPI” setup. Needs a small reverse-proxy workaround because the starter’s config schema does not expose a Bearer-token field. - Custom Spring Boot HAPI build - when you embed
HapiFhirContext+RemoteTerminologyServiceValidationSupportin your own Spring Boot app, you can attach aBearerTokenAuthInterceptordirectly and skip the proxy.
OMOPHub serves R4, R5, and R6. This guide uses R4 throughout because it’s what HAPI FHIR JPA Starter and most production HAPI deployments run. To target R5 or R6, replace
/fhir/r4 with /fhir/r5 or /fhir/r6 in your config - both the terminology endpoints and the CapabilityStatement at /metadata work identically.2. How HAPI Consumes OMOPHub
HAPI FHIR’sRemoteTerminologyServiceValidationSupport validates a code in three steps:
Discovery - CodeSystem search
GET /fhir/r4/CodeSystem?url=http://loinc.org - “does this server know LOINC?”. HAPI’s client expects a FHIR Bundle (type searchset) with at least one CodeSystem entry. OMOPHub returns a lightweight stub with content: "not-present", which tells HAPI the server supports LOINC via operations but doesn’t host the full concept list as a resource. If the Bundle is empty, HAPI skips OMOPHub entirely for that vocabulary.Code validation
GET /fhir/r4/CodeSystem/$validate-code?url=http://loinc.org&code=2951-2 - HAPI asks OMOPHub whether 2951-2 is a valid LOINC code and (optionally) whether the display text matches. OMOPHub’s handler dispatches on the FHIR system URI, looks up the concept in the underlying OMOP schema, and returns a FHIR Parameters resource with result: boolean and the canonical display string.Fallback
If OMOPHub returns
result: false or the HTTP request fails, HAPI’s validation chain falls through to whatever other support classes it has configured (in-memory default value sets, local CodeSystems loaded into the JPA database, etc.). This makes OMOPHub safely additive - it never blocks validation that HAPI could answer locally.$lookup (single-code lookup for display/designations/properties) and $translate (cross-vocabulary mapping via Maps to). Those are wired through the same config; no extra setup.
3. Path A: HAPI FHIR JPA Starter (Docker)
This is the path if you’re running the stockhapiproject/hapi:latest image. The challenge: the JPA Starter’s YAML schema has no auth field on its remote-terminology config. It accepts system and url for each provider, but it never calls addClientInterceptor() on the resulting validation-support bean, so Bearer tokens can’t be attached through configuration alone. We work around this with a tiny reverse proxy that injects the Authorization header on outbound requests.
3.1 application.yaml overlay
Mount this file into the container at /app/config/application.yaml:
application.yaml
system: '*'makes OMOPHub the catch-all terminology authority for every code system. If you want to scope it (e.g. SNOMED via OMOPHub, ICD-9 via a different server), register multiple entries and set eachsystemto a specific canonical URI.url:must point at your reverse proxy, not at OMOPHub directly - the proxy is what adds theAuthorizationheader. Trailing slash required.validation.requests_enabled/responses_enabledare off by default in the JPA Starter - flip them on so validation actually runs against incoming and outgoing resources.
3.2 Reverse proxy (nginx)
This proxy listens on port 8080, forwards everything tohttps://fhir.omophub.com, and injects Authorization: Bearer $OMOPHUB_CLIENT_ID on every request.
nginx.conf
3.3 Docker Compose
A minimal stack - HAPI + Postgres + the auth-injecting proxy:docker-compose.yml
4. Path B: Custom Spring Boot HAPI Build
If you’re embedding HAPI FHIR into your own Spring Boot application (as opposed to running the JPA Starter image), you can wire the remote terminology validator programmatically - no proxy needed. TheBearerTokenAuthInterceptor attaches to the HTTP client before every outbound call.
4.1 Static Bearer token
TerminologyConfig.java
ValidationSupportChain:
InMemoryTerminologyServerValidationSupport so your code system queries reach OMOPHub first; the in-memory support handles a few HL7-standard value sets that OMOPHub doesn’t.
4.2 OAuth2 client_credentials (alternative)
OMOPHub’s/oauth2/token endpoint accepts RFC 6749 client_credentials with either client_secret_basic (the Spring Security default) or client_secret_post. If your deployment prefers rotating OAuth2 tokens to static bearer keys, wire it via Spring Security:
application.yml
/oauth2/token endpoint is a minimal RFC 6749 client_credentials shim: it accepts either client_secret_basic (the Spring Security default) or client_secret_post client authentication, and it does not validate client_secret - your OMOPHub API key is the sole credential, passed as client_id. Spring’s config schema requires client-secret to be non-empty, so supply any non-empty placeholder (unused, not-required, etc.); OMOPHub ignores the value either way.
Then install an interceptor that fetches a token from the authorized-client manager and passes it as a Bearer header:
{access_token, token_type, expires_in}) - but the round trip does exercise token refresh behavior, which is useful for enterprise deployments that rotate credentials.
5. Verify It Works
Walk through these curls against your HAPI server after startup. Every command should produce the expected output - if any fails, jump to Troubleshooting.5.1 HAPI itself is up
5.2 CodeSystem discovery reaches OMOPHub
HAPI’s validator calls the FHIR CodeSystem search-type endpoint to check which systems OMOPHub serves. You can trigger the same call HAPI would make:result: false and a message like "CodeSystem is unknown and can't be validated: http://loinc.org", the remote terminology service wasn’t consulted - check the config path first, then the proxy’s auth header (see troubleshooting).
5.3 Bogus code is rejected
5.4 SNOMED works the same way
5.5 Direct check against OMOPHub
As a sanity check independent of HAPI, curl OMOPHub directly (or through the proxy):6. Supported Operations
HAPI’sRemoteTerminologyServiceValidationSupport consumes this subset of OMOPHub’s FHIR Terminology Service:
| Operation | OMOPHub endpoint | HAPI trigger |
|---|---|---|
| CodeSystem discovery | GET /fhir/r4/CodeSystem?url=<uri> | Every time HAPI first needs to resolve a system |
| CodeSystem instance read | GET /fhir/r4/CodeSystem/{id} | HAPI follow-up after discovery |
| Code validation | GET/POST /fhir/r4/CodeSystem/$validate-code | $validate-code operation + resource validation with coded fields |
| Concept lookup | GET/POST /fhir/r4/CodeSystem/$lookup | $lookup operation + display-text resolution |
| Cross-vocabulary translation | GET/POST /fhir/r4/ConceptMap/$translate | $translate operation |
| Subsumption testing | GET/POST /fhir/r4/CodeSystem/$subsumes | $subsumes operation |
| ValueSet expansion | GET/POST /fhir/r4/ValueSet/$expand | $expand operation, CQL engine, some validators |
| ValueSet support check | GET /fhir/r4/ValueSet?url=<uri> | Pre-flight for $expand / $validate-code |
$find-matches, $closure, $diff, and FHIR Batch Bundles. See the FHIR Integration guide for the full operation surface including those.
Version routing
All the routes above accept an optional version prefix:/fhir/r4/...- FHIR R4 (what HAPI JPA Starter uses by default)/fhir/r5/...- FHIR R5 (point HAPI 7.x +HAPI_FHIR_FHIR_VERSION: R5here)/fhir/r6/...- FHIR R6 (draft)
X-Vocab-Release request header or the vocab_release query parameter. See Vocabulary Versions for the full release history.
Error envelope
All 4xx/5xx responses from/fhir/* paths return a FHIR OperationOutcome resource (not the generic REST JSON envelope), so HAPI’s validator parses them cleanly and surfaces the diagnostics field in its own logs. For example, a missing API key returns:
7. Troubleshooting
CodeSystem is unknown and can't be validated: http://loinc.org on every $validate-code
HAPI’s remote terminology chain never reached OMOPHub. Three likely causes:
- Config not loaded. Check
docker exec <container> env | grep SPRING_CONFIG_ADDITIONAL_LOCATIONand confirm the overlay file is mounted. HAPI silently ignores unknown config keys, so a typo (e.g.remote_terminologyvsremote_terminology_service) yields no startup error - only the “unknown” validator error at query time. - Auth failing at the proxy. Run
docker logs <proxy>and look for401responses from the upstream. YourOMOPHUB_CLIENT_IDenv var may not be populated inside the nginx container -envsubstonly substitutes variables that are actually exported. - HAPI connecting but hitting a parse error. Check HAPI’s logs for
HAPI-1359: Failed to parse response. If the stack trace mentions Woodstox / StAX / XML, HAPI’s client fell into XML-parse mode because theAcceptheader negotiated XML first. OMOPHub prefers JSON on q-value ties, so this shouldn’t happen with the stock HAPI client - but a customIGenericClientwithsetEncoding(EncodingEnum.XML)will fail. Remove the XML encoding preference and it works.
HAPI-1359: Failed to parse response ... HAPI-1684: Unknown resource name "resourceType"
Classic XML-parser-reading-JSON error (the row/col coords refer to Woodstox output). OMOPHub’s content negotiation returns JSON whenever the client advertises JSON support at equal-or-higher q-value than XML, which matches HAPI’s default. If you’re seeing this error, your HAPI client is either:
- Configured to request XML only (
Accept: application/fhir+xml), or - Forcing XML via
setEncoding(EncodingEnum.XML)- switch toJSON.
No live upstreams from nginx after network restart
If the Docker network reshuffles IPs and nginx caches a stale upstream, restart the proxy container: docker compose restart omophub-proxy. Optionally add resolver 127.0.0.11 valid=5s; and set the upstream via a variable to force DNS re-resolution on every request.
Unknown code system URI: http://example.com/unknown
OMOPHub returned an OperationOutcome saying the URI isn’t recognized. Check that the system URI in your HAPI request matches one of the supported FHIR system URIs. OMOPHub suggests the closest match in its error message - use that.
8. Next Steps
FHIR Integration
The full OMOPHub FHIR Terminology Service surface -
$find-matches, $closure, $diff, Batch Bundles, plus the OMOP FHIR Resolver for ETL pipelines.EHRbase / openEHR
Point EHRbase at OMOPHub for template terminology validation. Uses the same FHIR endpoints as HAPI, with OAuth2 client_credentials as the native auth path.
EHR Integration
CDS Hooks, SMART on FHIR apps, and sidecar architectures that resolve vocabularies at the point of care.