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

> Use OMOPHub with FHIR - the FHIR Resolver for CodeableConcept mapping, the FHIR Terminology Service, and recipes for Observations and Medications.

## 1. Exchanging Data vs. Exchanging Meaning

FHIR solved the data exchange problem. A hospital can send a FHIR `Observation` to a research platform, a FHIR `MedicationStatement` to a safety database, or a FHIR `Condition` to a registry. The structure is standardized. The JSON is valid. The API works.

But open the payload and look at the `CodeableConcept` - the field that carries the clinical meaning. You'll find LOINC codes from one system, proprietary local codes from another, and sometimes just a `text` field with "Blood Sugar" and no structured code at all. The data exchanged successfully. The meaning got lost in translation.

This is the semantic interoperability gap. FHIR standardizes *structure*; OMOP standardizes *vocabulary*. To go from exchanged FHIR data to analyzable OMOP records, you need to resolve every `CodeableConcept` to a standard OMOP concept ID.

**OMOPHub** gives you two purpose-built surfaces for this job:

* **The FHIR Resolver** (`POST /v1/fhir/resolve*`) - a composite, OMOP-aware endpoint that takes a FHIR `Coding` or `CodeableConcept` and returns a standard OMOP concept, a mapping type, and a CDM target table. This is what you want for ETL pipelines.
* **The FHIR Terminology Service** (`https://fhir.omophub.com/fhir/{r4,r4b,r5,r6}/*`) - a spec-conformant FHIR terminology server implementing `$lookup`, `$validate-code`, `$translate`, `$expand`, `$subsumes`, `$closure`, `$find-matches`, and the OMOPHub-custom `$diff`. This is what you want when a FHIR client, HAPI FHIR server, or SMART app needs to talk to a real terminology service.

This guide covers both - when to use each, what they return, and how they combine in real pipelines.

## 2. Three Integration Paths

OMOPHub exposes three ways to do vocabulary resolution over FHIR data. Pick the one that matches your job:

| Your job                                                                                              | Use                          | Endpoint                                                   |
| ----------------------------------------------------------------------------------------------------- | ---------------------------- | ---------------------------------------------------------- |
| ETL: map a FHIR `Coding` to an OMOP standard concept + CDM target table                               | **FHIR Resolver**            | `POST https://api.omophub.com/v1/fhir/resolve`             |
| A FHIR client needs a conformant terminology server ($lookup, $validate-code, $translate, $expand...) | **FHIR Terminology Service** | `https://fhir.omophub.com/fhir/r4/*`                       |
| HAPI FHIR server needs a remote terminology backend                                                   | **FHIR Terminology Service** | See [HAPI FHIR Integration](/guides/integration/hapi-fhir) |
| Building SMART on FHIR / CDS Hooks apps at point of care                                              | **FHIR Resolver**            | See [EHR Integration](/guides/integration/ehr-integration) |
| Custom traversal, hierarchy walks, ingredient resolution, semantic search                             | **REST primitives**          | `/v1/concepts/*`, `/v1/search/*`, `/v1/mappings/*`         |

<Tip>
  **When in doubt, reach for the Resolver first.** If you're parsing FHIR resources and writing OMOP rows, the Resolver collapses URI mapping, code lookup, `Maps to` traversal, CDM target-table assignment, and semantic fallback into a single call. The Terminology Service is conformance-first - it returns `Parameters` and `OperationOutcome` resources for tools that speak FHIR natively.
</Tip>

## 3. Authentication & Endpoints

OMOPHub serves the Resolver and the Terminology Service from two hostnames, but both are backed by the same service and the same API key.

| Surface                  | Base URL                   | Format                                 |
| ------------------------ | -------------------------- | -------------------------------------- |
| REST API + FHIR Resolver | `https://api.omophub.com`  | OMOPHub JSON envelope                  |
| FHIR Terminology Service | `https://fhir.omophub.com` | FHIR `Parameters` / `OperationOutcome` |

