Skip to main content
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.
OperationResourceStatusNotes
$lookupCodeSystemProductionGET and POST. Returns OMOP properties (concept-id, domain-id, concept-class-id, standard-concept, etc.) and Phoebe recommendations via property=recommended
$validate-codeCodeSystemProductionGET and POST. Validates code existence and optional display match
$subsumesCodeSystemProductionTests hierarchical subsumption between two concepts in the same system
$find-matchesCodeSystemProductionSemantic property-based matching - OMOPHub’s semantic search exposed in a FHIR envelope
$translateConceptMapProductionGET and POST. Maps between any two vocabularies via OMOP Maps to relationships
$closureConceptMapProductionIncremental transitive-closure maintenance for streaming concept sets
$expandValueSetProductionExpands implicit ValueSets ({system}?fhir_vs and {system}?fhir_vs=isa/{code}). Deterministic sort by concept_id for stable pagination
$validate-codeValueSetProductionValidates code membership in an implicit ValueSet
$diffCodeSystemProductionOMOPHub custom - compares concepts between two OMOP vocabulary releases. No FHIR spec equivalent
GET /metadataCapabilityStatementProductionReturns declared resources, operations, and both JSON + XML formats
GET /metadata?mode=terminologyTerminologyCapabilitiesProductionReturns the full list of supported codeSystem URIs dynamically from the vocab config
GET /CodeSystem?url=<uri>CodeSystem (search-type)ProductionHAPI 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)ProductionReturns all 20 per-vocab stubs + the unified OMOP omnibus CodeSystem
GET /CodeSystem/{id}CodeSystem (instance read)ProductionPer-vocab stub IDs (loinc, snomed, rxnorm, …) + release IDs (omop-v20250827)
GET /ValueSet?url=<uri>ValueSet (search-type)ProductionPreflight support check used by HAPI and EHRbase before $expand / $validate-code
POST / (Batch Bundle)BundleProductionFHIR batch Bundle with multiple entry.request items; metered per entry, not per call

Not supported

CapabilityReason
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 storageOnly implicit ValueSets ({system}?fhir_vs). User-defined extensional ValueSets are on the roadmap
Full-fidelity XML Bundle serializationParameters, 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.
EndpointDescription
POST /v1/fhir/resolveSingle Coding → OMOP standard concept + domain + CDM target table + mapping type
POST /v1/fhir/resolve/batchBatch resolution, up to 100 codings per request. Metered per coding
POST /v1/fhir/resolve/codeable-conceptFull 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 URIOMOP vocabulary_id
http://snomed.info/sctSNOMED
http://loinc.orgLOINC
http://www.nlm.nih.gov/research/umls/rxnormRxNorm
http://hl7.org/fhir/sid/icd-10ICD10
http://hl7.org/fhir/sid/icd-10-cmICD10CM
http://hl7.org/fhir/sid/icd-10-pcsICD10PCS
http://hl7.org/fhir/sid/icd-10-cnICD10CN
http://hl7.org/fhir/sid/icd-10-gmICD10GM
http://hl7.org/fhir/sid/icd-9-cmICD9CM
http://hl7.org/fhir/sid/icd-9ICD9Proc
http://hl7.org/fhir/sid/icd-9-cnICD9ProcCN
http://hl7.org/fhir/sid/icd-o-3ICDO3
http://hl7.org/fhir/sid/cvxCVX
http://hl7.org/fhir/sid/ndcNDC
http://www.nlm.nih.gov/research/umls/hcpcsHCPCS
http://www.whocc.no/atcATC
http://unitsofmeasure.orgUCUM
urn:iso:std:iso:11073:10101MDC
http://fdasis.nlm.nih.govUNII
http://va.gov/terminology/medrtMED-RT
https://fhir-terminology.ohdsi.orgOMOP 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

PatternSupportedNotes
Bearer token (Authorization: Bearer oh_xxx)ProductionPrimary auth for REST and FHIR endpoints. Transmitted over TLS 1.2+
OAuth2 client_credentials (RFC 6749)ProductionPOST 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 parameterNoHeader-only for security
No auth / open accessNoAll 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 TypeSupportedNotes
application/fhir+jsonProductionDefault
application/jsonProductionTreated as FHIR JSON
application/fhir+xmlProductionServed 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
*/*ProductionResolves 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.