> ## 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.

# FHIR R4, R5, and R6 terminology service overview

> Standards-compliant FHIR R4/R5/R6 terminology operations over OMOP vocabularies - $lookup, $validate-code, $translate, $expand, and $subsumes.

## Overview

OMOPHub exposes its vocabulary data through a standards-compliant **FHIR Terminology Service** at `/fhir/{r4,r5,r6}/`. Any FHIR-compatible client (HAPI FHIR, Firely, etc.) can query OMOP concepts using standard FHIR operations.

This is NOT a full FHIR server - it serves terminology resources only (CodeSystem, ConceptMap, ValueSet). For clinical resources, continue using your FHIR server and point it at OMOPHub for terminology resolution.

## Endpoints

| Operation                                                                              | Path                                                  | Description                                                                                                         |
| -------------------------------------------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| **[Metadata](/api-reference/fhir-terminology/metadata)**                               | `GET /fhir/{version}/metadata`                        | CapabilityStatement describing server capabilities                                                                  |
| **[CodeSystem search](/api-reference/fhir-terminology/codesystem-search)**             | `GET /fhir/{version}/CodeSystem?url={uri}`            | Discover support for a canonical system URI (e.g. `http://loinc.org`). Omit `url` to list all supported CodeSystems |
| **[CodeSystem instance read](/api-reference/fhir-terminology/codesystem-read)**        | `GET /fhir/{version}/CodeSystem/{id}`                 | Fetch a single CodeSystem by FHIR id (stub id like `loinc` or release id like `omop-v20250827`)                     |
| **[\$lookup](/api-reference/fhir-terminology/lookup)**                                 | `GET\|POST /fhir/{version}/CodeSystem/$lookup`        | Concept details by code                                                                                             |
| **[\$validate-code](/api-reference/fhir-terminology/validate-code)**                   | `GET\|POST /fhir/{version}/CodeSystem/$validate-code` | Check if a code is valid in a CodeSystem                                                                            |
| **[\$translate](/api-reference/fhir-terminology/translate)**                           | `GET\|POST /fhir/{version}/ConceptMap/$translate`     | Translate between vocabularies                                                                                      |
| **[\$expand](/api-reference/fhir-terminology/expand)**                                 | `GET\|POST /fhir/{version}/ValueSet/$expand`          | Expand a ValueSet to list matching codes                                                                            |
| **[ValueSet search](/api-reference/fhir-terminology/valueset-search)**                 | `GET /fhir/{version}/ValueSet?url={uri}`              | Preflight check for ValueSet support before calling `$expand` / `$validate-code`                                    |
| **[ValueSet/\$validate-code](/api-reference/fhir-terminology/valueset-validate-code)** | `GET\|POST /fhir/{version}/ValueSet/$validate-code`   | Check if a code is a member of a ValueSet                                                                           |
| **[\$subsumes](/api-reference/fhir-terminology/subsumes)**                             | `GET\|POST /fhir/{version}/CodeSystem/$subsumes`      | Test subsumption between concepts                                                                                   |
| **[\$find-matches](/api-reference/fhir-terminology/find-matches)**                     | `GET\|POST /fhir/{version}/CodeSystem/$find-matches`  | Find concepts by property criteria                                                                                  |
| **[\$closure](/api-reference/fhir-terminology/closure)**                               | `POST /fhir/{version}/ConceptMap/$closure`            | Compute subsumption closure for a concept set                                                                       |
| **[Batch](/api-reference/fhir-terminology/batch)**                                     | `POST /fhir/{version}/`                               | Execute multiple GET operations in one request                                                                      |

### OMOPHub Extensions

These operations and properties are OMOPHub-specific and not part of the FHIR Terminology Service specification.