Every endpoint requires a Bearer token. Get one from your [dashboard](https://dashboard.omophub.com/api-keys) and pass it in the `Authorization` header:

```bash theme={null}
KEY="oh_your_api_key"

# 30-second smoke test - should return a FHIR CapabilityStatement
curl https://fhir.omophub.com/fhir/r4/metadata \
  -H "Authorization: Bearer $KEY"

# Same for the resolver
curl https://api.omophub.com/v1/vocabularies \
  -H "Authorization: Bearer $KEY"
```

Both hosts accept traffic on `/v1/*` and `/fhir/*`, but the convention is: **`api.omophub.com` for REST and the Resolver, `fhir.omophub.com` for the Terminology Service**. Use the matching host in your clients so DNS, CORS, and logs stay clean.

<Note>
  FHIR Terminology Service versions are path-segment prefixes: `/fhir/r4`, `/fhir/r4b`, `/fhir/r5`, `/fhir/r6`. Omitting the segment (`/fhir/metadata`) defaults to **r4**. Response headers `X-Vocab-Release` and `X-Vocab-Release-Status` tell you which OMOP vocabulary release served the request.
</Note>

## 4. Path A: The FHIR Resolver (Recommended for ETL)

The Resolver is a single composite endpoint that chains every step of FHIR → OMOP resolution:

1. Resolve the FHIR `system` URI to an OMOP `vocabulary_id`.
2. Look up the source concept by code + vocabulary.
3. If the source concept is non-standard, follow `Maps to` to the standard equivalent.
4. If the code isn't found, fall back to semantic search on the `display` text, filtered by the expected domain.
5. Determine the CDM `target_table` (e.g. `measurement`, `drug_exposure`, `condition_occurrence`) from the resolved concept's domain.
6. Optionally return Phoebe recommendations and mapping-quality signals.

It comes in three flavors:

* `POST /v1/fhir/resolve` - single `Coding`
* `POST /v1/fhir/resolve/batch` - up to 100 codings per request, metered as N calls
* `POST /v1/fhir/resolve/codeable-concept` - full `CodeableConcept` with OHDSI vocabulary preference ranking when multiple codings are present

### 4.1 Single Coding

```python Python theme={null}
import requests

resp = requests.post(
    "https://api.omophub.com/v1/fhir/resolve",
    headers={"Authorization": "Bearer oh_your_api_key"},
    json={
        "system": "http://loinc.org",
        "code": "2339-0",
        "display": "Blood Glucose",
        "resource_type": "Observation",
        "include_recommendations": False,
        "include_quality": True,
    },
)
result = resp.json()["data"]["resolution"]

result["standard_concept"]["concept_id"]    # 3004501
result["standard_concept"]["concept_name"]  # "Glucose [Mass/volume] in Serum or Plasma"
result["target_table"]                      # "measurement"
result["mapping_type"]                      # "direct"
result["domain_resource_alignment"]         # "aligned"
result["mapping_quality"]                   # "high"
```

The response envelope follows the standard OMOPHub `{success, data, meta}` shape. The `resolution` object always contains a `source_concept` (what was found for the input code) and a `standard_concept` (what you should write to CDM tables after `Maps to` traversal) - they're identical when the source is already standard.

### 4.2 Batch Resolve

Bulk ETL workloads should use the batch endpoint. Each item is resolved independently and errors are reported per-item:

```bash theme={null}
curl -X POST https://api.omophub.com/v1/fhir/resolve/batch \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "codings": [
      { "system": "http://loinc.org", "code": "2339-0", "resource_type": "Observation" },
      { "system": "http://hl7.org/fhir/sid/icd-10-cm", "code": "E11.9", "resource_type": "Condition" },
      { "system": "http://hl7.org/fhir/sid/ndc", "code": "0071-0155-01", "resource_type": "MedicationStatement" }
    ]
  }'
```

<Warning>
  Batch requests count against your quota **per coding**, not per HTTP call. A 50-item batch meters as 50 API calls. This matches how the Resolver actually does the work and keeps pricing predictable.
</Warning>

### 4.3 CodeableConcept with Vocabulary Preference

When a single `CodeableConcept` carries multiple codings for the same clinical meaning (common in EHR exports - SNOMED *and* ICD-10-CM for the same condition), the CodeableConcept endpoint picks the best one per OHDSI's vocabulary preference:

```python Python theme={null}
resp = requests.post(
    "https://api.omophub.com/v1/fhir/resolve/codeable-concept",
    headers={"Authorization": "Bearer oh_your_api_key"},
    json={
        "coding": [
            {"system": "http://snomed.info/sct", "code": "44054006"},
            {"system": "http://hl7.org/fhir/sid/icd-10-cm", "code": "E11.9"},
        ],
        "text": "Type 2 diabetes mellitus",
        "resource_type": "Condition",
    },
)
best = resp.json()["data"]["best_match"]["resolution"]

best["source_concept"]["vocabulary_id"]   # "SNOMED"  (preferred over ICD10CM for Condition)
best["standard_concept"]["concept_id"]    # 201826
best["target_table"]                      # "condition_occurrence"
```

The response also includes all the alternative resolutions under `data.all_matches`, so you can audit the preference decision.

### 4.4 Response Shape: the Fields That Matter

Every resolver response - single, batch, and CodeableConcept - returns the same `resolution` object. The fields worth knowing:

| Field                           | What it tells you                                                                                                                                                                                                             |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `source_concept`                | The OMOP concept matching the input code in its source vocabulary. May be non-standard.                                                                                                                                       |
| `standard_concept`              | The OMOP concept after `Maps to` traversal. **This is what you write to the CDM.**                                                                                                                                            |
| `mapping_type`                  | `direct` (source was already standard), `mapped` (followed `Maps to`), `semantic_match` (fell back to display-text search), or `unmapped`.                                                                                    |
| `similarity_score`              | Only present when `mapping_type = semantic_match` - the text similarity of the fallback match.                                                                                                                                |
| `target_table`                  | The OMOP CDM destination table (`measurement`, `condition_occurrence`, `drug_exposure`, `procedure_occurrence`, `observation`, ...). Computed from the standard concept's domain.                                             |
| `domain_resource_alignment`     | `aligned` / `misaligned` - whether the resolved concept's domain matches the FHIR resource type you declared in `resource_type`. A `misaligned` Observation → `drug` domain is a strong signal that the source data is wrong. |
| `alternative_standard_concepts` | Other plausible `Maps to` targets (for ambiguous source codes).                                                                                                                                                               |
| `recommendations`               | Phoebe recommendations, if `include_recommendations: true`.                                                                                                                                                                   |
| `mapping_quality`               | `high` / `medium` / `low` / `manual_review`, if `include_quality: true`.                                                                                                                                                      |

## 5. Path B: The FHIR Terminology Service

When you need a real FHIR terminology server - because your client speaks FHIR, because you're conformance-testing, or because HAPI FHIR wants a remote backend - point it at `https://fhir.omophub.com`. Every operation is spec-conformant (for FHIR R4/R4B/R5/R6) and returns FHIR `Parameters` or `OperationOutcome` resources with `application/fhir+json`.

Every FHIR route accepts an optional version prefix:

```
GET https://fhir.omophub.com/fhir/metadata            # defaults to r4
GET https://fhir.omophub.com/fhir/r4/metadata         # explicit r4
GET https://fhir.omophub.com/fhir/r5/metadata         # r5
GET https://fhir.omophub.com/fhir/r6/metadata         # r6 (draft)
```

All examples below use `/fhir/r4`. Replace with `/fhir/r5` or `/fhir/r6` as needed.

### 5.1 CapabilityStatement - `GET /metadata`

Every FHIR server starts here. OMOPHub returns a `CapabilityStatement` listing the supported resources and operations:

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

The response declares support for `CodeSystem` (read, search-type, `$lookup`, `$validate-code`, `$subsumes`, `$find-matches`), `ValueSet` (`$expand`, `$validate-code`), and `ConceptMap` (`$translate`, `$closure`).

### 5.2 `$lookup` - concept details for a code

Returns the display, designations, and properties for a given code. Supports the full FHIR `$lookup` parameter set, plus the OMOPHub-specific `property=recommended` for Phoebe recommendations.

<CodeGroup>
  ```bash GET theme={null}
  curl "https://fhir.omophub.com/fhir/r4/CodeSystem/\$lookup?\
  system=http://snomed.info/sct&code=44054006" \
    -H "Authorization: Bearer $KEY"
  ```

  ```bash POST theme={null}
  curl -X POST https://fhir.omophub.com/fhir/r4/CodeSystem/\$lookup \
    -H "Authorization: Bearer $KEY" \
    -H "Content-Type: application/fhir+json" \
    -d '{
      "resourceType": "Parameters",
      "parameter": [
        { "name": "system", "valueUri": "http://snomed.info/sct" },
        { "name": "code",   "valueCode": "44054006" },
        { "name": "property", "valueString": "recommended" }
      ]
    }'
  ```
</CodeGroup>

Response is a `Parameters` resource with `name`, `display`, `designation[]`, and `property[]` entries.

### 5.3 `$validate-code` - check that a code belongs to a system or value set

Two variants: one against a `CodeSystem`, one against a `ValueSet`.

```bash theme={null}
# CodeSystem - does SNOMED 44054006 exist, and is the display correct?
curl "https://fhir.omophub.com/fhir/r4/CodeSystem/\$validate-code?\
url=http://snomed.info/sct&code=44054006&display=Type+2+diabetes+mellitus" \
  -H "Authorization: Bearer $KEY"

# ValueSet - is this code in the US Core Condition value set?
curl "https://fhir.omophub.com/fhir/r4/ValueSet/\$validate-code?\
url=http://hl7.org/fhir/ValueSet/condition-code&\
system=http://snomed.info/sct&code=44054006" \
  -H "Authorization: Bearer $KEY"
```

The response is a `Parameters` resource with `result: boolean`, `display: string` (the canonical display), and `message: string` (on mismatch).

### 5.4 `$translate` - map a code between vocabularies

Uses the OMOP `Maps to` relationship graph to translate a source code to a target CodeSystem.

<CodeGroup>
  ```bash GET theme={null}
  curl "https://fhir.omophub.com/fhir/r4/ConceptMap/\$translate?\
  system=http://hl7.org/fhir/sid/icd-10-cm&code=E11.9&\
  target=http://snomed.info/sct" \
    -H "Authorization: Bearer $KEY"
  ```

  ```bash POST theme={null}
  curl -X POST https://fhir.omophub.com/fhir/r4/ConceptMap/\$translate \
    -H "Authorization: Bearer $KEY" \
    -H "Content-Type: application/fhir+json" \
    -d '{
      "resourceType": "Parameters",
      "parameter": [
        { "name": "system", "valueUri": "http://hl7.org/fhir/sid/icd-10-cm" },
        { "name": "code",   "valueCode": "E11.9" },
        { "name": "target", "valueUri": "http://snomed.info/sct" }
      ]
    }'
  ```
</CodeGroup>

The response contains one or more `match` parameters, each with an `equivalence` (`equivalent`, `wider`, `narrower`, ...) and a `concept` Coding.

### 5.5 `$expand` - enumerate a value set

Expands a `ValueSet` URL into its member concepts. Supports `filter`, `count`, `offset`, and `includeDesignations`. Works for implicit SNOMED value sets (`http://snomed.info/sct?fhir_vs`) and for OMOPHub's curated value sets.

```bash theme={null}
curl "https://fhir.omophub.com/fhir/r4/ValueSet/\$expand?\
url=http://snomed.info/sct?fhir_vs&filter=diabetes&count=20" \
  -H "Authorization: Bearer $KEY"
```

The response is a `ValueSet` with an `expansion` containing `total` (across the whole match, not just this page), `offset`, `parameter[]`, and `contains[]` (the member concepts).

### 5.6 `$subsumes` - hierarchy relationship between two codes

Answers: is code A an ancestor, descendant, or equivalent of code B in the same CodeSystem? Useful for phenotype inclusion/exclusion rules.

```bash theme={null}
# Is "Diabetes mellitus" (73211009) an ancestor of "Type 2 diabetes" (44054006)?
curl "https://fhir.omophub.com/fhir/r4/CodeSystem/\$subsumes?\
system=http://snomed.info/sct&codeA=73211009&codeB=44054006" \
  -H "Authorization: Bearer $KEY"
```

Returns `outcome: subsumes | subsumed-by | equivalent | not-subsumed`.

### 5.7 `$find-matches` - semantic search in a FHIR envelope

OMOPHub's semantic search exposed through the standard FHIR operation. Send `property` parameters describing what you're looking for; get `Coding` results back.

```bash theme={null}
curl -X POST "https://fhir.omophub.com/fhir/r4/CodeSystem/\$find-matches" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/fhir+json" \
  -d '{
    "resourceType": "Parameters",
    "parameter": [
      { "name": "system", "valueUri": "http://snomed.info/sct" },
      { "name": "property", "part": [
        { "name": "code",  "valueCode": "display" },
        { "name": "value", "valueString": "hemoglobin a1c" }
      ]}
    ]
  }'
```

Use this when your client is FHIR-native and you'd rather not maintain a second integration for OMOPHub's `/v1/search/semantic` endpoint.

### 5.8 `$closure` - maintain a transitive closure table

The standard FHIR operation for incrementally building a transitive closure as new codes are seen. Useful when you're materializing a phenotype or cohort definition over streaming data.

```bash theme={null}
curl -X POST https://fhir.omophub.com/fhir/r4/ConceptMap/\$closure \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/fhir+json" \
  -d '{
    "resourceType": "Parameters",
    "parameter": [
      { "name": "name", "valueString": "t2dm-cohort" },
      { "name": "concept", "valueCoding": { "system": "http://snomed.info/sct", "code": "44054006" } },
      { "name": "concept", "valueCoding": { "system": "http://snomed.info/sct", "code": "46635009" } }
    ]
  }'
```

Returns a `ConceptMap` with the new subsumption relationships discovered for the codes in this batch, relative to everything already in the closure.

### 5.9 `$diff` - OMOPHub-custom vocabulary version comparison

Not in the FHIR spec. OMOPHub adds this because vocabulary releases change over time and CDM operators need to know what moved. `$diff` compares two OMOP vocabulary releases and returns the added, removed, and changed concepts for a given CodeSystem.

```bash theme={null}
curl "https://fhir.omophub.com/fhir/r4/CodeSystem/\$diff?\
url=http://snomed.info/sct&from=2024v2&to=2026v1" \
  -H "Authorization: Bearer $KEY"
```

Use cases: impact analysis before cutting a new release into production, audit trails for regulated pipelines, release notes for cohort definitions.

### 5.10 Batch Bundle - multiple operations in one request

OMOPHub supports the FHIR `batch`/`transaction` Bundle pattern. Send a `Bundle` with multiple `entry.request` items and get back a `Bundle` with one `response` per entry.

```bash theme={null}
curl -X POST https://fhir.omophub.com/fhir/r4/ \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/fhir+json" \
  -d '{
    "resourceType": "Bundle",
    "type": "batch",
    "entry": [
      { "request": { "method": "GET", "url": "CodeSystem/$lookup?system=http://snomed.info/sct&code=44054006" } },
      { "request": { "method": "GET", "url": "CodeSystem/$validate-code?url=http://loinc.org&code=2339-0" } },
      { "request": { "method": "GET", "url": "ConceptMap/$translate?system=http://hl7.org/fhir/sid/icd-10-cm&code=E11.9&target=http://snomed.info/sct" } }
    ]
  }'
```

Each entry is metered individually - a 10-operation bundle counts as 10 API calls.

## 6. Path C: Raw Vocabulary Primitives

The Resolver and the Terminology Service are opinionated. When you need full control - walking `Has ingredient` relationships, custom semantic-search filters, hierarchy traversal with specific relationship types - drop to the REST primitives:

```python Python theme={null}
import omophub
client = omophub.OMOPHub()

# 1. Find a concept by code
concept = client.search.basic("44054006", vocabulary_ids=["SNOMED"])["concepts"][0]

# 2. Walk its Maps-to relationships
mappings = client.mappings.get(concept["concept_id"], target_vocabulary="SNOMED")

# 3. Explore hierarchy (ancestors / descendants)
ancestors = client.hierarchy.ancestors(concept["concept_id"], max_levels=5)

# 4. Non-hierarchy relationships (e.g. "Has ingredient" for drugs)
rels = client.concepts.relationships(concept["concept_id"])
```

Use this path when:

* You need `Has ingredient` / `RxNorm has ing` resolution for drugs (see the MedicationStatement use case in §8 below).
* You want to control the exact relationship types walked during a hierarchy traversal.
* You want to combine semantic search with domain filters that the Resolver doesn't expose.
* You're building tooling that needs raw concept metadata (vocabulary ID, concept class, domain, validity).

## 7. Use Case: FHIR Observation → OMOP Measurement

The most common FHIR-to-OMOP mapping. Lab results and vital signs arrive as `Observation` resources and need to become OMOP `measurement` records.

**Scenario:** A blood glucose `Observation` arrives with LOINC code `2339-0`. Resolve it to a standard OMOP concept and build a measurement row.

```python Python theme={null}
import requests, json

fhir_obs = {
    "resourceType": "Observation",
    "status": "final",
    "code": {
        "coding": [{
            "system": "http://loinc.org",
            "code": "2339-0",
            "display": "Glucose [Mass/volume] in Blood",
        }],
        "text": "Blood Glucose",
    },
    "valueQuantity": {"value": 120, "unit": "mg/dL"},
    "effectiveDateTime": "2023-10-26T09:30:00Z",
}

coding = fhir_obs["code"]["coding"][0]
resp = requests.post(
    "https://api.omophub.com/v1/fhir/resolve",
    headers={"Authorization": "Bearer oh_your_api_key"},
    json={
        "system": coding["system"],
        "code": coding["code"],
        "display": fhir_obs["code"].get("text"),
        "resource_type": "Observation",
    },
)
result = resp.json()["data"]["resolution"]

omop_measurement = {
    "measurement_concept_id": result["standard_concept"]["concept_id"],
    "measurement_source_value": coding.get("display", ""),
    "value_as_number": fhir_obs["valueQuantity"]["value"],
    "unit_source_value": fhir_obs["valueQuantity"].get("unit"),
    "measurement_date": fhir_obs["effectiveDateTime"][:10],
}
print(json.dumps(omop_measurement, indent=2))
```

**Key insight:** LOINC codes are usually already standard in OMOP, so `mapping_type = direct`. The Resolver confirms `target_table = "measurement"` - use that as a sanity check that your downstream CDM writer is pointed at the right table. If the Observation carried a local code instead of LOINC, the Resolver would fall back to semantic search over the `display` text automatically.

**See also:** [Lab normalization in EHR Integration](/guides/integration/ehr-integration) for the sidecar-app architecture that wraps this resolution pattern.

## 8. Use Case: FHIR MedicationStatement → OMOP Ingredient

Medication reconciliation requires rolling specific product codes up to their active ingredient for class-level analysis.

**Scenario:** A FHIR `MedicationStatement` arrives with NDC code `0071-0155-01` (Lipitor 10mg). For a drug safety study, you need the active ingredient (Atorvastatin), not the branded product.

The Resolver handles the NDC → standard RxNorm leg. The ingredient traversal is a separate `concepts.relationships` call because "Has ingredient" is a non-hierarchical relationship, not an ancestor.

```python Python theme={null}
import requests, omophub

client = omophub.OMOPHub()

# Step 1: NDC → standard RxNorm via the Resolver
resp = requests.post(
    "https://api.omophub.com/v1/fhir/resolve",
    headers={"Authorization": "Bearer oh_your_api_key"},
    json={
        "system": "http://hl7.org/fhir/sid/ndc",
        "code": "0071-0155-01",
        "display": "Lipitor 10 MG Oral Tablet",
        "resource_type": "MedicationStatement",
    },
)
rxnorm = resp.json()["data"]["resolution"]["standard_concept"]
print(rxnorm["concept_id"], rxnorm["concept_name"])
# 40165252 "atorvastatin 10 MG Oral Tablet [Lipitor]"

# Step 2: Walk "Has ingredient" to find the active ingredient concept
rels = client.concepts.relationships(rxnorm["concept_id"])
ingredients = [
    r for r in rels.get("relationships", [])
    if "ingredient" in r.get("relationship_id", "").lower()
    or r.get("concept_class_id") == "Ingredient"
]
if ingredients:
    ing = ingredients[0]
    print(ing["concept_id"], ing["concept_name"])
    # 1545958 "atorvastatin"
```

**Key insight:** The Resolver gives you the CDM-correct `drug_exposure_concept_id` (the branded product) in one call. Ingredient-level analysis is a *separate* question - OMOP stores drug composition as relationships, not hierarchy - and requires a follow-up call on the raw primitives. Don't try to squeeze this into the Resolver; use the right tool per leg.

**See also:** [Real-time medication CDS in EHR Integration](/guides/integration/ehr-integration) for the CDS-Hooks pattern that combines ingredient lookup with drug-drug interaction alerting.

## 9. FHIR Extensions & CodeableConcept Nuances

Real-world FHIR uses extensions - extra data fields not in the base spec. US Core adds detailed race/ethnicity extensions to `Patient`; mCODE adds staging and grade extensions to `Condition`; QI-Core adds adherence extensions to `MedicationStatement`. When extensions contain `CodeableConcept` values, the resolution pattern is identical to the base resources.

```python Python theme={null}
# US Core detailed race extension on a Patient resource
patient_extension = {
    "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race",
    "extension": [{
        "url": "ombCategory",
        "valueCoding": {
            "system": "urn:oid:2.16.840.1.113883.6.238",
            "code": "2106-3",
            "display": "White",
        }
    }]
}

# Resolve the Coding inside the extension just like any other Coding
coding = patient_extension["extension"][0]["valueCoding"]
resp = requests.post(
    "https://api.omophub.com/v1/fhir/resolve",
    headers={"Authorization": "Bearer oh_your_api_key"},
    json={
        "system": coding["system"],
        "code": coding["code"],
        "display": coding["display"],
        "resource_type": "Patient",
    },
)
# Write the result to person.race_concept_id
```

The **parsing** of extensions - finding them by URL, extracting their values - is application code. The **vocabulary resolution** step is identical regardless of whether the `CodeableConcept` lives at the top of a resource, inside a component, or inside an extension.

<Note>
  When a `CodeableConcept` in an extension has a `text` field but no `coding`, send it to the Resolver with `display` set and no `system`/`code`. The Resolver will fall back to semantic search over the text, scoped to whatever `resource_type` hint you provide.
</Note>

## 10. Versioning, Rate Limits & Errors

### Vocabulary versioning

Every response from the Terminology Service carries two headers telling you which OMOP vocabulary release served the request:

```
X-Vocab-Release: 2026v1
X-Vocab-Release-Status: active
```

You can pin a specific release in three ways (first match wins):

1. Header: `X-Vocab-Release: 2026v1`
2. Query parameter: `?vocab_release=2026v1`
3. Path segment in OMOPHub's legacy path-versioned routes (not used by the FHIR service - use the header for FHIR requests)

Available releases are published in the `CapabilityStatement` at `/metadata` and listed in detail in [vocabulary-versions](/vocabulary-versions).

### Rate limits

FHIR and REST share the same quota pool per API key. Default free-tier limit is 60 requests/minute. Every response includes:

```
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1729951200
```

Batch operations meter **per item**, not per HTTP call: a 50-coding `POST /v1/fhir/resolve/batch` or a 10-entry FHIR Bundle counts as 50 or 10 calls respectively. Plan your quota accordingly.

### Error envelopes

The two surfaces return different shapes:

<CodeGroup>
  ```json FHIR Terminology Service theme={null}
  {
    "resourceType": "OperationOutcome",
    "issue": [{
      "severity": "error",
      "code": "not-found",
      "diagnostics": "Code 'abc123' not found in system 'http://snomed.info/sct'"
    }]
  }
  ```

  ```json FHIR Resolver (REST) theme={null}
  {
    "success": false,
    "error": {
      "code": "CONCEPT_NOT_FOUND",
      "message": "No OMOP concept found for code 'abc123' in SNOMED"
    },
    "meta": {
      "request_id": "6638747b-4b12-482b-968d-bc5080336555",
      "timestamp": "2026-04-13T16:48:09.578Z"
    }
  }
  ```
</CodeGroup>

HTTP status codes are the same across both surfaces: `400` for bad parameters, `401` for missing/invalid API key, `404` for unknown code or value set, `410` for archived vocabulary releases, `429` for rate-limit exhaustion, `5xx` for server errors.

## 11. Next Steps

<CardGroup cols={3}>
  <Card title="FHIR → OMOP Standardization" icon="arrow-right-arrow-left" href="/guides/workflows/fhir-to-omop">
    The complete end-to-end workflow from FHIR-coded data to OMOP CDM tables.
  </Card>

  <Card title="Interoperability Test Matrix" icon="table-list" href="/guides/production/interop-test-matrix">
    Supported FHIR operations, tested clients, and auth patterns at a glance.
  </Card>

  <Card title="HAPI FHIR Integration" icon="server" href="/guides/integration/hapi-fhir">
    Point your HAPI FHIR server at OMOPHub as a remote terminology service - one config change, no local ATHENA download.
  </Card>

  <Card title="EHR Integration" icon="hospital" href="/guides/integration/ehr-integration">
    CDS Hooks, SMART on FHIR apps, sidecar architectures, and real-time vocabulary resolution at the point of care.
  </Card>

  <Card title="AI/LLM Grounding" icon="robot" href="/guides/integration/ai-llm-integration">
    The Extract → Ground → Reason pattern for using OMOPHub as a vocabulary backbone for clinical LLMs.
  </Card>

  <Card title="Known Limitations" icon="triangle-exclamation" href="/guides/production/known-limitations">
    FHIR-specific caveats: implicit ValueSets only, Bundle-shaped XML is best-effort, and what's on the roadmap.
  </Card>
</CardGroup>
