This page documents what OMOPHub supports, what has been tested against real clients, and where the boundaries are. Use it to evaluate compatibility before writing integration code.
1. FHIR R4 / R4B / R5 / R6 Terminology Operations
The FHIR Terminology Service is served at https://fhir.omophub.com/fhir/{r4,r4b,r5,r6}/. R4 is the default when no version prefix is provided.
| Operation | Resource | Status | Notes |
|---|
$lookup | CodeSystem | Production | GET and POST. Returns OMOP properties (concept-id, domain-id, concept-class-id, standard-concept, etc.) and Phoebe recommendations via property=recommended |
$validate-code | CodeSystem | Production | GET and POST. Validates code existence and optional display match |
$subsumes | CodeSystem | Production | Tests hierarchical subsumption between two concepts in the same system |
$find-matches | CodeSystem | Production | Semantic property-based matching - OMOPHub’s semantic search exposed in a FHIR envelope |
$translate | ConceptMap | Production | GET and POST. Maps between any two vocabularies via OMOP Maps to relationships |
$closure | ConceptMap | Production | Incremental transitive-closure maintenance for streaming concept sets |
$expand | ValueSet | Production | Expands implicit ValueSets ({system}?fhir_vs and {system}?fhir_vs=isa/{code}). Deterministic sort by concept_id for stable pagination |
$validate-code | ValueSet | Production | Validates code membership in an implicit ValueSet |
$diff | CodeSystem | Production | OMOPHub custom - compares concepts between two OMOP vocabulary releases. No FHIR spec equivalent |
GET /metadata | CapabilityStatement | Production | Returns declared resources, operations, and both JSON + XML formats |
GET /metadata?mode=terminology | TerminologyCapabilities | Production | Returns the full list of supported codeSystem URIs dynamically from the vocab config |
GET /CodeSystem?url=<uri> | CodeSystem (search-type) | Production | HAPI FHIR’s discovery call - returns a searchset Bundle with a per-vocabulary stub or empty Bundle for unknown URIs |
GET /CodeSystem (list-all) | CodeSystem (search-type) | Production | Returns all 20 per-vocab stubs + the unified OMOP omnibus CodeSystem |
GET /CodeSystem/{id} | CodeSystem (instance read) | Production | Per-vocab stub IDs (loinc, snomed, rxnorm, …) + release IDs (omop-v20250827) |
GET /ValueSet?url=<uri> | ValueSet (search-type) | Production | Preflight support check used by HAPI and EHRbase before $expand / $validate-code |
POST / (Batch Bundle) | Bundle | Production | FHIR batch Bundle with multiple entry.request items; metered per entry, not per call |
Not supported
| Capability | Reason |
|---|
FHIR clinical resources (Patient, Condition, Observation, …) | OMOPHub is a terminology service, not a clinical-data server |
FHIR REST interactions (create, update, delete, history, patch) | Read-only vocabulary data - content comes from OHDSI ATHENA releases |
| Extensional ValueSet storage | Only implicit ValueSets ({system}?fhir_vs). User-defined extensional ValueSets are on the roadmap |
| Full-fidelity XML Bundle serialization | Parameters, OperationOutcome, and single-resource responses serialize to clean FHIR XML. Bundle-shaped responses (search-type, batch) are best-effort - see the XML support matrix |
2. OMOPHub Concept Resolver
Purpose-built REST endpoints for FHIR-to-OMOP ETL pipelines. Returns OMOPHub’s native JSON envelope rather than FHIR Parameters.
| Endpoint | Description |
|---|
POST /v1/fhir/resolve | Single Coding → OMOP standard concept + domain + CDM target table + mapping type |
POST /v1/fhir/resolve/batch | Batch resolution, up to 100 codings per request. Metered per coding |
POST /v1/fhir/resolve/codeable-concept | Full CodeableConcept resolution with OHDSI vocabulary preference ranking and text fallback via semantic search |
See the FHIR Integration guide for request / response shapes and the FHIR → OMOP Standardization guide for the end-to-end pipeline pattern.
3. Supported FHIR Code System URIs
OMOPHub maps 19 canonical FHIR system URIs + 1 OMOP omnibus URL (total: 20 CodeSystems served via the Terminology Service). Source of truth is apps/api/src/config/fhir-vocabularies.ts - use GET /fhir/r4/metadata?mode=terminology to get the current list from the live service.
| FHIR System URI | OMOP vocabulary_id |
|---|
http://snomed.info/sct | SNOMED |
http://loinc.org | LOINC |
http://www.nlm.nih.gov/research/umls/rxnorm | RxNorm |
http://hl7.org/fhir/sid/icd-10 | ICD10 |
http://hl7.org/fhir/sid/icd-10-cm | ICD10CM |
http://hl7.org/fhir/sid/icd-10-pcs | ICD10PCS |
http://hl7.org/fhir/sid/icd-10-cn | ICD10CN |
http://hl7.org/fhir/sid/icd-10-gm | ICD10GM |
http://hl7.org/fhir/sid/icd-9-cm | ICD9CM |
http://hl7.org/fhir/sid/icd-9 | ICD9Proc |
http://hl7.org/fhir/sid/icd-9-cn | ICD9ProcCN |
http://hl7.org/fhir/sid/icd-o-3 | ICDO3 |
http://hl7.org/fhir/sid/cvx | CVX |
http://hl7.org/fhir/sid/ndc | NDC |
http://www.nlm.nih.gov/research/umls/hcpcs | HCPCS |
http://www.whocc.no/atc | ATC |
http://unitsofmeasure.org | UCUM |
urn:iso:std:iso:11073:10101 | MDC |
http://fdasis.nlm.nih.gov | UNII |
http://va.gov/terminology/medrt | MED-RT |
https://fhir-terminology.ohdsi.org | OMOP unified omnibus |
If your FHIR data uses a system URI not in this list, the Resolver returns a 400 error with an unknown_system code and a suggested match from the levenshtein-closest URI. You can bypass URI resolution by passing vocabulary_id directly instead of system.
4. Tested Client Scenarios
The following integration patterns have been validated against real clients.
HAPI FHIR Server
HAPI FHIR’s RemoteTerminologyServiceValidationSupport can delegate validation to OMOPHub. Two deployment shapes work, depending on whether you’re using the stock JPA Starter or building a custom Spring Boot HAPI app:
HAPI JPA Starter (hapiproject/hapi:* Docker image) - the stock image’s YAML schema does not expose a Bearer-token field for remote terminology services (AppProperties.RemoteSystem in the starter only binds system and url; TerminologyConfig never calls addClientInterceptor). Use a reverse-proxy pattern:
- Stand up nginx / Caddy / cloud gateway in front of OMOPHub
- Configure the proxy to inject
Authorization: Bearer $OMOPHUB_CLIENT_ID on every upstream request
- Point HAPI’s remote terminology URL at the proxy
- Config key:
hapi.fhir.remote_terminology_service.<name>.{system,url} - note that older blog posts reference hapi.fhir.validation.remote_terminology_service_urls, which is a different property path that the JPA Starter silently ignores
Custom Spring Boot HAPI app - use the HAPI FHIR library directly and attach a BearerTokenAuthInterceptor programmatically:
@Bean
public IValidationSupport omophubTerminologyService(FhirContext ctx) {
RemoteTerminologyServiceValidationSupport remote =
new RemoteTerminologyServiceValidationSupport(ctx);
remote.setBaseUrl("https://fhir.omophub.com/fhir/r4");
remote.addClientInterceptor(
new BearerTokenAuthInterceptor("oh_your_api_key"));
return remote;
}
- Verified operations:
$lookup, $validate-code, CodeSystem search-type (discovery call), GET /metadata
- Verified against: HAPI FHIR 8.8.0
- See: HAPI FHIR Integration for full config, verification checklist, and the
CodeSystem is unknown troubleshooting path
EHRbase (openEHR)
EHRbase delegates template-terminology validation to an external FHIR R4 server via Spring’s RemoteTerminologyServiceValidationSupport. Unlike HAPI JPA Starter, EHRbase supports OAuth2 client_credentials natively via spring.security.oauth2.client.* config blocks - and OMOPHub exposes a compatible token endpoint at POST /oauth2/token.
- Connection: Configure
validation.external-terminology.provider.omophub.url to https://fhir.omophub.com/fhir/r4/
- Auth: OAuth2
client_credentials - spring.security.oauth2.client.registration.omophub.client-id set to your OMOPHub API key, client-secret to any non-empty placeholder (OMOPHub’s shim doesn’t validate it)
- Verified operations: ValueSet search-type (discovery),
$expand, $validate-code, OAuth2 token exchange
- Verified against:
ehrbase/ehrbase:next with application.yml overlay
- End-to-end test result: invalid SNOMED code submitted via composition → EHRbase returns HTTP 422 with
"does not match any option from value set ..." - the full template upload → composition commit → $validate-code → reject loop is live
- See: EHRbase / openEHR Integration for the complete docker-compose and template-binding walkthrough
Direct REST / curl
All FHIR operations support both GET (query parameters) and POST (FHIR Parameters body):
# GET style
curl "https://fhir.omophub.com/fhir/r4/CodeSystem/\$lookup?\
system=http://snomed.info/sct&code=44054006" \
-H "Authorization: Bearer oh_your_api_key"
# POST style
curl -X POST "https://fhir.omophub.com/fhir/r4/CodeSystem/\$lookup" \
-H "Authorization: Bearer oh_your_api_key" \
-H "Content-Type: application/fhir+json" \
-d '{
"resourceType": "Parameters",
"parameter": [
{ "name": "system", "valueUri": "http://snomed.info/sct" },
{ "name": "code", "valueCode": "44054006" }
]
}'
Python SDK
import omophub
client = omophub.OMOPHub()
# Concept Resolver
result = client.fhir.resolve(
system="http://snomed.info/sct",
code="44054006",
resource_type="Condition",
)
# Batch resolve up to 100 codings
batch = client.fhir.resolve_batch([
{"system": "http://snomed.info/sct", "code": "44054006"},
{"system": "http://loinc.org", "code": "2339-0"},
])
# CodeableConcept with OHDSI vocabulary preference ranking
cc = client.fhir.resolve_codeable_concept(
coding=[
{"system": "http://snomed.info/sct", "code": "44054006"},
{"system": "http://hl7.org/fhir/sid/icd-10-cm", "code": "E11.9"},
],
resource_type="Condition",
)
R SDK
R6 object-oriented style via $-dereferencing:
library(omophub)
client <- OMOPHubClient$new(api_key = "oh_your_api_key")
result <- client$fhir$resolve(
system = "http://snomed.info/sct",
code = "44054006",
resource_type = "Condition"
)
MCP Server (AI agents)
The OMOPHub MCP Server exposes 11 tools to Claude, Cursor, VS Code, and any MCP-compatible AI client. Install with npx -y @omophub/omophub-mcp. See AI Onboarding and the MCP Tools Reference for the full tool list.
5. Authentication
| Pattern | Supported | Notes |
|---|
Bearer token (Authorization: Bearer oh_xxx) | Production | Primary auth for REST and FHIR endpoints. Transmitted over TLS 1.2+ |
OAuth2 client_credentials (RFC 6749) | Production | POST https://fhir.omophub.com/oauth2/token. Accepts both client_secret_basic (Spring Security default) and client_secret_post. client_secret is not validated - the client_id (your API key) is the sole credential |
| API key in query parameter | No | Header-only for security |
| No auth / open access | No | All endpoints require authentication |
The OAuth2 shim is specifically designed to satisfy Spring Security OAuth2 clients (HAPI FHIR custom builds, EHRbase, any Java/Kotlin Spring Boot stack). The returned access_token is your API key wrapped in an RFC 6749 envelope - the token exchange is nominal but lets Spring-based clients treat OMOPHub like any other OAuth2-protected resource.
6. Content Negotiation
| Content Type | Supported | Notes |
|---|
application/fhir+json | Production | Default |
application/json | Production | Treated as FHIR JSON |
application/fhir+xml | Production | Served when strictly preferred via Accept header q-values. Clean for Parameters, OperationOutcome, CapabilityStatement, ValueSet, ConceptMap, bare CodeSystem reads. Bundle-shaped responses (CodeSystem/ValueSet search-type, batch Bundles) are best-effort - request JSON for those endpoints |
*/* | Production | Resolves to JSON via wildcard content negotiation |
JSON wins on ties. Content negotiation follows RFC 7231 §5.3.2 q-value semantics; HAPI FHIR’s default client sends both JSON and XML at q=1.0, so the tie-break selects JSON. See the Content Type section of the FHIR Terminology Service overview for the full XML support matrix.
7. Rate Limits
FHIR endpoints share the same rate-limit and metering pool as the REST API. Each FHIR operation counts as one API call. Batch and bulk endpoints (POST /v1/fhir/resolve/batch, POST /fhir/r4/ batch Bundles, /v1/concepts/batch, /v1/search/bulk, and so on) meter per item inside the batch - a 100-item batch counts as 100 calls against your monthly quota.
See Rate Limits for the headers (ratelimit-limit, ratelimit-remaining, ratelimit-reset, ratelimit-window, retry-after) and the contact path for requesting higher limits.
8. Vocabulary Coverage
OMOPHub serves the full OHDSI ATHENA vocabulary set except licensed vocabularies.
See Known Limitations for the full boundary discussion, and Vocabulary Releases for the current release cadence.