| Extension                                          | Path / Parameter                             | Description                                                                                                                                                                                                                                    |
| -------------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **[\$diff](/api-reference/fhir-terminology/diff)** | `GET\|POST /fhir/{version}/CodeSystem/$diff` | Compare concepts between vocabulary versions                                                                                                                                                                                                   |
| **Phoebe recommendations**                         | `$lookup?property=recommended`               | Clinically-related concept recommendations from OMOPHub's Phoebe concept-set recommendation engine. See [\$lookup](/api-reference/fhir-terminology/lookup#phoebe-concept-recommendations-omophub-exclusive)                                    |
| **`target-table` product property**                | `$translate` response `product[]`            | Each `$translate` match includes the OMOP CDM destination table (`condition_occurrence`, `drug_exposure`, `measurement`, ...) - saves a separate domain lookup for ETL pipelines. See [\$translate](/api-reference/fhir-terminology/translate) |
| **Per-vocabulary CodeSystem stubs**                | `GET /CodeSystem/{stub-id}`                  | Thin FHIR-conformant CodeSystem stubs (one per canonical system URI) for clients that expect per-vocab discovery rather than OMOPHub's unified omnibus CodeSystem. See [CodeSystem search](/api-reference/fhir-terminology/codesystem-search)  |

## FHIR Versions

All four FHIR versions are supported from the same endpoint:

* `/fhir/r4/` - FHIR R4 (4.0.1) - US Cures Act, EHDS mandate
* `/fhir/r4b/` - FHIR R4B (4.3.0)
* `/fhir/r5/` - FHIR R5 (5.0.0)
* `/fhir/r6/` - FHIR R6 (6.0.0)
* `/fhir/` - Defaults to R4

## Authentication

OMOPHub supports two authentication methods for FHIR operations. Both use the same API key (`oh_xxx`) from your [dashboard](https://dashboard.omophub.com/api-keys) and share the same rate-limit and metering pool.

### Bearer token (default)

Pass the API key directly in the `Authorization` header:

```bash theme={null}
curl "https://fhir.omophub.com/fhir/r4/metadata" \
  -H "Authorization: Bearer oh_your_api_key"
```

This is what curl, Postman, the OMOPHub SDKs, and any FHIR client that lets you configure a static bearer token should use.

### OAuth2 `client_credentials` (for Spring-based clients)

FHIR clients built on Spring Security OAuth2 - HAPI FHIR JPA Starter, EHRbase, most Java/Kotlin Spring Boot stacks - expect to obtain a Bearer token from an OAuth2 token endpoint. OMOPHub exposes a minimal RFC 6749 `client_credentials` shim at `https://fhir.omophub.com/oauth2/token`:

```bash theme={null}
curl -X POST "https://fhir.omophub.com/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=oh_your_api_key"
```

The response echoes the API key as the `access_token` (`{"access_token": "oh_...", "token_type": "Bearer", "expires_in": 31536000}`). Both `client_secret_basic` (HTTP Basic auth header, Spring's default) and `client_secret_post` (form body) authentication methods are accepted. `client_secret` is **not validated** - the `client_id` is the sole credential. Supply any non-empty placeholder for `client_secret` to satisfy Spring's config schema.

For full walkthroughs, see the [HAPI FHIR](/guides/integration/hapi-fhir) and [EHRbase / openEHR](/guides/integration/ehrbase-openehr) integration guides.

## Content Type

OMOPHub responds with `Content-Type: application/fhir+json` by default and serves FHIR XML on request. Content negotiation follows RFC 7231 §5.3.2 with two OMOPHub-specific rules:

* **JSON wins on ties.** When a client advertises both JSON and XML at the same q-value (HAPI FHIR's default client sends `Accept: application/fhir+xml;q=1.0, application/fhir+json;q=1.0, ...`), OMOPHub returns JSON. XML is served only when XML is **strictly preferred** - i.e. the client's highest-scoring XML media type has a higher q-value than its highest-scoring JSON media type.
* **Wildcards are honored.** `*/*` and `application/*` count as matches at their advertised q-value, so a client sending `Accept: */*` receives JSON (the default), and a client sending `Accept: application/*, application/fhir+xml;q=0.5` also receives JSON (the wildcard matches JSON at q=1.0, which beats the explicit XML at q=0.5).

### Requesting XML explicitly

Send `Accept: application/fhir+xml` (with no JSON at equal or higher q-value):

```bash theme={null}
curl "https://fhir.omophub.com/fhir/r4/CodeSystem/\$lookup?\
system=http://snomed.info/sct&code=44054006" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/fhir+xml"
```

Response (abbreviated):

```xml theme={null}
<?xml version="1.0" encoding="UTF-8"?>
<Parameters xmlns="http://hl7.org/fhir">
  <parameter>
    <name value="system"/>
    <valueUri value="http://snomed.info/sct"/>
  </parameter>
  <parameter>
    <name value="code"/>
    <valueCode value="44054006"/>
  </parameter>
  <parameter>
    <name value="display"/>
    <valueString value="Type 2 diabetes mellitus"/>
  </parameter>
</Parameters>
```

### XML support matrix

OMOPHub's XML serializer is **clean for single-resource responses** and **best-effort for Bundle-shaped responses**. Request JSON for the Bundle-shaped endpoints unless you have a specific reason to need XML.

| Endpoint                                                 | Response shape        | XML status               |
| -------------------------------------------------------- | --------------------- | ------------------------ |
| `GET /metadata`                                          | `CapabilityStatement` | ✅ Clean XML              |
| `GET /CodeSystem/$lookup`                                | `Parameters`          | ✅ Clean XML              |
| `GET /CodeSystem/$validate-code`                         | `Parameters`          | ✅ Clean XML              |
| `GET /CodeSystem/$subsumes`                              | `Parameters`          | ✅ Clean XML              |
| `GET /CodeSystem/$find-matches`                          | `Parameters`          | ✅ Clean XML              |
| `GET /CodeSystem/$diff`                                  | `Parameters`          | ✅ Clean XML              |
| `GET /ConceptMap/$translate`                             | `Parameters`          | ✅ Clean XML              |
| `POST /ConceptMap/$closure`                              | `ConceptMap`          | ✅ Clean XML              |
| `GET /ValueSet/$expand`                                  | `ValueSet`            | ✅ Clean XML              |
| `GET /ValueSet/$validate-code`                           | `Parameters`          | ✅ Clean XML              |
| `GET /CodeSystem/{id}` (instance read)                   | `CodeSystem`          | ✅ Clean XML              |
| Handler-emitted 4xx / 5xx errors                         | `OperationOutcome`    | ✅ Clean XML              |
| Pre-auth errors (401 missing API key)                    | `OperationOutcome`    | ⚠️ JSON-only (see below) |
| `GET /CodeSystem` (search-type, with or without `?url=`) | `Bundle`              | ⚠️ Request JSON          |
| `GET /ValueSet?url=...` (search-type)                    | `Bundle`              | ⚠️ Request JSON          |
| `POST /` (batch Bundle)                                  | `Bundle`              | ⚠️ Request JSON          |

<Warning>
  **Bundle-shaped responses should be requested as JSON.** The XML serializer produces malformed output for nested resources inside a `Bundle.entry[].resource` (the inner resource's type is not used as the wrapping element name). The three Bundle-shaped endpoints above are all discovery / batch operations - FHIR clients calling them from code already prefer JSON, so this is not a common path in practice. Request JSON for those endpoints explicitly, or let content negotiation pick JSON on a tie (the default HAPI / Firely / EHRbase client behavior).
</Warning>

<Note>
  **Pre-auth error responses come back as JSON even when XML was requested.** When a request fails authentication (401, missing or invalid API key) or exhausts the monthly quota (429), the FHIR-scoped error middleware rewrites the response into an `OperationOutcome` resource but emits it as JSON regardless of `Accept`. The response body is still valid FHIR OperationOutcome - HAPI, Firely, and most FHIR clients parse it correctly - it's only the content-type header and wire format that don't honor the XML preference. Handler-emitted errors (404 unknown code, 400 bad parameter, 403 restricted vocab, 500 internal) *do* respect the Accept header and return XML.
</Note>

Errors on any `/fhir/*` path - including auth failures, missing routes, and unsupported operations - are returned as FHIR `OperationOutcome` resources in whichever format the client negotiated (JSON by default, XML when requested). The generic REST JSON envelope (`{"success": false, "error": {...}}`) is never used on FHIR routes.

## CodeSystem URLs

OMOPHub serves CodeSystem resources at two equivalent URL shapes:

1. **Canonical per-vocabulary URIs** - e.g. `http://snomed.info/sct`, `http://loinc.org`, `http://www.nlm.nih.gov/research/umls/rxnorm`. These resolve to per-vocabulary stubs (`content: "not-present"`) and are what FHIR clients like HAPI and EHRbase use for discovery. Every URI in the [Supported Vocabularies](#supported-vocabularies) table is a valid CodeSystem URL.

2. **Unified OMOP omnibus URL** - `https://fhir-terminology.ohdsi.org`. A single CodeSystem covering all 130+ OMOP vocabularies by `concept_id`. This is the OHDSI community canonical URL; use it when you want to address concepts by their OMOP internal ID rather than by vocabulary-specific code.

Both shapes resolve to the same underlying concept data. Operations like `$lookup` and `$validate-code` dispatch on the `system` / `url` parameter, so you can use whichever matches your client's expectations. Codes resolved via OMOPHub are interchangeable with codes from any other FHIR server serving OMOP vocabularies.

Discover the full list of supported URLs with [CodeSystem search](/api-reference/fhir-terminology/codesystem-search) or the [metadata endpoint](/api-reference/fhir-terminology/metadata).

## Supported Vocabularies

Query [CodeSystem search](/api-reference/fhir-terminology/codesystem-search) without a `url` parameter for the live list, or see the current registry below. Each row is a valid **CodeSystem URL** (usable in `$lookup`, `$validate-code`, `$translate`, etc.) with a corresponding **stub id** (usable in `GET /CodeSystem/{id}`).

| FHIR System URI                               | Stub `id`         | OMOP vocabulary\_id | Description                         |
| --------------------------------------------- | ----------------- | ------------------- | ----------------------------------- |
| `https://fhir-terminology.ohdsi.org`          | `omop-v{release}` | (unified)           | All OMOP concepts by concept\_id    |
| `http://snomed.info/sct`                      | `snomed`          | SNOMED              | SNOMED CT (International Edition)   |
| `http://loinc.org`                            | `loinc`           | LOINC               | Lab tests, measurements             |
| `http://www.nlm.nih.gov/research/umls/rxnorm` | `rxnorm`          | RxNorm              | Drugs (US)                          |
| `http://hl7.org/fhir/sid/icd-10`              | `icd-10`          | ICD10               | ICD-10 (WHO)                        |
| `http://hl7.org/fhir/sid/icd-10-cm`           | `icd-10-cm`       | ICD10CM             | ICD-10-CM (US)                      |
| `http://hl7.org/fhir/sid/icd-10-cn`           | `icd-10-cn`       | ICD10CN             | ICD-10 Chinese Edition              |
| `http://hl7.org/fhir/sid/icd-10-gm`           | `icd-10-gm`       | ICD10GM             | ICD-10 German Modification          |
| `http://hl7.org/fhir/sid/icd-10-pcs`          | `icd-10-pcs`      | ICD10PCS            | ICD-10 Procedure Coding System      |
| `http://hl7.org/fhir/sid/icd-9-cm`            | `icd-9-cm`        | ICD9CM              | ICD-9-CM (legacy)                   |
| `http://hl7.org/fhir/sid/icd-9`               | `icd-9-proc`      | ICD9Proc            | ICD-9 Procedure Codes               |
| `http://hl7.org/fhir/sid/icd-9-cn`            | `icd-9-proc-cn`   | ICD9ProcCN          | ICD-9 Chinese Edition Procedures    |
| `http://hl7.org/fhir/sid/icd-o-3`             | `icd-o-3`         | ICDO3               | Oncology morphology                 |
| `http://hl7.org/fhir/sid/cvx`                 | `cvx`             | CVX                 | Vaccines                            |
| `http://hl7.org/fhir/sid/ndc`                 | `ndc`             | NDC                 | Drug products (US)                  |
| `http://unitsofmeasure.org`                   | `ucum`            | UCUM                | Units of measure                    |
| `urn:iso:std:iso:11073:10101`                 | `mdc`             | MDC                 | ISO/IEEE 11073 Medical Device Codes |
| `http://www.whocc.no/atc`                     | `atc`             | ATC                 | Drug classification (WHO)           |
| `http://fdasis.nlm.nih.gov`                   | `unii`            | UNII                | Substance identifiers (FDA)         |
| `http://va.gov/terminology/medrt`             | `med-rt`          | MED-RT              | Medication reference (VA)           |
| `http://www.nlm.nih.gov/research/umls/hcpcs`  | `hcpcs`           | HCPCS               | Procedures (US outpatient)          |

## Three Access Patterns

**1. By vocabulary-specific code (type-level, most common):**

```
GET /fhir/r4/CodeSystem/$lookup?system=http://snomed.info/sct&code=44054006
```

Pattern 1 is what ETL developers and FHIR clients need most of the time - they have FHIR-native codes from source systems and want to resolve them against the canonical vocabulary URI.

**2. By OMOP concept\_id (via the unified omnibus URL):**

```
GET /fhir/r4/CodeSystem/$lookup?system=https://fhir-terminology.ohdsi.org&code=201826
```

Pattern 2 is useful when you already have an OMOP concept\_id (from a previous `$lookup`, from an ETL pipeline, or from a `$translate` result) and want to look it up without maintaining a vocabulary-ID round-trip.

**3. By CodeSystem instance ID (instance-level):**

```
GET /fhir/r4/CodeSystem/loinc/$lookup?code=2951-2
GET /fhir/r4/CodeSystem/omop-v20250827/$lookup?code=201826
```

Pattern 3 uses the CodeSystem's FHIR `id` from [CodeSystem search](/api-reference/fhir-terminology/codesystem-search) - either a per-vocab stub id (`loinc`, `snomed`, ...) or a release-specific unified id (`omop-v20250827`). This is what HAPI FHIR emits after its discovery phase, and what you get back from [CodeSystem instance read](/api-reference/fhir-terminology/codesystem-read).

## Designations (Synonyms)

`$lookup` responses include `designation` entries for concept synonyms. Each
designation has a `language` code and a `value` string. These are the alternate
names from the OMOP `concept_synonym` table.

## Property Naming

Standard OMOP properties use kebab-case names for interoperability. Relationship properties use their OMOP relationship names (e.g., `Maps to`, `Is a`).

### Standard OMOP properties

| Property              | Type    | Description                                                |
| --------------------- | ------- | ---------------------------------------------------------- |
| `concept-id`          | integer | OMOP concept\_id                                           |
| `source-concept-code` | Coding  | Original source code as FHIR Coding (system URI + code)    |
| `concept-name`        | string  | OMOP concept\_name                                         |
| `domain-id`           | code    | OMOP domain (Condition, Drug, Measurement, etc.)           |
| `vocabulary-id`       | code    | OMOP vocabulary\_id (SNOMED, ICD10CM, LOINC, etc.)         |
| `concept-class-id`    | code    | OMOP concept\_class\_id (Clinical Finding, Disorder, etc.) |
| `standard-concept`    | code    | S=Standard, C=Classification, NS=Non-standard              |
| `inactive`            | boolean | Whether concept is inactive                                |
| `valid-start-date`    | date    | Date concept became valid                                  |
| `valid-end-date`      | date    | Date concept becomes invalid                               |
| `invalid-reason`      | code    | D=Deleted, U=Updated (omitted if valid)                    |

### Relationship properties

| Property      | Type   | Description              |
| ------------- | ------ | ------------------------ |
| `Maps to`     | Coding | Standard concept mapping |
| `Mapped from` | Coding | Inverse of Maps to       |
| `Is a`        | Coding | Parent/supertype         |
| `Subsumes`    | Coding | Child/subtype            |

Use the `property` query parameter on `$lookup` to request specific properties
(e.g. `property=concept-id&property=domain-id`). When omitted, all standard
properties are returned.

## Metering

FHIR operations count against the same API-call quota as REST API calls - there is no separate FHIR pool. Two exceptions to the "1 operation = 1 call" rule:

* **[Batch Bundles](/api-reference/fhir-terminology/batch)** are metered per entry. A 50-entry batch counts as 50 calls.
* **[Resolver batch](/api-reference/fhir/resolve-batch)** (`POST /v1/fhir/resolve/batch`) is also metered per coding, matching FHIR batch behavior.

Rate-limit headers (`X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`) are included on every response.

## See Also

* [FHIR Integration guide](/guides/integration/fhir-integration) - comprehensive walkthrough of the FHIR Resolver and Terminology Service for new integrations
* [FHIR Concept Resolver](/api-reference/fhir/resolve) - OMOPHub-native JSON for ETL pipelines, with `target_table` + mapping-quality signals
* [EHRbase / openEHR Integration](/guides/integration/ehrbase-openehr) - use OMOPHub for openEHR template terminology validation
* [HAPI FHIR Integration](/guides/integration/hapi-fhir) - config for HAPI JPA Starter (via reverse proxy) and custom Spring Boot HAPI builds
* [EHR Integration](/guides/integration/ehr-integration) - SMART on FHIR, CDS Hooks, and point-of-care patterns
