# Docs for Agents
Source: https://docs.omophub.com/ai/docs-for-agents
Three documentation formats optimized for AI agents and LLM context windows.
## Overview
OMOPHub provides documentation in three formats specifically designed for AI consumption. Each format serves a different use case depending on your context window budget and the depth of information your agent needs.
## Documentation Formats
Concise index of the OMOPHub API. Fits in small context windows.
Complete documentation in a single file. Full API reference included.
Structured skills reference for AI agents with action-oriented instructions.
## When to Use Each Format
| Format | Size | Best For |
| :-------------- | :----- | :------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `llms.txt` | Small | Quick orientation. Gives an agent a high-level map of available endpoints and capabilities. Use when context window space is limited. |
| `llms-full.txt` | Large | Deep integration work. Contains the full API reference, parameter details, and response schemas. Use when the agent needs to write code against the API. |
| `skill.md` | Medium | Action-oriented tasks. Structured as capabilities the agent can perform, with step-by-step instructions. Use for agents that follow skill/tool patterns. |
## Direct URLs
You can fetch these files directly for injection into your agent's context:
```
https://docs.omophub.com/llms.txt
https://docs.omophub.com/llms-full.txt
https://docs.omophub.com/skill.md
```
## Usage Example
To give an AI agent access to OMOPHub documentation, include the appropriate file in the system prompt or context:
```python theme={null}
import httpx
# Fetch the concise index for a small context window
response = httpx.get("https://docs.omophub.com/llms.txt")
llms_context = response.text
# Include in your agent's system prompt
system_prompt = f"""You are a medical coding assistant.
Use the following API documentation to look up medical concepts:
{llms_context}
"""
```
# AI & LLM Integration
Source: https://docs.omophub.com/ai/integration-guide
Grounding intelligence in clinical standards. Pair LLMs with OMOPHub vocabulary lookups to eliminate hallucinated codes, build grounded clinical chatbots, and verify every concept ID against the OMOP source of truth.
## 1. The "Stochastic Parrot" in the Exam Room
An LLM passes the USMLE. It drafts a patient summary that reads better than most residents' notes. It explains a complex diagnosis in plain language that would take a doctor fifteen minutes to compose. Magic.
Then you ask it for the ICD-10 code for "Type 2 diabetes mellitus with diabetic chronic kidney disease." It returns E11.22. Confident. Authoritative. And wrong: the actual code is E11.22 for Type 2 DM with diabetic CKD, but the decimal placement, edition specificity, and mapping to the correct OMOP concept are all things the LLM is guessing at based on pattern matching, not looking up in a source of truth.
In a clinical setting, "mostly right" is the same as "dangerously wrong."
This is the hallucination problem for clinical AI. LLMs are reasoning engines - they understand clinical language, extract entities, and follow logical chains. But they are not vocabulary databases. They predict the next likely token; they don't look up codes. When they output a SNOMED concept ID, an ICD-10 code, or an RxNorm identifier, they're generating something that *looks right* based on training data patterns, not verifying it against a live vocabulary source.
**OMOPHub** solves the verification half of this problem. It's a vocabulary API: the source of truth for OMOP concept IDs, SNOMED codes, RxNorm identifiers, LOINC codes, and 100+ other vocabularies. By pairing an LLM (for reasoning and extraction) with OMOPHub (for vocabulary lookup and verification), you get a system where the LLM does what it's good at and OMOPHub does what it's good at. Neither one alone is sufficient. Together, they're a clinical AI pipeline that's both intelligent and grounded.
## 2. The Core Concept: The "OMOP-Loop"
Every clinical AI tool that touches standardized codes should follow this three-step pattern:
**1. Extract**: The LLM processes unstructured text (physician notes, patient questions, trial protocols) and identifies clinical entities: conditions, drugs, procedures, measurements.
**2. Ground**: Each extracted entity is sent to OMOPHub. The API returns the actual, verified OMOP concept ID. No guessing, no hallucination - just a vocabulary lookup. If the entity doesn't match anything in OMOPHub, that's a signal to flag it for review rather than fabricate a code.
**3. Reason**: The LLM uses the grounded, verified concept IDs to perform its final task: generating a query, drafting a coded summary, checking trial eligibility, or building a concept set.
The key insight: the LLM never generates codes directly. It extracts clinical meaning from text (which it's excellent at), then hands off to OMOPHub for the standardization step (which a vocabulary API is built for). This separation of concerns is what makes the system safe.
## 3. Use Case A: Building a Grounded Clinical Research Chatbot
A researcher asks: "Find me all patients with heart failure who are on ACE inhibitors." A generic chatbot understands the intent but doesn't know which OMOP concept IDs to query. A grounded chatbot does.
**The Workflow:** LLM extracts "heart failure" and "ACE inhibitor" then OMOPHub resolves each to a standard concept ID and expands descendants and only then the application generates OMOP CDM SQL with verified IDs.
```bash theme={null}
pip install omophub
```
````python Python theme={null}
import omophub
import json
from openai import OpenAI # Or any LLM client
omop = omophub.OMOPHub()
llm = OpenAI() # API key from environment
def ground_clinical_query(user_query):
"""Extract clinical entities via LLM, ground them via OMOPHub."""
print(f"User query: {user_query}\n")
# --- Step 1: EXTRACT (LLM) ---
extraction_prompt = (
f"Extract clinical conditions and drug classes from this query: "
f"'{user_query}'. Return ONLY a JSON array of strings, no explanation."
)
response = llm.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": extraction_prompt}],
)
raw_output = response.choices[0].message.content.strip()
# Strip markdown code fences if present
if raw_output.startswith("```"):
raw_output = raw_output.split("\n", 1)[1].rsplit("```", 1)[0]
extracted_terms = json.loads(raw_output)
print(f" LLM extracted: {extracted_terms}\n")
grounded = {}
# --- Step 2: GROUND (OMOPHub) ---
for term in extracted_terms:
print(f" Grounding: '{term}'")
# Determine likely domain from context
# (In production, have the LLM tag each entity with its domain)
is_drug = any(kw in term.lower() for kw in ["inhibitor", "blocker", "agonist", "drug", "medication"])
vocab_filter = ["RxNorm"] if is_drug else ["SNOMED"]
domain_filter = ["Drug"] if is_drug else ["Condition"]
try:
# Search OMOPHub for the standard concept
results = omop.search.basic(
term,
vocabulary_ids=vocab_filter,
domain_ids=domain_filter,
page_size=3,
)
candidates = results.get("concepts", []) if results else []
# If basic search misses, try semantic
if not candidates:
semantic = omop.search.semantic(term, vocabulary_ids=vocab_filter, domain_ids=domain_filter, page_size=3)
candidates = (semantic.get("results", semantic.get("concepts", [])) if semantic else [])
if candidates:
best = candidates[0]
concept_id = best["concept_id"]
print(f" -> {best.get('concept_name')} (ID: {concept_id})")
# Expand descendants for comprehensive concept set
concept_set = {concept_id}
try:
desc = omop.hierarchy.descendants(concept_id, max_levels=3, relationship_types=["Is a"])
desc_list = (
desc if isinstance(desc, list)
else desc.get("concepts", [])
) if desc else []
for d in desc_list:
concept_set.add(d["concept_id"])
except omophub.APIError:
pass # Use just the parent if hierarchy fails
grounded[term] = {
"concept_name": best.get("concept_name"),
"concept_id": concept_id,
"domain": best.get("domain_id"),
"expanded_count": len(concept_set),
"concept_ids": list(concept_set),
}
print(f" -> Expanded to {len(concept_set)} concepts (including descendants)")
else:
print(f" -> NOT FOUND - flagging for manual review")
grounded[term] = {"concept_name": None, "concept_id": None, "status": "ungrounded"}
except omophub.APIError as e:
print(f" -> API error: {e.message}")
return grounded
# --- Example ---
query = "Patients with heart failure on ACE inhibitors"
result = ground_clinical_query(query)
# Step 3: REASON - use grounded IDs to build a query
print("\n--- Grounded Concept Sets ---")
for term, data in result.items():
if data.get("concept_id"):
print(f" '{term}': {data['concept_name']} -> {data['expanded_count']} concept IDs")
else:
print(f" '{term}': UNGROUNDED - needs manual resolution")
````
**The Key Insight:** The LLM never touches a concept ID. It extracts "heart failure" and "ACE inhibitor" as text strings. OMOPHub resolves those strings to verified OMOP concept IDs. The hierarchy expansion ensures you catch patients coded with specific subtypes (e.g., "Congestive heart failure" or "Acute on chronic systolic heart failure"). This is the OMOP-Loop in action: Extract → Ground → Reason.
## 4. Use Case B: Grounded Clinical Note Coding
An AI scribe generates a visit summary. For it to be useful for billing and research, the clinical entities need to be linked to verified codes - not LLM-hallucinated ones.
**The Workflow:** LLM generates the summary → LLM extracts key clinical entities → OMOPHub grounds each entity to the correct vocabulary (ICD-10-CM for conditions, CPT-4 for procedures, RxNorm for drugs).
````python Python theme={null}
import omophub
import json
from openai import OpenAI
omop = omophub.OMOPHub()
llm = OpenAI()
# AI-generated visit summary
visit_summary = """
Patient presents with persistent cough and shortness of breath for 2 weeks.
Physical exam reveals wheezing. Suspected acute bronchitis.
Performed a chest X-ray to rule out pneumonia.
Prescribed Albuterol inhaler.
"""
print("Grounding clinical note for coding...\n")
# Step 1: LLM extracts entities with domain tags
extraction_prompt = f"""Extract clinical entities from this visit note.
Return JSON array of objects with "term" and "type" (condition/procedure/drug):
{visit_summary}
Return ONLY the JSON array."""
response = llm.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": extraction_prompt}],
)
raw = response.choices[0].message.content.strip()
if raw.startswith("```"):
raw = raw.split("\n", 1)[1].rsplit("```", 1)[0]
entities = json.loads(raw)
print(f" LLM extracted: {json.dumps(entities, indent=2)}\n")
# Step 2: Ground each entity via OMOPHub in the appropriate vocabulary
DOMAIN_CONFIG = {
"condition": {"vocabs": ["SNOMED"], "domains": ["Condition"]},
"procedure": {"vocabs": ["CPT4", "SNOMED"], "domains": ["Procedure"]},
"drug": {"vocabs": ["RxNorm"], "domains": ["Drug"]},
}
suggested_codes = []
for entity in entities:
term = entity.get("term", "")
entity_type = entity.get("type", "condition").lower()
config = DOMAIN_CONFIG.get(entity_type, DOMAIN_CONFIG["condition"])
print(f" Grounding [{entity_type}]: '{term}'")
try:
results = omop.search.basic(
term,
vocabulary_ids=config["vocabs"],
domain_ids=config["domains"],
page_size=3,
)
candidates = results.get("concepts", []) if results else []
if candidates:
best = candidates[0]
print(f" -> {best.get('concept_name')} ({best.get('vocabulary_id')}: {best.get('concept_code', 'N/A')}, OMOP: {best['concept_id']})")
# For billing codes, also get the ICD-10-CM or CPT4 mapping
if entity_type == "condition":
mappings = omop.mappings.get(best["concept_id"], target_vocabulary="ICD10CM")
map_list = (
mappings if isinstance(mappings, list)
else mappings.get("concepts", mappings.get("mappings", []))
) if mappings else []
if map_list:
icd = map_list[0]
print(f" -> ICD-10-CM: {icd.get('concept_name')} ({icd.get('concept_code')})")
suggested_codes.append({
"term": term,
"type": entity_type,
"concept_name": best.get("concept_name"),
"concept_id": best["concept_id"],
"vocabulary": best.get("vocabulary_id"),
"code": best.get("concept_code"),
})
else:
print(f" -> No match found")
except omophub.APIError as e:
print(f" -> API error: {e.message}")
print(f"\n--- Suggested Codes (All Verified via OMOPHub) ---")
for code in suggested_codes:
print(f" [{code['vocabulary']}] {code['concept_name']} (Code: {code['code']}, OMOP: {code['concept_id']})")
````
**The Key Insight:** The LLM extracts entities and tags them by type. OMOPHub resolves each one in the appropriate vocabulary. No code is ever generated by the LLM - every code comes from a verified vocabulary lookup. This is the difference between "AI-suggested coding" (risky) and "AI-extracted, vocabulary-verified coding" (safe).
## 5. The Hallucination Safety Net
This is the most important pattern in the entire article. Every time an LLM outputs a clinical code or concept ID, verify it against OMOPHub before using it.
```python Python theme={null}
import omophub
omop = omophub.OMOPHub()
def verify_concept(concept_id, expected_domain=None):
"""
Verify that an LLM-generated concept ID actually exists in OMOP.
Returns the concept if valid, None if hallucinated.
"""
try:
concept = omop.concepts.get(concept_id)
if not concept:
return {"valid": False, "reason": "Concept ID does not exist"}
# Check domain if expected
if expected_domain and concept.get("domain_id") != expected_domain:
return {
"valid": False,
"reason": f"Domain mismatch: expected {expected_domain}, got {concept.get('domain_id')}",
"concept": concept,
}
# Check if it's a standard concept
if concept.get("standard_concept") != "S":
return {
"valid": False,
"reason": f"Non-standard concept (standard_concept='{concept.get('standard_concept')}'). Use the 'Maps to' standard equivalent.",
"concept": concept,
}
return {
"valid": True,
"concept_name": concept.get("concept_name"),
"concept_id": concept["concept_id"],
"domain": concept.get("domain_id"),
"vocabulary": concept.get("vocabulary_id"),
}
except omophub.APIError:
return {"valid": False, "reason": "Concept ID not found in OMOP vocabulary"}
# --- Example: LLM claims these are valid concept IDs ---
llm_outputs = [
{"claimed_id": 201826, "claimed_name": "Type 2 diabetes mellitus", "domain": "Condition"},
{"claimed_id": 9999999, "claimed_name": "Fake condition", "domain": "Condition"},
{"claimed_id": 4329847, "claimed_name": "Myocardial infarction", "domain": "Condition"},
]
print("Hallucination Safety Check:\n")
for output in llm_outputs:
result = verify_concept(output["claimed_id"], expected_domain=output["domain"])
status = "VERIFIED" if result["valid"] else "REJECTED"
reason = f" - {result['reason']}" if not result["valid"] else ""
print(f" {status}: ID {output['claimed_id']} ({output['claimed_name']}){reason}")
```
**The Key Insight:** This is a 10-line function that catches hallucinated codes before they enter your system. `concepts.get()` is a real OMOPHub SDK method - it takes a concept ID and returns the concept if it exists. If the LLM hallucinated the ID, the call returns nothing. If the ID exists but is in the wrong domain (the LLM said it was a Condition but it's actually a Drug), the domain check catches it. If it's a non-standard concept, you get a warning to find the standard equivalent. This single validation step is worth more than any amount of prompt engineering for clinical safety.
## 6. Conclusion: Don't Just Prompt - Ground
LLMs are extraordinary reasoning engines. They understand clinical language better than any rule-based system ever could. But they are not vocabulary databases, and they should never be trusted to generate clinical codes from memory.
The OMOP-Loop - Extract, Ground, Reason - is the pattern that makes clinical AI safe:
* The LLM extracts clinical meaning from text (what it's built for)
* OMOPHub verifies and standardizes every entity against the OMOP vocabularies (what it's built for)
* The LLM reasons over verified, grounded data to produce its final output
No hallucinated codes. No fabricated concept IDs. No "mostly right" in a setting where mostly right is dangerously wrong.
Build the OMOP-Loop into your next clinical AI tool. Start with the hallucination safety net - `concepts.get()` on every LLM-generated concept ID. That single check is the difference between a demo and a deployable system.
# HTTP & Docker Deployment
Source: https://docs.omophub.com/ai/mcp-docker
Deploy the OMOPHub MCP Server as an HTTP service or Docker container for centralized, multi-agent access.
## HTTP Transport Mode
Run the MCP server as an HTTP service that clients connect to via URL. Useful for centralized deployments where multiple AI agents share one server instance.
```bash theme={null}
# Start HTTP server on port 3100
npx -y @omophub/omophub-mcp --transport=http --port=3100 --api-key=oh_your_key_here
```
The server exposes two endpoints:
| Endpoint | Description |
| :-------- | :--------------------------------------------------- |
| `/mcp` | MCP protocol endpoint — connect your AI clients here |
| `/health` | Health check endpoint for monitoring |
```bash theme={null}
# Verify the server is running
curl http://localhost:3100/health
# → {"status":"ok","version":"1.1.0","uptime_seconds":42}
```
## Docker
The Docker image defaults to HTTP mode on port 3100 with health checks built in.
### Quick Start
```bash theme={null}
# HTTP mode (default in Docker) — serves MCP on port 3100
docker run -e OMOPHUB_API_KEY=oh_your_key_here -p 3100:3100 omophub/omophub-mcp
```
### Stdio Mode
For piping to local MCP clients:
```bash theme={null}
docker run -i -e OMOPHUB_API_KEY=oh_your_key_here omophub/omophub-mcp --transport=stdio
```
### Docker Compose
```yaml theme={null}
services:
omophub-mcp:
image: omophub/omophub-mcp
ports:
- "3100:3100"
environment:
OMOPHUB_API_KEY: ${OMOPHUB_API_KEY}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3100/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
```
Multi-arch support: the Docker image is available for both `amd64` and `arm64` architectures.
## Health Checks
### HTTP Mode
The health endpoint is automatically available at `/health` on the same port as the MCP endpoint:
```bash theme={null}
npx @omophub/omophub-mcp --transport=http --port=3100 --api-key=oh_your_key
curl http://localhost:3100/health
```
### Stdio Mode
Use `--health-port` for a standalone health endpoint when running in stdio mode:
```bash theme={null}
HEALTH_PORT=8080 OMOPHUB_API_KEY=oh_your_key npx @omophub/omophub-mcp
curl http://localhost:8080/health
```
## CLI Arguments
```bash theme={null}
# Stdio mode (default)
npx @omophub/omophub-mcp --api-key=oh_your_key --base-url=https://custom.api.com/v1
# HTTP mode
npx @omophub/omophub-mcp --transport=http --port=3100 --api-key=oh_your_key
# Stdio mode with standalone health endpoint
npx @omophub/omophub-mcp --api-key=oh_your_key --health-port=8080
```
# MCP Server
Source: https://docs.omophub.com/ai/mcp-server
Install the OMOPHub MCP Server to give Claude, Cursor, VS Code, and other AI clients direct access to medical vocabularies.
## What is the MCP Server?
The OMOPHub MCP Server connects your AI assistant to the entire OHDSI ATHENA vocabulary. No database setup, no CSV wrangling — just ask your AI about medical concepts and get verified answers.
```
You: "Map ICD-10 code E11.9 to SNOMED"
Claude: Found it — E11.9 (Type 2 diabetes mellitus without complications)
maps to SNOMED concept 201826 (Type 2 diabetes mellitus)
via standard 'Maps to' relationship.
```
## Installation
Install via npm — no build step required:
```bash theme={null}
npm install -g @omophub/omophub-mcp
```
Or run directly with `npx`:
```bash theme={null}
npx -y @omophub/omophub-mcp
```
### Claude Desktop
Open Claude Desktop settings > **Developer** tab > **Edit Config**. Add to `claude_desktop_config.json`:
```json theme={null}
{
"mcpServers": {
"omophub": {
"command": "npx",
"args": ["-y", "@omophub/omophub-mcp"],
"env": {
"OMOPHUB_API_KEY": "oh_your_key_here"
}
}
}
}
```
### Claude Code
```bash theme={null}
claude mcp add omophub -- npx -y @omophub/omophub-mcp
# Then set OMOPHUB_API_KEY in your environment
```
### Cursor
Open the command palette > **Cursor Settings** > **MCP** > **Add new global MCP server**. Add to `.cursor/mcp.json`:
```json theme={null}
{
"mcpServers": {
"omophub": {
"command": "npx",
"args": ["-y", "@omophub/omophub-mcp"],
"env": {
"OMOPHUB_API_KEY": "oh_your_key_here"
}
}
}
}
```
### VS Code
Add to `.vscode/mcp.json`:
```json theme={null}
{
"servers": {
"omophub": {
"command": "npx",
"args": ["-y", "@omophub/omophub-mcp"],
"env": {
"OMOPHUB_API_KEY": "oh_your_key_here"
}
}
}
}
```
### Windsurf
Add to your Windsurf MCP configuration:
```json theme={null}
{
"mcpServers": {
"omophub": {
"command": "npx",
"args": ["-y", "@omophub/omophub-mcp"],
"env": {
"OMOPHUB_API_KEY": "oh_your_key_here"
}
}
}
}
```
## Available Tools
| Tool | Description |
| :-------------------- | :--------------------------------------------------------------------------- |
| `search_concepts` | Search for medical concepts by name or clinical term across all vocabularies |
| `get_concept` | Get detailed info about a specific OMOP concept by `concept_id` |
| `get_concept_by_code` | Look up a concept using a vocabulary-specific code (e.g., ICD-10 `E11.9`) |
| `map_concept` | Map a concept to equivalent concepts in other vocabularies |
| `get_hierarchy` | Navigate concept hierarchy — ancestors, descendants, or both |
| `list_vocabularies` | List available medical vocabularies with statistics |
See detailed parameter schemas and examples for each tool.
## Environment Variables
| Variable | Required | Description |
| :------------------------- | :------: | :------------------------------------------------------------- |
| `OMOPHUB_API_KEY` | Yes | Your OMOPHub API key |
| `OMOPHUB_BASE_URL` | | Custom API base URL (default: `https://api.omophub.com/v1`) |
| `OMOPHUB_LOG_LEVEL` | | `debug` · `info` · `warn` · `error` (default: `info`) |
| `OMOPHUB_ANALYTICS_OPTOUT` | | Set to `true` to disable analytics headers |
| `MCP_TRANSPORT` | | `stdio` (default) or `http` |
| `MCP_PORT` | | HTTP server port (default: `3100`, only with `http` transport) |
| `HEALTH_PORT` | | Port for standalone health endpoint in stdio mode |
## Example Prompts
Try these after installing:
> "What's the OMOP concept ID for type 2 diabetes?"
> "Map ICD-10 code E11.9 to SNOMED"
> "Show me all descendants of Diabetes mellitus in SNOMED"
> "Search for metformin in RxNorm"
> "Help me build a concept set for heart failure including all descendants"
## Troubleshooting
| Error | Solution |
| :---------------------- | :---------------------------------------------------------------------------------------------------- |
| `API key required` | Set `OMOPHUB_API_KEY` in your environment or MCP config |
| `Authentication failed` | API key may be invalid or expired — [generate a new one](https://dashboard.omophub.com) |
| `Rate limit exceeded` | Automatic retries are built in. For higher limits, [upgrade your plan](https://dashboard.omophub.com) |
| Tools not appearing | Restart your AI client, verify `npx @omophub/omophub-mcp` runs without errors |
# Tools Reference
Source: https://docs.omophub.com/ai/mcp-tools
Complete parameter reference for all 6 OMOPHub MCP tools.
## search\_concepts
Search for medical concepts by name or clinical term across all vocabularies.
### Parameters
| Parameter | Type | Required | Description |
| :----------------- | :----- | :------: | :------------------------------------------------------------------------------------------------- |
| `query` | string | Yes | The medical term or concept name to search for (1–500 characters) |
| `vocabulary_ids` | string | | Comma-separated vocabulary IDs to filter by. Examples: `SNOMED`, `ICD10CM`, `RxNorm`, `LOINC` |
| `domain_ids` | string | | Comma-separated domain IDs to filter by. Examples: `Condition`, `Drug`, `Measurement`, `Procedure` |
| `standard_concept` | string | | Filter by standard concept status: `S` for Standard, `C` for Classification |
| `page` | number | | Page number, 1-based (default: `1`) |
| `page_size` | number | | Results per page, 1–50 (default: `10`) |
### Example
```
"Search for metformin in RxNorm"
→ search_concepts(query: "metformin", vocabulary_ids: "RxNorm")
```
***
## get\_concept
Get detailed information about a specific OMOP concept by its numeric ID.
### Parameters
| Parameter | Type | Required | Description |
| :----------- | :----- | :------: | :---------------------------------------- |
| `concept_id` | number | Yes | The OMOP concept\_id (numeric identifier) |
### Example
```
"What is concept 201826?"
→ get_concept(concept_id: 201826)
```
***
## get\_concept\_by\_code
Look up a concept using a vocabulary-specific code (e.g., ICD-10 `E11.9`, SNOMED `44054006`).
### Parameters
| Parameter | Type | Required | Description |
| :-------------- | :----- | :------: | :---------------------------------------------------------------------------------------------- |
| `vocabulary_id` | string | Yes | The vocabulary system. Examples: `ICD10CM`, `SNOMED`, `RxNorm`, `LOINC`, `CPT4`, `HCPCS`, `NDC` |
| `concept_code` | string | Yes | The vocabulary-specific code. Examples: `E11.9` (ICD-10), `44054006` (SNOMED), `4850` (LOINC) |
### Example
```
"Look up ICD-10 code E11.9"
→ get_concept_by_code(vocabulary_id: "ICD10CM", concept_code: "E11.9")
```
***
## map\_concept
Map a concept to equivalent concepts in other vocabularies (e.g., SNOMED to ICD-10).
### Parameters
| Parameter | Type | Required | Description |
| :-------------------- | :----- | :------: | :-------------------------------------------------------------------------------- |
| `concept_id` | number | Yes | The source OMOP concept\_id to map from |
| `target_vocabularies` | string | | Comma-separated vocabulary IDs to map to. Examples: `ICD10CM`, `SNOMED`, `RxNorm` |
### Example
```
"Map SNOMED concept 201826 to ICD-10"
→ map_concept(concept_id: 201826, target_vocabularies: "ICD10CM")
```
***
## get\_hierarchy
Navigate concept hierarchy — ancestors, descendants, or both.
### Parameters
| Parameter | Type | Required | Description |
| :--------------- | :----- | :------: | :------------------------------------------------------------------------------------ |
| `concept_id` | number | Yes | The OMOP concept\_id |
| `direction` | string | | `up` for ancestors, `down` for descendants, `both` for full context (default: `both`) |
| `max_levels` | number | | Maximum levels to traverse (default: `5` for up, `10` for down) |
| `max_results` | number | | Maximum nodes to return, 1–500 (default: `500`) |
| `vocabulary_ids` | string | | Comma-separated vocabulary IDs to filter results |
### Example
```
"Show me all descendants of Diabetes mellitus"
→ get_hierarchy(concept_id: 201820, direction: "down", max_levels: 3)
```
***
## list\_vocabularies
List available medical vocabularies with statistics.
### Parameters
| Parameter | Type | Required | Description |
| :-------- | :----- | :------: | :-------------------------------------------------- |
| `search` | string | | Optional search term to filter vocabularies by name |
### Example
```
"What vocabularies are available?"
→ list_vocabularies()
"Search for drug vocabularies"
→ list_vocabularies(search: "drug")
```
# AI Onboarding
Source: https://docs.omophub.com/ai/onboarding
Give your AI agents access to 10M+ medical concepts with zero hallucination risk. Search, map, and navigate OMOP vocabularies from any AI client.
## Why OMOPHub for AI?
LLMs are powerful reasoning engines, but they hallucinate clinical codes. OMOPHub provides the verification layer: a vocabulary API covering SNOMED CT, ICD-10, RxNorm, LOINC, and 100+ medical terminologies.
Your AI agent extracts clinical meaning from text. OMOPHub grounds it in verified OMOP concept IDs. No fabricated codes, no "mostly right" in a setting where mostly right is dangerously wrong.
## Prerequisites
Sign up at [dashboard.omophub.com](https://dashboard.omophub.com).
Go to **API Keys** in your dashboard and create a new key.
## Choose Your Integration Path
Connect OMOPHub to Claude, Cursor, VS Code, or any MCP-compatible AI client.
Install in under a minute.
Feed your AI agent the right documentation format: `llms.txt`, `llms-full.txt`,
or `skill.md`.
Detailed reference for all 6 MCP tools: parameters, descriptions, and example
usage.
Build grounded clinical AI pipelines with the OMOP-Loop pattern. Eliminate
hallucinated codes in production.
## What You Can Do
| Capability | Description |
| :--------------------------- | :----------------------------------------------------------------------- |
| **Concept lookup** | Find OMOP concept IDs for any clinical term in seconds |
| **Cross-vocabulary mapping** | Map between ICD-10, SNOMED, RxNorm, LOINC, and 100+ vocabularies |
| **Hierarchy navigation** | Explore ancestors and descendants for phenotype definitions |
| **Concept set building** | Let your AI agent assemble complete concept sets for cohort definitions |
| **Code validation** | Verify medical codes and check their standard mappings |
| **Hallucination detection** | Validate every LLM-generated concept ID against the OMOP source of truth |
# Batch Concepts
Source: https://docs.omophub.com/api-reference/concepts/batch-concepts
POST /concepts/batch
Retrieve multiple concepts in a single request
## Overview
Efficiently retrieve information for multiple concepts at once. This endpoint is optimized for bulk operations and reduces the number of API calls needed.
## Request Body
Array of concept IDs to retrieve (max: 100)
Include relationships for all concepts
Include synonyms for all concepts
Include cross-vocabulary mappings for all concepts
Filter results to specific vocabularies
Only return standard concepts
```bash cURL theme={null}
curl -X POST "https://api.omophub.com/v1/concepts/batch" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"concept_ids": [320128, 201826, 4329847],
"include_relationships": true,
"include_synonyms": true
}'
```
```python Python theme={null}
import requests
payload = {
"concept_ids": [320128, 201826, 4329847],
"include_relationships": True,
"include_synonyms": True,
"include_mappings": False
}
response = requests.post(
"https://api.omophub.com/v1/concepts/batch",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json=payload
)
concepts = response.json()
for concept in concepts['data']['concepts']:
print(f"{concept['concept_name']} ({concept['vocabulary_id']})")
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/concepts/batch', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
concept_ids: [320128, 201826, 4329847],
include_relationships: true,
include_synonyms: true
})
});
const data = await response.json();
console.log(`Retrieved ${data.data.concepts.length} concepts`);
```
```bash cURL (with filtering) theme={null}
curl -X POST "https://api.omophub.com/v1/concepts/batch" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"concept_ids": [320128, 201826, 4329847, 313217],
"vocabulary_filter": ["SNOMED", "ICD10CM"],
"include_mappings": true,
"standard_only": true
}'
```
```python Python (comprehensive) theme={null}
import requests
# Large batch request with comprehensive data
concept_ids = [320128, 201826, 4329847, 313217, 435216]
payload = {
"concept_ids": concept_ids,
"include_relationships": True,
"include_synonyms": True,
"include_mappings": True,
"standard_only": True
}
response = requests.post(
"https://api.omophub.com/v1/concepts/batch",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json=payload
)
data = response.json()
print(f"Successfully retrieved {len(data['data']['concepts'])} concepts")
print(f"Failed to retrieve {len(data['data']['failed_concepts'])} concepts")
# Process successful concepts
for concept in data['data']['concepts']:
print(f"\n{concept['concept_name']}")
print(f" ID: {concept['concept_id']}")
print(f" Vocabulary: {concept['vocabulary_id']}")
print(f" Domain: {concept['domain_id']}")
if concept.get('synonyms'):
print(f" Synonyms: {len(concept['synonyms'])} found")
if concept.get('relationships'):
print(f" Relationships: {len(concept['relationships'])} found")
```
```json theme={null}
{
"success": true,
"data": {
"concepts": [
{
"concept_id": 320128,
"concept_name": "Essential hypertension",
"concept_code": "59621000",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null,
"synonyms": [
{
"concept_synonym_name": "Primary hypertension",
"language_concept_id": 4180186,
"language_concept_name": "English"
},
{
"concept_synonym_name": "Idiopathic hypertension",
"language_concept_id": 4180186,
"language_concept_name": "English"
}
],
"relationships": [
{
"relationship_id": "Is a",
"target_concept_id": 316866,
"target_concept_name": "Hypertensive disorder",
"target_vocabulary_id": "SNOMED",
"relationship_direction": "outbound"
}
]
},
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null,
"synonyms": [
{
"concept_synonym_name": "Type II diabetes mellitus",
"language_concept_id": 4180186,
"language_concept_name": "English"
},
{
"concept_synonym_name": "Adult-onset diabetes",
"language_concept_id": 4180186,
"language_concept_name": "English"
}
],
"relationships": [
{
"relationship_id": "Is a",
"target_concept_id": 73211009,
"target_concept_name": "Diabetes mellitus",
"target_vocabulary_id": "SNOMED",
"relationship_direction": "outbound"
}
]
},
{
"concept_id": 4329847,
"concept_name": "Myocardial infarction",
"concept_code": "22298006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null,
"synonyms": [
{
"concept_synonym_name": "Heart attack",
"language_concept_id": 4180186,
"language_concept_name": "English"
},
{
"concept_synonym_name": "MI - Myocardial infarction",
"language_concept_id": 4180186,
"language_concept_name": "English"
}
],
"relationships": [
{
"relationship_id": "Is a",
"target_concept_id": 134057,
"target_concept_name": "Acute myocardial infarction",
"target_vocabulary_id": "SNOMED",
"relationship_direction": "inbound"
}
]
}
],
"failed_concepts": [],
"summary": {
"total_requested": 3,
"successful_retrievals": 3,
"failed_retrievals": 0,
"vocabularies_represented": ["SNOMED"],
"domains_represented": ["Condition"]
}
},
"meta": {
"request_id": "req_batch_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2",
"query_time_ms": 45.2
}
}
```
# Batch Hierarchy Queries
Source: https://docs.omophub.com/api-reference/concepts/batch-hierarchy-queries
POST https://api.omophub.com/v1/concepts/hierarchy/batch
Perform multiple hierarchy queries in a single request for efficient bulk processing
## Overview
This endpoint allows you to perform multiple hierarchy queries (ancestors, descendants, or full hierarchy) for multiple concepts in a single API call. This is highly efficient for applications that need hierarchical information for many concepts simultaneously.
## Request Body
Array of hierarchy query requests (1-100 queries)
Unique identifier for this query (used to match results)
The OMOP concept ID to query hierarchy for
Type of hierarchy query. Options: `ancestors`, `descendants`, `hierarchy`, `level`, `relationships`, `related`, `traverse`
Optional parameters for the query
Maximum number of hierarchy levels to traverse
Array of relationship types to follow (default: \["Is a"])
Limit results to specific vocabularies
Limit results to specific domains
Include relationships to invalid/deprecated concepts
## Query Parameters
Specific vocabulary release version (e.g., "2025.1")
## Response
Indicates whether the request was successful
Response data containing batch results
Array of query results matching the input order
The query identifier from the request
The concept ID that was queried
The operation that was performed
Whether this individual query succeeded
Result data if the query succeeded (structure varies by operation)
Array of ancestor concepts (for ancestors/hierarchy operations)
OMOP concept ID
Primary concept name
Source vocabulary identifier
Clinical domain classification
Concept class identifier
Standard concept flag (S, C, or null)
Distance from the source concept in hierarchy
Array of descendant concepts (for descendants/hierarchy operations)
OMOP concept ID
Primary concept name
Source vocabulary identifier
Clinical domain classification
Concept class identifier
Standard concept flag (S, C, or null)
Distance from the source concept in hierarchy
Total number of results
Error message if the query failed
Response metadata
Unique request identifier
ISO 8601 timestamp
Vocabulary release version used
```bash cURL theme={null}
curl -X POST "https://api.omophub.com/v1/concepts/hierarchy/batch" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"queries": [
{
"query_id": "q1",
"concept_id": 201826,
"operation": "ancestors",
"params": {
"max_levels": 5
}
},
{
"query_id": "q2",
"concept_id": 4182210,
"operation": "descendants",
"params": {
"max_levels": 3
}
},
{
"query_id": "q3",
"concept_id": 313217,
"operation": "hierarchy",
"params": {
"max_levels": 4,
"relationship_types": ["Is a"]
}
}
]
}'
```
```python Python theme={null}
import requests
def batch_hierarchy_query(queries, api_key):
url = "https://api.omophub.com/v1/concepts/hierarchy/batch"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
data = {"queries": queries}
response = requests.post(url, json=data, headers=headers)
return response.json()
# Example usage
queries = [
{
"query_id": "diabetes",
"concept_id": 201826,
"operation": "ancestors",
"params": {"max_levels": 5}
},
{
"query_id": "hypertension",
"concept_id": 4182210,
"operation": "descendants",
"params": {"max_levels": 3}
},
{
"query_id": "afib",
"concept_id": 313217,
"operation": "hierarchy",
"params": {"max_levels": 4}
}
]
result = batch_hierarchy_query(queries, "YOUR_API_KEY")
for query_result in result['data']['results']:
if query_result['success']:
data = query_result['data']
ancestors = len(data.get('ancestors', []))
descendants = len(data.get('descendants', []))
print(f"{query_result['query_id']}: {ancestors} ancestors, {descendants} descendants")
else:
print(f"{query_result['query_id']}: Error - {query_result['error']}")
```
```javascript JavaScript theme={null}
const batchHierarchyQuery = async (queries) => {
const response = await fetch('https://api.omophub.com/v1/concepts/hierarchy/batch', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({ queries })
});
return response.json();
};
// Example usage
const queries = [
{ query_id: 'q1', concept_id: 201826, operation: 'ancestors', params: { max_levels: 5 } },
{ query_id: 'q2', concept_id: 4182210, operation: 'descendants', params: { max_levels: 3 } },
{ query_id: 'q3', concept_id: 313217, operation: 'hierarchy', params: { max_levels: 4 } }
];
const result = await batchHierarchyQuery(queries);
result.data.results.forEach((queryResult) => {
if (queryResult.success) {
const ancestors = queryResult.data.ancestors?.length || 0;
const descendants = queryResult.data.descendants?.length || 0;
console.log(`${queryResult.query_id}: ${ancestors} ancestors, ${descendants} descendants`);
} else {
console.error(`${queryResult.query_id} failed: ${queryResult.error}`);
}
});
```
```json theme={null}
{
"success": true,
"data": {
"results": [
{
"query_id": "q1",
"concept_id": 201826,
"operation": "ancestors",
"success": true,
"data": {
"ancestors": [
{
"concept_id": 73211009,
"concept_name": "Diabetes mellitus",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 1
},
{
"concept_id": 64572001,
"concept_name": "Disease",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 2
}
],
"total_count": 2
}
},
{
"query_id": "q2",
"concept_id": 4182210,
"operation": "descendants",
"success": true,
"data": {
"descendants": [
{
"concept_id": 320128,
"concept_name": "Essential hypertension",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 1
}
],
"total_count": 1
}
},
{
"query_id": "q3",
"concept_id": 313217,
"operation": "hierarchy",
"success": true,
"data": {
"ancestors": [
{
"concept_id": 49436004,
"concept_name": "Atrial fibrillation and flutter",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 1
}
],
"descendants": [],
"total_count": 1
}
}
]
},
"meta": {
"request_id": "req_batch_hierarchy_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.1"
}
}
```
## Usage Examples
### Simple Batch Ancestors
Get ancestors for multiple concepts:
```json theme={null}
{
"queries": [
{"query_id": "q1", "concept_id": 201826, "operation": "ancestors"},
{"query_id": "q2", "concept_id": 4182210, "operation": "ancestors"},
{"query_id": "q3", "concept_id": 313217, "operation": "ancestors"}
]
}
```
### Mixed Operations
Combine different operations in one batch:
```json theme={null}
{
"queries": [
{"query_id": "ancestors_query", "concept_id": 201826, "operation": "ancestors", "params": {"max_levels": 5}},
{"query_id": "descendants_query", "concept_id": 4182210, "operation": "descendants", "params": {"max_levels": 3}},
{"query_id": "full_hierarchy", "concept_id": 313217, "operation": "hierarchy", "params": {"max_levels": 4}}
]
}
```
### With Filtering
Use vocabulary and relationship filters:
```json theme={null}
{
"queries": [
{
"query_id": "filtered_query",
"concept_id": 201826,
"operation": "hierarchy",
"params": {
"max_levels": 5,
"relationship_types": ["Is a"],
"vocabulary_ids": ["SNOMED", "ICD10CM"]
}
}
]
}
```
## Important Notes
* **Batch size limit**: Maximum 100 queries per batch request
* **Performance optimization**: Batch processing is significantly faster than individual requests
* **Error handling**: Individual query failures don't affect other queries in the batch
* **Result matching**: Use `query_id` to match results to your input queries
* **Rate limiting**: Batch requests count as single requests for rate limiting purposes
## Related Endpoints
* [Get Concept Ancestors](/api-reference/concepts/get-concept-ancestors) - Single concept ancestors
* [Get Concept Descendants](/api-reference/concepts/get-concept-descendants) - Single concept descendants
* [Get Concept Hierarchy](/api-reference/concepts/get-concept-hierarchy) - Complete hierarchy tree
# Batch Relationship Queries
Source: https://docs.omophub.com/api-reference/concepts/batch-relationship-queries
POST https://api.omophub.com/v1/concepts/relationships/batch
Perform multiple relationship queries in a single request for efficient bulk processing
## Overview
This endpoint allows you to perform multiple relationship-related queries for multiple concepts in a single API call. It supports different operation types: `relationships`, `related`, and `traverse`.
## Request Body
Array of relationship query requests (1-100 queries)
Unique identifier for this query (used to match results)
The OMOP concept ID to query relationships for
Type of relationship query. Options: `relationships`, `related`, `traverse`
Optional parameters for the query
Array of relationship types to filter by (e.g., \["Maps to", "Is a"])
Array of vocabulary IDs to filter related concepts
Include relationships to invalid/deprecated concepts
## Query Parameters
Specific vocabulary release version (e.g., "2025.1")
## Response
Indicates whether the overall batch request was successful
Array of query results matching the input order
The query identifier from the request
The concept ID that was queried
The operation that was performed
Whether this individual query succeeded
Result data if the query succeeded (structure varies by operation)
Error message if the query failed
Response metadata
Unique request identifier
ISO 8601 timestamp
Vocabulary release version used
```bash cURL theme={null}
curl -X POST "https://api.omophub.com/v1/concepts/relationships/batch" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"queries": [
{
"query_id": "diabetes_rels",
"concept_id": 201826,
"operation": "relationships",
"params": {
"relationship_types": ["Maps to", "Is a"]
}
},
{
"query_id": "hypertension_rels",
"concept_id": 4182210,
"operation": "relationships",
"params": {
"relationship_types": ["Maps to"],
"vocabulary_ids": ["ICD10CM", "ICD9CM"]
}
},
{
"query_id": "afib_related",
"concept_id": 313217,
"operation": "related",
"params": {
"include_invalid": false
}
}
]
}'
```
```python Python theme={null}
import requests
def batch_relationship_query(queries, api_key):
url = "https://api.omophub.com/v1/concepts/relationships/batch"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
data = {"queries": queries}
response = requests.post(url, json=data, headers=headers)
return response.json()
# Example: Batch query for cardiovascular conditions
queries = [
{
"query_id": "diabetes",
"concept_id": 201826,
"operation": "relationships",
"params": {
"relationship_types": ["Maps to", "Is a"]
}
},
{
"query_id": "hypertension",
"concept_id": 4182210,
"operation": "relationships",
"params": {
"relationship_types": ["Maps to"],
"vocabulary_ids": ["ICD10CM"]
}
},
{
"query_id": "afib",
"concept_id": 313217,
"operation": "related",
"params": {}
}
]
result = batch_relationship_query(queries, "YOUR_API_KEY")
for query_result in result['data']:
if query_result['success']:
print(f"{query_result['query_id']}: Success")
else:
print(f"{query_result['query_id']}: Error - {query_result['error']}")
```
```javascript JavaScript theme={null}
const batchRelationshipQuery = async (queries) => {
const response = await fetch('https://api.omophub.com/v1/concepts/relationships/batch', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({ queries })
});
return response.json();
};
// Example usage
const queries = [
{
query_id: 'q1',
concept_id: 201826,
operation: 'relationships',
params: {
relationship_types: ['Maps to', 'Is a']
}
},
{
query_id: 'q2',
concept_id: 4182210,
operation: 'relationships',
params: {
relationship_types: ['Maps to'],
vocabulary_ids: ['ICD10CM']
}
},
{
query_id: 'q3',
concept_id: 313217,
operation: 'related',
params: {}
}
];
const result = await batchRelationshipQuery(queries);
result.data.forEach((queryResult) => {
if (queryResult.success) {
console.log(`${queryResult.query_id}: Success`);
} else {
console.error(`${queryResult.query_id} failed: ${queryResult.error}`);
}
});
```
```json theme={null}
{
"success": true,
"data": [
{
"query_id": "diabetes_rels",
"concept_id": 201826,
"operation": "relationships",
"success": true,
"data": {
"relationships": [
{
"relationship_id": "Maps to",
"concept_id_2": 44054006,
"concept_name": "Diabetes mellitus type 2",
"vocabulary_id": "SNOMED"
},
{
"relationship_id": "Is a",
"concept_id_2": 73211009,
"concept_name": "Diabetes mellitus",
"vocabulary_id": "SNOMED"
}
]
}
},
{
"query_id": "hypertension_rels",
"concept_id": 4182210,
"operation": "relationships",
"success": true,
"data": {
"relationships": [
{
"relationship_id": "Maps to",
"concept_id_2": 312327,
"concept_name": "Essential hypertension",
"vocabulary_id": "ICD10CM"
}
]
}
},
{
"query_id": "afib_related",
"concept_id": 313217,
"operation": "related",
"success": true,
"data": {
"related_concepts": [
{
"concept_id": 49436004,
"concept_name": "Atrial fibrillation and flutter",
"vocabulary_id": "SNOMED"
}
]
}
}
],
"meta": {
"request_id": "req_batch_relationships_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.1"
}
}
```
## Usage Examples
### Basic Batch Query
Get relationships for multiple concepts:
```json theme={null}
{
"queries": [
{"query_id": "q1", "concept_id": 201826, "operation": "relationships"},
{"query_id": "q2", "concept_id": 4182210, "operation": "relationships"},
{"query_id": "q3", "concept_id": 313217, "operation": "relationships"}
]
}
```
### Filtered by Relationship Type
Get only mapping relationships:
```json theme={null}
{
"queries": [
{
"query_id": "diabetes_maps",
"concept_id": 201826,
"operation": "relationships",
"params": {
"relationship_types": ["Maps to"]
}
},
{
"query_id": "hypertension_maps",
"concept_id": 4182210,
"operation": "relationships",
"params": {
"relationship_types": ["Maps to"],
"vocabulary_ids": ["ICD10CM"]
}
}
]
}
```
### Mixed Operations
Combine different operations in one batch:
```json theme={null}
{
"queries": [
{
"query_id": "rels_query",
"concept_id": 201826,
"operation": "relationships",
"params": {
"relationship_types": ["Is a", "Subsumes"]
}
},
{
"query_id": "related_query",
"concept_id": 4182210,
"operation": "related",
"params": {}
},
{
"query_id": "traverse_query",
"concept_id": 313217,
"operation": "traverse",
"params": {
"relationship_types": ["Is a"]
}
}
]
}
```
## Important Notes
* **Batch size limit**: Maximum 100 queries per batch request
* **Performance optimization**: Batch processing is significantly more efficient than individual requests
* **Error isolation**: Individual query failures don't affect other queries in the batch
* **Result matching**: Use `query_id` to match results to your input queries
* **Rate limiting**: Batch requests count as single requests for rate limiting purposes
## Related Endpoints
* [Get Concept Relationships](/api-reference/concepts/get-concept-relationships) - Single concept relationships
* [Batch Hierarchy Queries](/api-reference/concepts/batch-hierarchy-queries) - Batch hierarchy queries
* [Traverse Relationships](/api-reference/concepts/traverse-relationships) - Complex relationship traversals
# Check Concept Relations
Source: https://docs.omophub.com/api-reference/concepts/check-concept-relations
GET https://api.omophub.com/v1/concepts/{conceptId}/relations/any
Check if a concept has any relationships without returning the full relationship data
## Overview
This lightweight endpoint quickly determines whether a concept has any relationships to other concepts. This is useful for UI components, validation logic, or filtering concepts that are isolated vs. connected in the terminology network.
## Path Parameters
The unique OMOP concept ID to check for relationships
## Query Parameters
Comma-separated list of relationship types to check (e.g., "Maps to", "Is a")
Comma-separated list of vocabulary IDs to limit relationship check
Include reverse relationships (concepts that relate to this concept)
Include relationships to invalid/deprecated concepts
Specific vocabulary release version (e.g., "2024.1")
## Response
Indicates whether the request was successful
Response data containing relationship check results
The concept ID that was checked
Whether this concept has any relationships matching the specified criteria
Summary information about relationships (when has\_relations is true)
Total number of relationships found
Number of outbound relationships (from this concept)
Number of inbound relationships (to this concept)
Array of relationship types found (up to 10 most common)
Array of vocabularies that have related concepts (up to 10)
Whether concept has mappings to standard concepts
Details about what was checked
Relationship types included in the check
Vocabularies included in the check
Whether reverse relationships were checked
Whether invalid relationships were checked
Response metadata and API information
Unique request identifier for debugging
ISO 8601 timestamp of the response
Vocabulary release version used
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/201826/relations/any" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
concept_id = 201826 # Type 2 diabetes
url = f"https://api.omophub.com/v1/concepts/{concept_id}/relations/any"
params = {
"relationship_types": "Maps to,Is a",
"include_reverse": True
}
headers = {
"Authorization": "Bearer YOUR_API_KEY"
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
if data['data']['has_relations']:
print(f"✓ Concept {concept_id} has relationships")
summary = data['data']['relation_summary']
print(f" - Total: {summary['total_count']}")
print(f" - Standard mappings: {'Yes' if summary['has_standard_mappings'] else 'No'}")
else:
print(f"✗ Concept {concept_id} has no relationships")
```
```javascript JavaScript theme={null}
const conceptId = 201826; // Type 2 diabetes
const response = await fetch(`https://api.omophub.com/v1/concepts/${conceptId}/relations/any`, {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const relationCheck = await response.json();
const data = relationCheck.data;
if (data.has_relations) {
console.log(`Concept ${conceptId} has ${data.relation_summary.total_count} relationships`);
console.log('Relationship types:', data.relation_summary.relationship_types);
} else {
console.log(`Concept ${conceptId} has no relationships`);
}
```
```bash cURL (filtered check) theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/201826/relations/any?relationship_types=Maps%20to&vocabulary_ids=ICD10CM,ICD9CM" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (batch validation) theme={null}
import requests
def validate_concept_connectivity(concept_ids):
"""
Check which concepts have standard mappings for data quality validation
"""
connected_concepts = []
isolated_concepts = []
for concept_id in concept_ids:
url = f"https://api.omophub.com/v1/concepts/{concept_id}/relations/any"
params = {
"relationship_types": "Maps to",
"vocabulary_ids": "SNOMED,ICD10CM,ICD9CM"
}
response = requests.get(url, params=params, headers={
"Authorization": "Bearer YOUR_API_KEY"
})
data = response.json()
concept_data = data['data']
if concept_data['has_relations'] and concept_data['relation_summary']['has_standard_mappings']:
connected_concepts.append({
'concept_id': concept_id,
'total_mappings': concept_data['relation_summary']['total_count'],
'vocabularies': concept_data['relation_summary']['connected_vocabularies']
})
else:
isolated_concepts.append(concept_id)
print(f"Connected concepts: {len(connected_concepts)}")
print(f"Isolated concepts: {len(isolated_concepts)}")
return connected_concepts, isolated_concepts
# Validate a set of cardiovascular concepts
cardiovascular_concepts = [201826, 4182210, 313217, 320128, 434557]
connected, isolated = validate_concept_connectivity(cardiovascular_concepts)
for concept in connected[:3]: # Show top 3
print(f"Concept {concept['concept_id']}: {concept['total_mappings']} mappings across {len(concept['vocabularies'])} vocabularies")
```
```json theme={null}
{
"success": true,
"data": {
"concept_id": 201826,
"has_relations": true,
"relation_summary": {
"total_count": 15,
"outbound_count": 8,
"inbound_count": 7,
"relationship_types": ["Maps to", "Is a", "Has finding site", "May be a"],
"connected_vocabularies": ["SNOMED", "ICD10CM", "ICD9CM"],
"has_standard_mappings": true
},
"checks_performed": {
"included_relationship_types": ["all"],
"included_vocabularies": ["all"],
"included_reverse_relations": true,
"included_invalid_relations": false
}
},
"meta": {
"request_id": "req_check_relations_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Simple Relationship Check
Check if a concept has any relationships:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/201826/relations/any" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Check for Standard Mappings
Check if concept has "Maps to" relationships:
```bash theme={null}
curl -G "https://api.omophub.com/v1/concepts/201826/relations/any" \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "relationship_types=Maps to"
```
### Check Cross-Vocabulary Relations
Check relationships to specific vocabularies:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/201826/relations/any?vocabulary_ids=ICD10CM,ICD9CM" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Batch Checking in Applications
Use in loops or batch processing to filter connected concepts:
```javascript theme={null}
const conceptIds = [201826, 4182210, 313217];
const connectedConcepts = [];
for (const id of conceptIds) {
const response = await fetch(`/v1/concepts/${id}/relations/any`);
const result = await response.json();
const data = result.data;
if (data.has_relations && data.relation_summary.has_standard_mappings) {
connectedConcepts.push({
concept_id: id,
relation_count: data.relation_summary.total_count
});
}
}
```
## Important Notes
* **Performance optimized** - Much faster than fetching full relationship data
* **Filtering support** - All the same filters as the full relations endpoint
* **UI integration** - Perfect for showing relationship icons or badges
* **Batch processing** - Ideal for filtering large concept lists
* **Standard mappings** - The `has_standard_mappings` flag is useful for data quality checks
## Related Endpoints
* [Get Concept Relationships](/api-reference/concepts/get-concept-relationships) - Get full relationship data
* [Batch Hierarchy Queries](/api-reference/concepts/batch-hierarchy-queries) - Check relationships for multiple concepts
# Get Concept
Source: https://docs.omophub.com/api-reference/concepts/get-concept
GET /concepts/{conceptId}
Get detailed information about a specific medical concept
## Overview
Retrieve comprehensive information about a medical concept using its unique concept ID. This endpoint provides full concept details including synonyms, relationships, and classification information.
## Path Parameters
The unique concept identifier (e.g., 320128 for "Essential hypertension")
## Query Parameters
Specific vocabulary release to use (e.g., "2025.2"). Uses latest if not specified.
Include related concepts (parents, children, mappings)
Include all synonyms and alternative names
Include ancestor and descendant concepts in hierarchy
Include concepts with invalid\_reason set (deprecated/updated concepts).
Set to false to only return valid concepts.
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/320128" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/concepts/320128",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
concept = response.json()
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/concepts/320128', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const concept = await response.json();
```
```bash cURL (with details) theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/320128?include_relationships=true&include_synonyms=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (with details) theme={null}
import requests
params = {
"include_relationships": True,
"include_synonyms": True
}
response = requests.get(
"https://api.omophub.com/v1/concepts/320128",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params=params
)
concept = response.json()
```
```json theme={null}
{
"success": true,
"data": {
"concept_id": 320128,
"concept_name": "Essential hypertension",
"concept_code": "59621000",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null,
"is_valid": true,
"is_standard": true,
"is_classification": false,
"synonyms": [
"Primary hypertension",
"Idiopathic hypertension",
"High blood pressure"
],
"relationships": {
"parents": [
{
"concept_id": 316866,
"concept_name": "Hypertensive disorder",
"relationship_id": "Is a"
}
],
"children": [
{
"concept_id": 4124681,
"concept_name": "Accelerated hypertension",
"relationship_id": "Subsumes"
}
]
}
},
"meta": {
"request_id": "req_concept_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Common Use Cases
### 1. Clinical Decision Support
Get concept details for clinical coding and documentation systems.
### 2. Data Validation
Verify concept information during data quality checks.
### 3. User Interface Display
Show detailed concept information in healthcare applications.
### 4. Relationship Analysis
Understand concept hierarchies and mappings for analysis.
## Related Endpoints
* [Get Concept by Code](/api-reference/concepts/get-concept-by-code) - Look up concept using vocabulary-specific code
* [Get Concept Relationships](/api-reference/concepts/get-concept-relationships) - Get all relationships for a concept
* [Search Concepts](/api-reference/search/basic-search) - Search for concepts by name or description
# Get Concept by Code
Source: https://docs.omophub.com/api-reference/concepts/get-concept-by-code
GET /concepts/by-code/{vocabulary_id}/{concept_code}
Look up a concept using its vocabulary-specific code
## Overview
Retrieve concept information using the vocabulary-specific code instead of the universal concept ID. This is useful when working with source data that contains original codes.
## Path Parameters
The vocabulary identifier (e.g., "SNOMED", "ICD10CM", "LOINC")
The vocabulary-specific code (e.g., "59621000" for SNOMED, "I10" for ICD10CM)
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/by-code/SNOMED/59621000" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/concepts/by-code/SNOMED/59621000",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
concept = response.json()
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/concepts/by-code/SNOMED/59621000', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const concept = await response.json();
```
```bash cURL (ICD10CM) theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/by-code/ICD10CM/I10" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (ICD10CM) theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/concepts/by-code/ICD10CM/I10",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
icd_concept = response.json()
```
```json Response theme={null}
{
"success": true,
"data": {
"concept_id": 320128,
"concept_name": "Essential hypertension",
"concept_code": "59621000",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01T00:00:00.000Z",
"valid_end_date": "2099-12-31T00:00:00.000Z",
"invalid_reason": null,
"is_valid": true,
"is_standard": true,
"is_classification": false
},
"meta": {
"request_id": "req_concept_code_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
```json Response (with details) theme={null}
{
"success": true,
"data": {
"concept_id": 320128,
"concept_name": "Essential hypertension",
"concept_code": "59621000",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01T00:00:00.000Z",
"valid_end_date": "2099-12-31T00:00:00.000Z",
"invalid_reason": null,
"is_valid": true,
"is_standard": true,
"is_classification": false,
"synonyms": [
"Primary hypertension",
"Idiopathic hypertension",
"High blood pressure"
],
"relationships": {
"parents": [
{
"concept_id": 316866,
"concept_name": "Hypertensive disorder",
"relationship_id": "Is a"
}
],
"children": [
{
"concept_id": 4124681,
"concept_name": "Accelerated hypertension",
"relationship_id": "Subsumes"
}
]
}
},
"meta": {
"request_id": "req_concept_code_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Response Fields
| Field | Type | Description |
| ------------------- | ------- | ----------------------------------------------------------------------- |
| `concept_id` | integer | Unique concept identifier |
| `concept_name` | string | Human-readable concept name |
| `concept_code` | string | Vocabulary-specific code |
| `vocabulary_id` | string | Source vocabulary identifier |
| `domain_id` | string | Clinical domain (Condition, Drug, etc.) |
| `concept_class_id` | string | Concept classification within vocabulary |
| `standard_concept` | string | "S" (Standard), "C" (Classification), or null |
| `valid_start_date` | string | ISO 8601 timestamp when concept became valid |
| `valid_end_date` | string | ISO 8601 timestamp when concept expires |
| `invalid_reason` | string | Reason for deprecation, or null if valid |
| `is_valid` | boolean | Whether concept is currently valid (not expired) |
| `is_standard` | boolean | Whether concept is a standard concept |
| `is_classification` | boolean | Whether concept is a classification concept |
| `synonyms` | array | List of synonym names (only when `include_synonyms=true`) |
| `relationships` | object | Parent and child relationships (only when `include_relationships=true`) |
## Query Parameters
Specific vocabulary release to use (e.g., "2025.2"). Uses latest if not specified.
Include concept synonyms in the response
Include concept relationships and mappings
Include concept hierarchy information
Include concepts with invalid\_reason set (deprecated/updated concepts).
Set to false to only return valid concepts.
## Common Use Cases
### 1. Source Data Processing
Convert source system codes to standardized concepts during ETL processes.
### 2. Legacy System Integration
Map existing codes from legacy healthcare systems.
### 3. Claims Processing
Look up diagnosis and procedure codes from insurance claims.
### 4. Clinical Documentation
Validate codes entered by healthcare providers.
# Get Concept Level
Source: https://docs.omophub.com/api-reference/concepts/get-concept-level
GET https://api.omophub.com/v1/concepts/{conceptId}/level
Get basic hierarchical level and depth information for a concept
## Overview
This endpoint returns basic hierarchical positioning information for a concept, including its depth from root concepts and counts of ancestors and descendants.
## Path Parameters
The unique OMOP concept ID to get level information for
## Query Parameters
Specific vocabulary release version (e.g., "2025.1")
## Response
Indicates whether the request was successful
Response data containing concept level information
The concept ID that was queried
The level of the concept in the hierarchy (0-based)
Number of levels from the root concept to this concept
Number of concepts at the same level (may be null if not calculated)
Total number of descendant concepts below this concept
Total number of ancestor concepts above this concept
Response metadata and API information
Unique request identifier for debugging
ISO 8601 timestamp of the response
Vocabulary release version used
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/201826/level" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
concept_id = 201826 # Type 2 diabetes
url = f"https://api.omophub.com/v1/concepts/{concept_id}/level"
headers = {
"Authorization": "Bearer YOUR_API_KEY"
}
response = requests.get(url, headers=headers)
data = response.json()
level_info = data['data']
print(f"Concept ID: {level_info['concept_id']}")
print(f"Depth from root: {level_info['depth_from_root']}")
print(f"Total ancestors: {level_info['total_ancestors']}")
print(f"Total descendants: {level_info['total_descendants']}")
```
```javascript JavaScript theme={null}
const conceptId = 201826; // Type 2 diabetes
const response = await fetch(`https://api.omophub.com/v1/concepts/${conceptId}/level`, {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const levelInfo = await response.json();
const data = levelInfo.data;
console.log(`Concept ID: ${data.concept_id}`);
console.log(`Depth from root: ${data.depth_from_root}`);
console.log(`Total ancestors: ${data.total_ancestors}`);
console.log(`Total descendants: ${data.total_descendants}`);
```
```json theme={null}
{
"success": true,
"data": {
"concept_id": 201826,
"level": 0,
"depth_from_root": 4,
"breadth_at_level": null,
"total_descendants": 15,
"total_ancestors": 3
},
"meta": {
"request_id": "req_concept_level_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.1"
}
}
```
## Usage Examples
### Basic Level Information
Get hierarchy level for a concept:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/201826/level" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### With Specific Vocabulary Version
Get level information for a specific vocabulary release:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/201826/level?vocab_release=2025.1" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Important Notes
* **Depth interpretation** - `depth_from_root` indicates how many "Is a" relationship steps exist between this concept and the root
* **Descendant count** - `total_descendants` includes all child concepts at any depth below
* **Ancestor count** - `total_ancestors` includes all parent concepts up to the root
## Related Endpoints
* [Get Concept Hierarchy](/api-reference/concepts/get-concept-hierarchy) - Full hierarchical tree
* [Get Concept Ancestors](/api-reference/concepts/get-concept-ancestors) - Parent concepts
* [Get Concept Descendants](/api-reference/concepts/get-concept-descendants) - Child concepts
# Get Concept Relationships
Source: https://docs.omophub.com/api-reference/concepts/get-concept-relationships
GET https://api.omophub.com/v1/concepts/{conceptId}/relationships
Get all relationships for a specific concept
## Overview
Retrieve all relationships for a concept, including hierarchical relationships (parents/children), mappings to other vocabularies, and semantic relationships.
## Path Parameters
The unique concept identifier
## Query Parameters
Filter by relationship type IDs (comma-separated)
**Examples**: `Is a`, `Subsumes`, `Maps to`, `Is a,Subsumes`
Filter relationships to specific target vocabularies (comma-separated)
**Examples**: `SNOMED`, `ICD10CM`, `SNOMED,ICD10CM`
Filter relationships to specific target domains (comma-separated)
**Examples**: `Condition`, `Drug`, `Condition,Procedure`
Include relationships to invalid concepts
Only include relationships to standard concepts
Include reverse relationships (where this concept is the target)
Specific vocabulary release version (defaults to latest)
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/320128/relationships" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/concepts/320128/relationships",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
relationships = response.json()
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/concepts/320128/relationships', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const relationships = await response.json();
```
```bash cURL (filtered) theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/320128/relationships?relationship_ids=Is%20a&vocabulary_ids=SNOMED" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (filtered) theme={null}
import requests
params = {
"relationship_ids": "Is a",
"vocabulary_ids": "SNOMED",
"standard_only": True
}
response = requests.get(
"https://api.omophub.com/v1/concepts/320128/relationships",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params=params
)
filtered_relationships = response.json()
```
```json theme={null}
{
"success": true,
"data": {
"relationships": [
{
"relationship_id": "Is a",
"concept_id_1": 320128,
"concept_id_2": 316866,
"concept_name": "Hypertensive disorder",
"concept_code": "38341003",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31"
},
{
"relationship_id": "Maps to",
"concept_id_1": 320128,
"concept_id_2": 435216,
"concept_name": "Essential hypertension",
"concept_code": "I10",
"vocabulary_id": "ICD10CM",
"domain_id": "Condition",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31"
},
{
"relationship_id": "Has finding site",
"concept_id_1": 320128,
"concept_id_2": 4103720,
"concept_name": "Cardiovascular system",
"concept_code": "113257007",
"vocabulary_id": "SNOMED",
"domain_id": "Spec Anatomic Site",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31"
}
]
},
"meta": {
"request_id": "req_relationships_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2",
"version": "2025.2",
"duration": 45
}
}
```
## Response Fields
Indicates if the request was successful
Array of relationship objects
Type of relationship (e.g., "Is a", "Maps to", "Subsumes")
Source concept ID
Target concept ID
Target concept name
Target concept code
Target vocabulary ID
Target domain ID
Standard concept flag (S=Standard, C=Classification, null=Non-standard)
Relationship validity start date
Relationship validity end date
Unique identifier for the request
Request timestamp (ISO 8601 UTC format)
Vocabulary release version used
Schema version used for the query
Request duration in milliseconds
## Usage Examples
### Get All Relationships
Retrieve all relationships for a concept:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/320128/relationships" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Filter by Relationship Type
Get only hierarchical relationships:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/320128/relationships?relationship_ids=Is%20a,Subsumes" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Filter by Target Vocabulary
Get mappings to specific vocabularies:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/320128/relationships?vocabulary_ids=ICD10CM,ICD9CM" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Standard Concepts Only
Get relationships to standard concepts only:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/320128/relationships?standard_only=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Include Reverse Relationships
Include relationships where this concept is the target:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/320128/relationships?include_reverse=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Common Relationship Types
| Relationship | Description |
| --------------------------- | -------------------------------- |
| `Is a` | Hierarchical parent relationship |
| `Subsumes` | Hierarchical child relationship |
| `Maps to` | Mapping to another vocabulary |
| `Mapped from` | Reverse mapping relationship |
| `Has finding site` | Anatomical location |
| `Has associated morphology` | Associated morphological finding |
## Related Endpoints
* [Get Concept](/api-reference/concepts/get-concept) - Get detailed concept information
* [Get Relationship Options](/api-reference/concepts/get-relationship-options) - Get available relationship types for a concept
* [Traverse Relationships](/api-reference/concepts/traverse-relationships) - Complex relationship traversal
# Get Trending Concepts
Source: https://docs.omophub.com/api-reference/concepts/get-concept-trending
GET https://api.omophub.com/v1/concepts/trending
Retrieve trending concepts based on search frequency and usage patterns.
## Overview
This endpoint provides insights into trending concepts by analyzing search frequency and usage patterns. It identifies concepts that are seeing increased activity over different time periods.
## Query Parameters
Time period for trend analysis
**Options**: `day`, `week`, `month`
Filter trends to specific vocabularies (comma-separated)
**Examples**: `SNOMED`, `SNOMED,ICD10CM`, `RXNORM,LOINC`
Maximum number of trending concepts to return (1-1000)
Specific vocabulary release version (defaults to latest)
## Response
Indicates if the request was successful
Time period analyzed (`day`, `week`, or `month`)
Array of trending concepts
Unique concept identifier
Primary concept name
Source vocabulary identifier
Number of searches for this concept in the period
Percentage growth rate compared to the previous period
Calculated trend score (higher = more trending). Weighted combination of search volume (60%) and growth rate (40%).
Total number of trending concepts found
Maximum results returned (from page\_size parameter)
Unique identifier for the request
Request timestamp (ISO 8601 UTC format)
Vocabulary release version used
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/trending?period=week&vocabulary_ids=SNOMED,ICD10CM&page_size=20" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/concepts/trending?period=week&vocabulary_ids=SNOMED&page_size=50', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const data = await response.json();
console.log(`Found ${data.data.total_concepts} trending concepts`);
```
```python Python theme={null}
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY'
}
params = {
'period': 'month',
'vocabulary_ids': 'SNOMED,ICD10CM',
'page_size': 50
}
response = requests.get(
'https://api.omophub.com/v1/concepts/trending',
headers=headers,
params=params
)
data = response.json()
for concept in data['data']['concepts']:
print(f"{concept['concept_name']}: score {concept['trend_score']}")
```
```json Response theme={null}
{
"success": true,
"data": {
"period": "week",
"concepts": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "SNOMED",
"search_count": 1547,
"growth_rate": 23.5,
"trend_score": 7.8
},
{
"concept_id": 320128,
"concept_name": "Essential hypertension",
"vocabulary_id": "SNOMED",
"search_count": 1234,
"growth_rate": 18.2,
"trend_score": 6.9
},
{
"concept_id": 4329847,
"concept_name": "Myocardial infarction",
"vocabulary_id": "SNOMED",
"search_count": 987,
"growth_rate": 15.7,
"trend_score": 5.4
}
],
"total_concepts": 156,
"limit": 20
},
"meta": {
"request_id": "req_trending_abc123",
"timestamp": "2024-01-15T10:30:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Weekly Trending Concepts
Get trending concepts from the past week:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/trending?period=week&page_size=50" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Vocabulary-Specific Trends
Focus on trends within specific vocabularies:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/trending?vocabulary_ids=SNOMED&period=month" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Daily Trending
Get the most recent daily trends:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/trending?period=day&page_size=100" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Trend Score Calculation
The `trend_score` is a composite metric calculated from:
* **Search volume (60% weight)**: Number of searches for the concept in the current period
* **Growth rate (40% weight)**: Percentage change compared to the previous period of equal length
Higher scores indicate concepts with both high search volume and significant growth.
## Related Endpoints
* [Search Concepts](/api-reference/search/basic-search) - Search for specific concepts
* [Get Concept](/api-reference/concepts/get-concept) - Detailed concept information
* [Suggest Concepts](/api-reference/concepts/suggest-concepts) - Concept suggestions
# Get Related Concepts
Source: https://docs.omophub.com/api-reference/concepts/get-related-concepts
GET https://api.omophub.com/v1/concepts/{conceptId}/related
Find conceptually related concepts through relationship analysis
## Overview
This endpoint finds concepts that are related to a given concept through various relationship types. Results are scored and ordered by relationship strength.
## Path Parameters
The unique OMOP concept ID to find related concepts for
## Query Parameters
Maximum number of related concepts to return (1-100)
Minimum relationship score (0.0-1.0) to include in results
Comma-separated list of relationship types to filter by (e.g., "Is a,Maps to")
Specific vocabulary release version (e.g., "2025.1")
## Response
Indicates if the request was successful
Array of related concepts ordered by relationship score
Related concept ID
Related concept name
Related concept code
Related concept vocabulary ID
Related concept vocabulary name
Related concept domain
Related concept class
Standard concept flag ("S" for standard, "C" for classification, null for non-standard)
Relationship type ID (e.g., "Is a", "Maps to")
Human-readable relationship name
Relationship strength score (0.0-1.0)
Number of relationship steps from source concept
Response metadata
Unique request identifier
ISO 8601 timestamp
Vocabulary release version used
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/201826/related?min_score=0.4&page_size=20" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
concept_id = 201826 # Type 2 diabetes
url = f"https://api.omophub.com/v1/concepts/{concept_id}/related"
params = {
"min_score": 0.4,
"page_size": 30,
"relationship_types": "Is a,Maps to"
}
headers = {
"Authorization": "Bearer YOUR_API_KEY"
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
print(f"Found {len(data['data'])} related concepts")
for related in data['data'][:5]:
print(f" - {related['concept_name']} ({related['vocabulary_id']})")
print(f" Relationship: {related['relationship_name']}")
print(f" Score: {related['relationship_score']:.2f}")
```
```javascript JavaScript theme={null}
const conceptId = 201826; // Type 2 diabetes
const response = await fetch(`https://api.omophub.com/v1/concepts/${conceptId}/related?min_score=0.3&page_size=20`, {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const result = await response.json();
console.log(`Found ${result.data.length} related concepts`);
result.data.slice(0, 5).forEach((concept, index) => {
console.log(`${index + 1}. ${concept.concept_name} (${concept.vocabulary_id})`);
console.log(` Relationship: ${concept.relationship_name}`);
console.log(` Score: ${concept.relationship_score.toFixed(2)}`);
});
```
```json theme={null}
{
"success": true,
"data": [
{
"concept_id": 73211009,
"concept_name": "Diabetes mellitus",
"concept_code": "73211009",
"vocabulary_id": "SNOMED",
"vocabulary_name": "SNOMED CT",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"relationship_id": "Is a",
"relationship_name": "Is a",
"relationship_score": 0.95,
"relationship_distance": 1
},
{
"concept_id": 443735,
"concept_name": "Type 2 diabetes mellitus without complications",
"concept_code": "443735",
"vocabulary_id": "SNOMED",
"vocabulary_name": "SNOMED CT",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"relationship_id": "Subsumes",
"relationship_name": "Subsumes",
"relationship_score": 0.87,
"relationship_distance": 1
},
{
"concept_id": 435216,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "E11",
"vocabulary_id": "ICD10CM",
"vocabulary_name": "ICD10CM",
"domain_id": "Condition",
"concept_class_id": "ICD10 code",
"standard_concept": null,
"relationship_id": "Maps to",
"relationship_name": "Maps to",
"relationship_score": 0.92,
"relationship_distance": 1
}
],
"meta": {
"request_id": "req_related_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.1"
}
}
```
## Usage Examples
### Basic Related Concepts
Find general related concepts:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/201826/related" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### High-Score Relationships
Get only strongly related concepts:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/201826/related?min_score=0.7&page_size=10" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Filtered by Relationship Type
Find specific relationship types:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/201826/related?relationship_types=Is%20a,Maps%20to" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Important Notes
* **Score interpretation** - Scores above 0.7 indicate strong relationships, 0.4-0.7 moderate, below 0.4 weak
* **Distance interpretation** - Distance of 1 means direct relationship, higher values indicate indirect connections
* **Performance** - Results are cached for improved response times on repeated queries
## Related Endpoints
* [Get Concept Relationships](/api-reference/concepts/get-concept-relationships) - Get all direct relationships
* [Traverse Relationships](/api-reference/concepts/traverse-relationships) - Complex relationship traversals
# Get Relationship Options
Source: https://docs.omophub.com/api-reference/concepts/get-relationship-options
GET https://api.omophub.com/v1/concepts/{conceptId}/relationships/options
Get available relationship types for a specific concept
## Overview
This endpoint returns all available relationship types that exist for a specific concept. This is useful for building dynamic UI components like dropdowns and understanding what relationships are available for a concept.
## Path Parameters
The unique OMOP concept ID to get relationship options for
## Query Parameters
Specific vocabulary release version (e.g., "2025.1")
## Response
Indicates whether the request was successful
Response data containing relationship options
The concept ID that was queried
Array of available relationship types for this concept
Unique relationship type ID (e.g., "Is a", "Maps to")
Human-readable relationship name
Response metadata
Unique request identifier
ISO 8601 timestamp
Vocabulary release version used
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/201826/relationships/options" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
concept_id = 201826 # Type 2 diabetes
url = f"https://api.omophub.com/v1/concepts/{concept_id}/relationships/options"
headers = {
"Authorization": "Bearer YOUR_API_KEY"
}
response = requests.get(url, headers=headers)
data = response.json()
print(f"Concept ID: {data['data']['concept_id']}")
print("Available relationship types:")
for rel in data['data']['available_relationships']:
print(f" - {rel['name']} ({rel['id']})")
```
```javascript JavaScript theme={null}
const conceptId = 201826; // Type 2 diabetes
const response = await fetch(`https://api.omophub.com/v1/concepts/${conceptId}/relationships/options`, {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const result = await response.json();
const data = result.data;
console.log(`Concept ID: ${data.concept_id}`);
console.log('Available relationships:');
data.available_relationships.forEach(rel => {
console.log(` - ${rel.name} (${rel.id})`);
});
```
```json theme={null}
{
"success": true,
"data": {
"concept_id": 201826,
"available_relationships": [
{
"id": "Is a",
"name": "Is a"
},
{
"id": "Maps to",
"name": "Maps to"
},
{
"id": "Mapped from",
"name": "Mapped from"
},
{
"id": "Subsumes",
"name": "Subsumes"
}
]
},
"meta": {
"request_id": "req_relationship_options_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.1"
}
}
```
## Usage Examples
### Basic Relationship Options
Get all available relationship types for a concept:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/201826/relationships/options" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### With Specific Vocabulary Version
Get relationship options for a specific vocabulary release:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/201826/relationships/options?vocab_release=2025.1" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### UI Dropdown Integration
Use in a dropdown component:
```javascript theme={null}
async function loadRelationshipOptions(conceptId) {
const response = await fetch(`/v1/concepts/${conceptId}/relationships/options`, {
headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
});
const result = await response.json();
return result.data.available_relationships.map(rel => ({
value: rel.id,
label: rel.name
}));
}
// Use in UI
const options = await loadRelationshipOptions(201826);
const selectHtml = options.map(opt =>
``
).join('');
```
## Important Notes
* **Dynamic options** - Available relationship types vary by concept
* **Both directions** - Returns relationships where the concept is either source or target
* **Valid only** - Only returns relationships with valid (non-deprecated) status
## Related Endpoints
* [Get Concept Relationships](/api-reference/concepts/get-concept-relationships) - Get actual relationships for a concept
* [Get Relationship Types](/api-reference/relationships/get-relationship-types) - Get all relationship types in the system
# Recommended Concepts
Source: https://docs.omophub.com/api-reference/concepts/recommended-concepts
POST /concepts/recommended
Get concept recommendations using the OHDSI Phoebe algorithm
## Overview
The Recommended Concepts endpoint implements the OHDSI Phoebe algorithm to provide curated concept recommendations. This feature helps researchers discover related concepts during cohort building and phenotype development.
**Key Features:**
* Curated recommendations from OHDSI community data
* Relationship-based concept discovery
* Support for multiple source concepts
* Filtering by vocabulary, domain, and relationship type
**Use Cases:**
* Cohort building assistance
* Phenotype expansion
* Related concept discovery
* Clinical terminology exploration
## Request Body
Array of source concept IDs to get recommendations for (min: 1, max: 100)
Filter recommendations by relationship types (max: 20). Examples: "Has finding", "Associated finding", "Has clinical course"
Filter recommended concepts to specific vocabularies (max: 50). Examples: \["SNOMED", "ICD10CM", "LOINC"]
Filter recommended concepts to specific domains (max: 50). Examples: \["Condition", "Procedure", "Drug"]
Only return standard concepts (S flag in OMOP CDM)
Include invalid/deprecated concepts in results
Page number for pagination (min: 1)
Number of recommendations to return per page (min: 1, max: 1000)
```bash cURL theme={null}
curl -X POST "https://api.omophub.com/v1/concepts/recommended" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"concept_ids": [201826, 4329847],
"standard_only": true,
"page": 1,
"page_size": 20
}'
```
```python Python theme={null}
import requests
payload = {
"concept_ids": [201826, 4329847], # Type 2 diabetes, Myocardial infarction
"relationship_types": ["Has finding", "Associated finding"],
"vocabulary_ids": ["SNOMED"],
"standard_only": True,
"page": 1,
"page_size": 20
}
response = requests.post(
"https://api.omophub.com/v1/concepts/recommended",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json=payload
)
data = response.json()
for source_id, recommendations in data['data'].items():
print(f"\nRecommendations for concept {source_id}:")
for rec in recommendations[:5]:
print(f" - {rec['concept_name']} ({rec['vocabulary_id']}) via {rec['relationship_id']}")
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/concepts/recommended', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
concept_ids: [201826, 4329847],
relationship_types: ['Has finding', 'Associated finding'],
vocabulary_ids: ['SNOMED'],
standard_only: true,
page: 1,
page_size: 20
})
});
const data = await response.json();
console.log(`Page ${data.meta.pagination.page} of ${data.meta.pagination.total_pages}`);
console.log(`Total items: ${data.meta.pagination.total_items}`);
```
```bash cURL (with filtering) theme={null}
curl -X POST "https://api.omophub.com/v1/concepts/recommended" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"concept_ids": [201826],
"vocabulary_ids": ["SNOMED", "ICD10CM"],
"domain_ids": ["Condition", "Measurement"],
"relationship_types": ["Has finding"],
"standard_only": true,
"include_invalid": false,
"page": 1,
"page_size": 50
}'
```
```python Python (comprehensive) theme={null}
import requests
# Get comprehensive recommendations for Type 2 diabetes
payload = {
"concept_ids": [201826], # Type 2 diabetes mellitus (SNOMED)
"vocabulary_ids": ["SNOMED", "LOINC"],
"domain_ids": ["Condition", "Measurement", "Procedure"],
"standard_only": True,
"page": 1,
"page_size": 50
}
response = requests.post(
"https://api.omophub.com/v1/concepts/recommended",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json=payload
)
data = response.json()
# Analyze recommendations by domain
from collections import Counter
domains = Counter(rec['domain_id'] for recs in data['data'].values() for rec in recs)
print(f"Recommendations by domain: {dict(domains)}")
# Show top recommendations
for source_id, recommendations in data['data'].items():
print(f"\nTop 10 recommendations for concept {source_id}:")
for i, rec in enumerate(recommendations[:10], 1):
print(f" {i}. {rec['concept_name']}")
print(f" [{rec['vocabulary_id']}] via '{rec['relationship_id']}'")
print(f" Domain: {rec['domain_id']}, Code: {rec['concept_code']}")
```
```json theme={null}
{
"success": true,
"data": {
"201826": [
{
"concept_id": 4193704,
"concept_name": "Hyperglycemia",
"vocabulary_id": "SNOMED",
"concept_code": "80394007",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"invalid_reason": null,
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"relationship_id": "Has finding"
},
{
"concept_id": 4058243,
"concept_name": "Diabetic retinopathy",
"vocabulary_id": "SNOMED",
"concept_code": "4855003",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"invalid_reason": null,
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"relationship_id": "Associated finding"
},
{
"concept_id": 3004410,
"concept_name": "Hemoglobin A1c measurement",
"vocabulary_id": "LOINC",
"concept_code": "4548-4",
"domain_id": "Measurement",
"concept_class_id": "Lab Test",
"standard_concept": "S",
"invalid_reason": null,
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"relationship_id": "Has finding"
}
],
"4329847": [
{
"concept_id": 437894,
"concept_name": "Coronary arteriosclerosis",
"vocabulary_id": "SNOMED",
"concept_code": "53741008",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"invalid_reason": null,
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"relationship_id": "Associated finding"
},
{
"concept_id": 4213540,
"concept_name": "Troponin measurement",
"vocabulary_id": "LOINC",
"concept_code": "6598-7",
"domain_id": "Measurement",
"concept_class_id": "Lab Test",
"standard_concept": "S",
"invalid_reason": null,
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"relationship_id": "Has finding"
}
]
},
"meta": {
"request_id": "rec_1234567890_abc",
"vocab_release": "2025.2",
"pagination": {
"page": 1,
"page_size": 100,
"total_items": 296,
"total_pages": 3,
"has_next": true,
"has_previous": false
}
}
}
```
## Implementation Notes
### Data Source
* Data is curated by the OHDSI community using the Phoebe algorithm
* Version-independent: recommendations persist across vocabulary releases
### Response Structure
* Results are **grouped by source concept ID**
* Each source concept ID maps to an array of recommended concepts
* Recommendations include full concept details and relationship information
### Filtering
* Apply multiple filters simultaneously (vocabulary, domain, relationship type)
* `standard_only=false` (default) includes all concepts; set to `true` to filter to standard concepts only
* `include_invalid=false` (default) excludes deprecated concepts
## Related Endpoints
* [Search Concepts](/api-reference/search/basic-search) - Full-text concept search
* [Get Concept Relationships](/api-reference/concepts/get-concept-relationships) - Direct relationships
* [Get Concept Hierarchy](/api-reference/hierarchy/get-concept-hierarchy) - Hierarchical relationships
* [Batch Concepts](/api-reference/concepts/batch-concepts) - Retrieve multiple concepts
# Suggest Concepts
Source: https://docs.omophub.com/api-reference/concepts/suggest-concepts
GET /concepts/suggest
Get concept suggestions based on partial input
## Overview
Get intelligent concept suggestions for autocomplete functionality, based on partial text input. Uses prefix matching on concept names to provide relevant suggestions.
## Query Parameters
Partial text input for suggestions (minimum 2 characters, maximum 100 characters)
Page number for pagination (1-based)
Number of suggestions per page (max: 100)
Comma-separated list of vocabulary IDs to filter suggestions (e.g., "SNOMED,ICD10CM")
Comma-separated list of domain IDs to filter suggestions (e.g., "Condition,Drug")
Vocabulary release version to query (e.g., "2025.1"). Defaults to latest release.
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/suggest?query=diab&page_size=5" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```bash cURL with filters theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/suggest?query=diab&vocabulary_ids=SNOMED&domain_ids=Condition&page=1&page_size=10" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
params = {
"query": "diab",
"page": 1,
"page_size": 5,
"vocabulary_ids": "SNOMED",
"domain_ids": "Condition"
}
response = requests.get(
"https://api.omophub.com/v1/concepts/suggest",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params=params
)
data = response.json()
```
```javascript JavaScript theme={null}
const params = new URLSearchParams({
query: 'diab',
page: '1',
page_size: '5',
vocabulary_ids: 'SNOMED',
domain_ids: 'Condition'
});
const response = await fetch(`https://api.omophub.com/v1/concepts/suggest?${params}`, {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const data = await response.json();
```
```json theme={null}
{
"success": true,
"data": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31",
"invalid_reason": null
},
{
"concept_id": 4000678,
"concept_name": "Diabetes mellitus",
"concept_code": "73211009",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31",
"invalid_reason": null
},
{
"concept_id": 435216,
"concept_name": "Type 1 diabetes mellitus",
"concept_code": "46635009",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31",
"invalid_reason": null
},
{
"concept_id": 4174977,
"concept_name": "Diabetic retinopathy",
"concept_code": "4855003",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31",
"invalid_reason": null
},
{
"concept_id": 443767,
"concept_name": "Diabetic nephropathy",
"concept_code": "127013003",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31",
"invalid_reason": null
}
],
"meta": {
"request_id": "req_suggest_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2",
"pagination": {
"page": 1,
"page_size": 5,
"total_items": 47,
"total_pages": 10,
"has_next": true,
"has_previous": false
}
}
}
```
# Traverse Relationships
Source: https://docs.omophub.com/api-reference/concepts/traverse-relationships
POST https://api.omophub.com/v1/concepts/relationships/traverse
Traverse relationship networks to discover connected concepts
## Overview
This endpoint allows you to traverse relationship networks starting from one or more concepts, following chains of relationships to discover connected concepts. Uses a recursive graph traversal algorithm with cycle detection.
## Request Body
Array of starting concept IDs for traversal (1-50 concepts)
Optional parameters for controlling the traversal
Maximum traversal depth (1-5)
Array of relationship types to follow (default: \["Is a", "Subsumes", "Mapped from"])
Limit traversal to specific vocabularies
Include invalid/deprecated concepts in traversal
Include detailed path information in response
Use breadth-first traversal (default is depth-first)
Minimum relevance score filter (0.0-1.0)
Maximum number of results to return
## Query Parameters
Specific vocabulary release version (e.g., "2025.1")
## Response
Indicates whether the request was successful
Response data containing traversal results
Array of concept IDs that were used as starting points
Array of concepts discovered during traversal
Discovered concept IDDiscovered concept nameConcept vocabularyDistance from starting conceptArray of concept names in the pathArray of relationship types used
Detailed path information (when include\_paths=true)
Source concept IDTarget concept IDPath detailsPath distance
Summary statistics for the traversal
Total concepts discoveredMaximum distance reachedRelationship types traversed
Response metadata
Unique request identifierISO 8601 timestampVocabulary release version used
```bash cURL theme={null}
curl -X POST "https://api.omophub.com/v1/concepts/relationships/traverse" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"concept_ids": [201826],
"traversal_params": {
"relationship_types": ["Is a", "Maps to"],
"max_depth": 3
}
}'
```
```python Python theme={null}
import requests
def traverse_relationships(concept_ids, traversal_params=None):
url = "https://api.omophub.com/v1/concepts/relationships/traverse"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
data = {"concept_ids": concept_ids}
if traversal_params:
data["traversal_params"] = traversal_params
response = requests.post(url, json=data, headers=headers)
return response.json()
# Example: Traverse from Type 2 diabetes
result = traverse_relationships(
concept_ids=[201826],
traversal_params={
"relationship_types": ["Is a", "Subsumes", "Maps to"],
"max_depth": 3,
"include_paths": True
}
)
data = result['data']
print(f"Starting concepts: {data['starting_concepts']}")
print(f"Discovered {data['traversal_summary']['total_discovered']} concepts")
print(f"Max distance: {data['traversal_summary']['max_distance_reached']}")
for concept in data['discovered_concepts'][:5]:
print(f" - {concept['concept_name']} (distance: {concept['distance']})")
```
```javascript JavaScript theme={null}
const traverseRelationships = async (conceptIds, traversalParams = {}) => {
const response = await fetch('https://api.omophub.com/v1/concepts/relationships/traverse', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
concept_ids: conceptIds,
traversal_params: traversalParams
})
});
return response.json();
};
// Example usage
const result = await traverseRelationships([201826], {
relationship_types: ['Is a', 'Maps to'],
max_depth: 3
});
const data = result.data;
console.log(`Discovered ${data.traversal_summary.total_discovered} concepts`);
data.discovered_concepts.slice(0, 5).forEach(concept => {
console.log(`- ${concept.concept_name} (distance: ${concept.distance})`);
});
```
```json theme={null}
{
"success": true,
"data": {
"starting_concepts": [201826],
"discovered_concepts": [
{
"concept_id": 73211009,
"concept_name": "Diabetes mellitus",
"vocabulary_id": "SNOMED",
"distance": 1,
"path": ["Type 2 diabetes mellitus", "Diabetes mellitus"],
"relationship_path": ["Is a"]
},
{
"concept_id": 443767,
"concept_name": "Type 2 diabetes mellitus with complications",
"vocabulary_id": "SNOMED",
"distance": 1,
"path": ["Type 2 diabetes mellitus", "Type 2 diabetes mellitus with complications"],
"relationship_path": ["Subsumes"]
},
{
"concept_id": 435216,
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "ICD10CM",
"distance": 1,
"path": ["Type 2 diabetes mellitus", "Type 2 diabetes mellitus"],
"relationship_path": ["Maps to"]
}
],
"relationship_paths": [],
"traversal_summary": {
"total_discovered": 25,
"max_distance_reached": 3,
"relationship_types_used": ["Is a", "Subsumes", "Maps to"]
}
},
"meta": {
"request_id": "req_traverse_123",
"timestamp": "2025-01-15T10:00:00Z",
"vocab_release": "2025.1"
}
}
```
## Usage Examples
### Basic Traversal
Simple traversal with default settings:
```json theme={null}
{
"concept_ids": [201826],
"traversal_params": {
"max_depth": 2
}
}
```
### With Path Details
Include detailed path information:
```json theme={null}
{
"concept_ids": [201826],
"traversal_params": {
"relationship_types": ["Is a", "Maps to"],
"max_depth": 3,
"include_paths": true
}
}
```
### Multiple Starting Concepts
Traverse from multiple concepts:
```json theme={null}
{
"concept_ids": [201826, 4182210, 313217],
"traversal_params": {
"max_depth": 2,
"page_size": 50
}
}
```
### Vocabulary-Filtered Traversal
Limit to specific vocabularies:
```json theme={null}
{
"concept_ids": [201826],
"traversal_params": {
"vocabulary_ids": ["SNOMED", "ICD10CM"],
"max_depth": 3
}
}
```
## Important Notes
* **Concept limit**: Maximum 50 starting concepts per request
* **Depth limit**: Maximum traversal depth is 5 to prevent performance issues
* **Cycle detection**: The algorithm prevents infinite loops by tracking visited concepts
* **Performance**: Deep traversals with many starting concepts may take longer
* **Default relationships**: If not specified, uses \["Is a", "Subsumes", "Mapped from"]
## Related Endpoints
* [Batch Hierarchy Queries](/api-reference/concepts/batch-hierarchy-queries) - Batch hierarchy queries
* [Get Concept Relationships](/api-reference/concepts/get-concept-relationships) - Single concept relationships
* [Get Concept Hierarchy](/api-reference/concepts/get-concept-hierarchy) - Hierarchical relationships only
# Get Concept Classes
Source: https://docs.omophub.com/api-reference/domains/get-concept-classes
GET /v1/concept-classes
Retrieve all available concept classes that categorize medical concepts
This endpoint returns concept classes, which provide categorization of medical concepts within their domains. Concept classes distinguish between different types of concepts like "Clinical Finding" vs "Disorder" within conditions, or "Ingredient" vs "Clinical Drug" within drugs.
## Query Parameters
Filter concept classes to those used by specific vocabularies. Comma-separated list.
**Example:** `SNOMED,RxNorm,ICD10CM`
Include concept counts and vocabulary coverage for each concept class.
Specific vocabulary release version to query
**Example:** `2025.1`
## Response
Indicates if the request was successful.
Response data container.
Array of concept class objects.
Unique identifier for the concept class.
Human-readable name of the concept class.
OMOP concept ID representing this concept class.
Number of concepts in this concept class (when `include_stats=true`).
List of vocabulary IDs that have concepts in this class (when `include_stats=true`).
Response metadata.
Unique identifier for this request.
Vocabulary release version used.
Response timestamp (ISO format).
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/concept-classes?include_stats=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.omophub.com/v1/concept-classes?include_stats=true',
{
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
}
);
const data = await response.json();
console.log(`Found ${data.data.concept_classes.length} concept classes`);
// Display concept classes with counts
data.data.concept_classes.forEach(cc => {
console.log(`${cc.concept_class_name}: ${cc.concept_count || 'N/A'} concepts`);
});
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/concept-classes",
params={"include_stats": True, "vocabulary_ids": "SNOMED,RxNorm"},
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
for cc in data["data"]["concept_classes"]:
print(f"{cc['concept_class_name']}: {cc.get('concept_count', 'N/A')} concepts")
```
```json theme={null}
{
"success": true,
"data": {
"concept_classes": [
{
"concept_class_id": "Clinical Finding",
"concept_class_name": "Clinical Finding",
"concept_class_concept_id": 441840,
"concept_count": 425891,
"vocabulary_coverage": ["SNOMED", "Read"]
},
{
"concept_class_id": "Ingredient",
"concept_class_name": "Ingredient",
"concept_class_concept_id": 441828,
"concept_count": 45678,
"vocabulary_coverage": ["RxNorm", "SNOMED"]
},
{
"concept_class_id": "Procedure",
"concept_class_name": "Procedure",
"concept_class_concept_id": 441838,
"concept_count": 198765,
"vocabulary_coverage": ["SNOMED", "HCPCS", "CPT4"]
}
]
},
"meta": {
"request_id": "req_abc123",
"vocab_release": "2025.1",
"timestamp": "2025-01-05T10:00:00Z"
}
}
```
## Usage Examples
### All Concept Classes
Get all concept classes without statistics:
```bash theme={null}
GET /v1/concept-classes
```
### With Statistics
Get concept classes with concept counts and vocabulary coverage:
```bash theme={null}
GET /v1/concept-classes?include_stats=true
```
### Filter by Vocabulary
Get concept classes used by specific vocabularies:
```bash theme={null}
GET /v1/concept-classes?vocabulary_ids=SNOMED,RxNorm&include_stats=true
```
## Related Endpoints
* [Get Domains](/api-reference/domains/get-domains) - Available domain information
* [Get Domain Concepts](/api-reference/domains/get-domain-concepts) - Concepts within a domain
## Notes
* Concept classes provide finer categorization than domains
* Many concept classes are vocabulary-specific (e.g., "3-char billing code" for ICD codes)
* When filtering by vocabulary, only concept classes that contain concepts from those vocabularies are returned
* The `vocabulary_coverage` field shows which vocabularies contribute concepts to each class
# Get Domain Concepts
Source: https://docs.omophub.com/api-reference/domains/get-domain-concepts
GET /v1/domains/{domain_id}/concepts
Retrieve all concepts within a specific domain
This endpoint provides access to all medical concepts within a particular domain, such as all conditions, drugs, or procedures.
## Path Parameters
The domain identifier to retrieve concepts from.
**Example:** `Condition`, `Drug`, `Procedure`
## Query Parameters
Filter concepts to specific vocabularies within the domain (comma-separated).
**Example:** `SNOMED,ICD10CM`
Return only standard concepts (standard\_concept = 'S').
Include invalid/deprecated concepts in results.
Page number for pagination (1-based).
Number of concepts to return per page (max 1000).
Specific vocabulary release version to query
**Example:** `2025.1`
## Response
Indicates if the request was successful.
Response data container.
Array of concept objects within the domain.
Unique concept identifier.
Standard name of the concept.
Original code from the vocabulary.
Vocabulary containing this concept.
Human-readable vocabulary name.
Domain identifier.
Concept class identifier.
Standard concept designation ('S', 'C', or null).
Date when concept became valid (ISO format).
Date when concept becomes invalid (ISO format).
Reason for concept invalidation if applicable.
Response metadata and pagination information.
Unique identifier for this request.
Vocabulary release version used.
Response timestamp (ISO format).
Current page number.Items per page.Total concepts matching filters.Total number of pages.Whether next page exists.Whether previous page exists.
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/domains/Condition/concepts?vocabulary_ids=SNOMED&standard_only=true&page_size=50" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.omophub.com/v1/domains/Condition/concepts?vocabulary_ids=SNOMED&standard_only=true&page_size=50',
{
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
}
);
const data = await response.json();
console.log(`Found ${data.meta.pagination.total_items} concepts`);
data.data.concepts.forEach(concept => {
console.log(`${concept.concept_name} (${concept.concept_id})`);
});
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/domains/Drug/concepts",
params={
"vocabulary_ids": "RxNorm",
"standard_only": True,
"page_size": 100
},
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
for concept in data["data"]["concepts"]:
print(f"{concept['concept_name']} ({concept['concept_id']})")
```
```json theme={null}
{
"success": true,
"data": {
"concepts": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null
},
{
"concept_id": 46635009,
"concept_name": "Type 1 diabetes mellitus",
"concept_code": "46635009",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null
}
]
},
"meta": {
"request_id": "req_abc123",
"vocab_release": "2025.1",
"timestamp": "2025-01-05T10:00:00Z",
"pagination": {
"page": 1,
"page_size": 50,
"total_items": 847,
"total_pages": 17,
"has_next": true,
"has_previous": false
}
}
}
```
## Usage Examples
### All Concepts in a Domain
Get all concepts within the Condition domain:
```bash theme={null}
GET /v1/domains/Condition/concepts?page_size=100
```
### Standard Concepts Only
Get only standard concepts from a specific vocabulary:
```bash theme={null}
GET /v1/domains/Drug/concepts?standard_only=true&vocabulary_ids=RxNorm
```
### Filter by Multiple Vocabularies
Get concepts from multiple vocabularies:
```bash theme={null}
GET /v1/domains/Procedure/concepts?vocabulary_ids=SNOMED,CPT4,HCPCS
```
### Include Invalid Concepts
Include deprecated concepts in results:
```bash theme={null}
GET /v1/domains/Condition/concepts?include_invalid=true
```
## Related Endpoints
* [Get Domains](/api-reference/domains/get-domains) - Available domain list
* [Get Concept Details](/api-reference/concepts/get-concept-details) - Individual concept information
* [Search Concepts](/api-reference/search/basic-search) - Cross-domain concept search
## Notes
* Domain queries can return large result sets - use pagination appropriately
* Standard concepts (S) are preferred for most clinical applications
* Default page size is 20, maximum is 1000
* Results are not sorted by default
# Get Domain Statistics
Source: https://docs.omophub.com/api-reference/domains/get-domain-statistics
GET /v1/domains/{domain_id}/statistics
Get statistical overview for a specific OMOP domain
This endpoint provides statistical information about a domain, including concept counts, vocabulary distribution, and concept class breakdown.
## Path Parameters
The domain identifier to get statistics for.
**Example:** `Condition`, `Drug`, `Procedure`
## Query Parameters
Specific vocabulary release version to query
**Example:** `2025.1`
## Response
Domain identifier.
Statistical overview of the domain.
Domain identifier.
Human-readable domain name.
Total number of concepts in the domain.
Number of standard concepts (standard\_concept = 'S').
Number of non-standard concepts.
Number of invalid/deprecated concepts.
Distribution of concepts by vocabulary.
Vocabulary identifier.
Human-readable vocabulary name.
Number of concepts from this vocabulary.
Distribution of concepts by concept class.
Concept class identifier.
Human-readable concept class name.
Number of concepts in this class.
Date range of concept validity dates.
Earliest valid\_start\_date (ISO format).
Latest valid\_start\_date (ISO format).
Response metadata.
Unique identifier for this request.
Vocabulary release version used.
Response timestamp (ISO format).
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/domains/Condition/statistics" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.omophub.com/v1/domains/Condition/statistics',
{
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
}
);
const data = await response.json();
console.log(`Total concepts: ${data.data.statistics.total_concepts}`);
console.log(`Standard: ${data.data.statistics.standard_concepts}`);
// Show vocabulary distribution
data.data.statistics.vocabulary_breakdown.forEach(v => {
console.log(`${v.vocabulary_id}: ${v.concept_count} concepts`);
});
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/domains/Drug/statistics",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
stats = data["data"]["statistics"]
print(f"Total: {stats['total_concepts']}")
print(f"Standard: {stats['standard_concepts']}")
print(f"Non-standard: {stats['non_standard_concepts']}")
# Vocabulary breakdown
for v in stats["vocabulary_breakdown"]:
print(f" {v['vocabulary_id']}: {v['concept_count']}")
```
```json theme={null}
{
"success": true,
"data": {
"domain_id": "Condition",
"statistics": {
"domain_id": "Condition",
"domain_name": "Condition",
"total_concepts": 845672,
"standard_concepts": 423891,
"non_standard_concepts": 421781,
"invalid_concepts": 12453,
"vocabulary_breakdown": [
{
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"concept_count": 387421
},
{
"vocabulary_id": "ICD10CM",
"vocabulary_name": "International Classification of Diseases, Tenth Revision, Clinical Modification",
"concept_count": 98234
},
{
"vocabulary_id": "ICD10",
"vocabulary_name": "International Classification of Diseases, Tenth Revision",
"concept_count": 87456
}
],
"concept_class_breakdown": [
{
"concept_class_id": "Clinical Finding",
"concept_class_name": "Clinical Finding",
"concept_count": 312456
},
{
"concept_class_id": "Disorder",
"concept_class_name": "Disorder",
"concept_count": 156789
}
],
"creation_date_range": {
"earliest": "1970-01-01",
"latest": "2024-12-01"
}
}
},
"meta": {
"request_id": "req_abc123",
"vocab_release": "2025.1",
"timestamp": "2025-01-05T10:00:00Z"
}
}
```
## Usage Examples
### Get Condition Domain Statistics
```bash theme={null}
GET /v1/domains/Condition/statistics
```
### Get Drug Domain Statistics
```bash theme={null}
GET /v1/domains/Drug/statistics
```
### Get Procedure Domain Statistics
```bash theme={null}
GET /v1/domains/Procedure/statistics
```
## Related Endpoints
* [Get Domains](/api-reference/domains/get-domains) - List all available domains
* [Get Domain Concepts](/api-reference/domains/get-domain-concepts) - Get concepts within a domain
## Notes
* Statistics are calculated from the current vocabulary release
* The vocabulary\_breakdown shows which source vocabularies contribute concepts to the domain
* The concept\_class\_breakdown shows the distribution of concept types within the domain
* Standard concepts are preferred for clinical applications
# Get Domains
Source: https://docs.omophub.com/api-reference/domains/get-domains
GET /v1/domains
Retrieve all available domains that categorize medical concepts into high-level semantic groups
This endpoint provides information about all domains available in the OMOP vocabulary system. Domains represent high-level categorizations that group medical concepts by their semantic meaning, such as Condition, Drug, Procedure, and others.
## Query Parameters
Include concept counts and vocabulary coverage for each domain
Specific vocabulary release version to query
**Example:** `2025.1`
## Response
Array of domain objects with their properties
Unique identifier for the domain (e.g., "Condition", "Drug", "Procedure")
Human-readable name of the domain
OMOP concept ID representing this domain
Total number of concepts in this domain (when `include_stats=true`)
Number of standard concepts in this domain (when `include_stats=true`)
List of vocabularies that contribute concepts to this domain (when `include_stats=true`)
Response metadata
Unique identifier for this request
Vocabulary release version used for this response
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/domains?include_stats=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/domains?include_stats=true', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const data = await response.json();
console.log(`Found ${data.data.domains.length} domains`);
// Display domains with concept counts
data.data.domains.forEach(domain => {
const count = domain.concept_count || 'N/A';
console.log(`${domain.domain_name}: ${count} concepts`);
});
```
```python Python theme={null}
import requests
url = "https://api.omophub.com/v1/domains"
params = {"include_stats": "true"}
headers = {"Authorization": "Bearer YOUR_API_KEY"}
response = requests.get(url, params=params, headers=headers)
data = response.json()
print(f"Found {len(data['data']['domains'])} domains")
# Display domains sorted by concept count
domains = data['data']['domains']
for domain in sorted(domains, key=lambda d: d.get('concept_count', 0), reverse=True):
count = domain.get('concept_count', 0)
standard = domain.get('standard_concept_count', 0)
print(f"{domain['domain_name']}: {count:,} total, {standard:,} standard")
```
```r R theme={null}
library(omophub)
client <- omophub_client()
domains <- client$domains$list(include_stats = TRUE)
# Display domains
for (domain in domains$domains) {
cat(sprintf("%s: %d concepts\n",
domain$domain_name,
domain$concept_count %||% 0))
}
```
```json Basic Response theme={null}
{
"success": true,
"data": {
"domains": [
{
"domain_id": "Condition",
"domain_name": "Condition",
"domain_concept_id": 19
},
{
"domain_id": "Drug",
"domain_name": "Drug",
"domain_concept_id": 13
},
{
"domain_id": "Procedure",
"domain_name": "Procedure",
"domain_concept_id": 10
},
{
"domain_id": "Measurement",
"domain_name": "Measurement",
"domain_concept_id": 21
},
{
"domain_id": "Observation",
"domain_name": "Observation",
"domain_concept_id": 27
}
]
},
"meta": {
"request_id": "req_abc123",
"vocab_release": "2025.1"
}
}
```
```json With Statistics (include_stats=true) theme={null}
{
"success": true,
"data": {
"domains": [
{
"domain_id": "Condition",
"domain_name": "Condition",
"domain_concept_id": 19,
"concept_count": 845672,
"standard_concept_count": 423891,
"vocabulary_coverage": ["SNOMED", "ICD10CM", "ICD10", "ICD9CM", "Read"]
},
{
"domain_id": "Drug",
"domain_name": "Drug",
"domain_concept_id": 13,
"concept_count": 652341,
"standard_concept_count": 198765,
"vocabulary_coverage": ["RxNorm", "RxNorm Extension", "NDC", "SNOMED"]
},
{
"domain_id": "Procedure",
"domain_name": "Procedure",
"domain_concept_id": 10,
"concept_count": 456789,
"standard_concept_count": 234561,
"vocabulary_coverage": ["SNOMED", "ICD10PCS", "HCPCS", "CPT4"]
},
{
"domain_id": "Measurement",
"domain_name": "Measurement",
"domain_concept_id": 21,
"concept_count": 234567,
"standard_concept_count": 156789,
"vocabulary_coverage": ["LOINC", "SNOMED"]
},
{
"domain_id": "Observation",
"domain_name": "Observation",
"domain_concept_id": 27,
"concept_count": 123456,
"standard_concept_count": 87654,
"vocabulary_coverage": ["SNOMED", "LOINC"]
}
]
},
"meta": {
"request_id": "req_xyz789",
"vocab_release": "2025.1"
}
}
```
## Usage Examples
### Basic Domain List
Get all available domains:
```javascript theme={null}
const response = await fetch('/v1/domains', {
headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
});
const { data } = await response.json();
console.log(data.domains);
```
### Domains with Statistics
Get domains with concept counts and vocabulary coverage:
```javascript theme={null}
const response = await fetch('/v1/domains?include_stats=true', {
headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
});
const { data } = await response.json();
// Find the domain with the most concepts
const largestDomain = data.domains.reduce((max, domain) =>
(domain.concept_count > (max.concept_count || 0)) ? domain : max
, {});
console.log(`Largest domain: ${largestDomain.domain_name} with ${largestDomain.concept_count} concepts`);
```
### Filter by Vocabulary Coverage
Find domains that include concepts from a specific vocabulary:
```javascript theme={null}
const response = await fetch('/v1/domains?include_stats=true', {
headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
});
const { data } = await response.json();
// Find domains with SNOMED concepts
const snomedDomains = data.domains.filter(domain =>
domain.vocabulary_coverage?.includes('SNOMED')
);
console.log('Domains with SNOMED concepts:', snomedDomains.map(d => d.domain_name));
```
## Related Endpoints
* [Get Domain Concepts](/api-reference/domains/get-domain-concepts) - Concepts within a specific domain
* [Get Concept Classes](/api-reference/domains/get-concept-classes) - Concept class classifications
* [Search Concepts](/api-reference/search/basic-search) - Search within specific domains
* [Get Vocabularies](/api-reference/vocabulary/get-vocabularies) - Available vocabularies
## Notes
* Domains provide the highest level of semantic categorization in OMOP
* The most commonly used clinical domains are Condition, Drug, Procedure, and Measurement
* Use `include_stats=true` to get concept counts for capacity planning and analysis
* The `vocabulary_coverage` field shows which vocabularies contribute concepts to each domain
# Errors
Source: https://docs.omophub.com/api-reference/errors
Troubleshoot problems with this comprehensive breakdown of all error codes.
# Error Handling
OMOPHub API uses conventional HTTP response codes and provides detailed error information.
## HTTP Status Codes
### Success Codes
* `200 OK` - Request successful
* `201 Created` - Resource created successfully
* `204 No Content` - Request successful, no content to return
### Client Error Codes (4xx)
* `400 Bad Request` - Invalid request parameters
* `401 Unauthorized` - Authentication required
* `403 Forbidden` - Insufficient permissions
* `404 Not Found` - Resource not found
* `413 Payload Too Large` - Request body exceeds size limits
* `422 Unprocessable Entity` - Valid request format but semantic errors
* `429 Too Many Requests` - Rate limit exceeded
### Server Error Codes (5xx)
* `500 Internal Server Error` - Unexpected server error
* `503 Service Unavailable` - Service temporarily unavailable
## Common Error Codes
### Authentication & Authorization
* `missing_api_key` (401) - No API key provided in Authorization header
* `invalid_api_key` (403) - API key is invalid, expired, or revoked
* `unauthorized` (401) - Authentication token is invalid
* `forbidden` (403) - Insufficient permissions for this resource
### Request Validation
* `validation_error` (422) - Well-formed request but semantically invalid parameters
* `missing_required_field` (400) - Required fields missing or malformed in the request
* `bad_request` (400) - General malformed request (bad JSON/format)
### Resource Management
* `not_found` (404) - Requested resource does not exist
* `concept_not_found` (404) - Specific concept ID not found
* `vocabulary_not_found` (404) - Vocabulary ID not found
### Rate Limits & Quotas
* `rate_limit_exceeded` (429) - Too many requests per second
* `daily_quota_exceeded` (429) - Daily request limit reached
### Healthcare & Security
* `healthcare_data_error` (422) - Medical data validation failure
* `invalid_mapping` (400) - Concept mapping validation error
### System & Service Errors
* `internal_server_error` (500) - Unexpected server error
* `service_unavailable` (503) - API temporarily unavailable
### Version & Compatibility
* `version_not_supported` (400) - Requested API version not supported
* `search_error` (400) - Search operation failed
# Get Concept Ancestors
Source: https://docs.omophub.com/api-reference/hierarchy/get-concept-ancestors
GET /v1/concepts/{concept_id}/ancestors
Retrieve all ancestor concepts for a given concept, providing hierarchical context and classification paths
This endpoint returns the complete ancestor hierarchy for a specific concept, including parent concepts, grandparents, and all higher-level classifications up to the root of the vocabulary hierarchy.
## Path Parameters
The unique identifier of the concept to retrieve ancestors for
**Example:** `201826` (Type 2 diabetes mellitus)
## Query Parameters
Filter ancestors to specific vocabularies (comma-separated)
**Example:** `SNOMED,ICD10CM`
Filter ancestors to specific domains (comma-separated)
**Example:** `Condition,Observation`
Maximum number of hierarchy levels to traverse
**Range:** `1-20`
Relationship types to follow for hierarchy traversal. Spaces must be percent-encoded when used in query strings (e.g., "Is%20a,Part%20of").
**Default:** `Is a`
**Allowed values:** `Is a`, `Part of`, `Has part`, `Subsumes`, `Has ingredient`, `RxNorm has dose form`
**Example:** `Is%20a,Part%20of` (URL-encoded)
Include `hierarchy_level` field for each ancestor (distance from source concept)
Include `path_length` field for each ancestor
Include deprecated/invalid concepts in ancestry (default excludes them)
Page number for pagination (1-based)
Number of ancestor concepts to return per page (max 1000)
Specific vocabulary release version to query
**Example:** `2025.1`
## Response
The concept ID for which ancestors were retrieved
Standard name of the source concept
Vocabulary containing the source concept
Array of ancestor concepts in hierarchical order
Unique identifier for the ancestor concept
Standard name of the ancestor concept
Original code from the vocabulary
Vocabulary containing this ancestor concept
Human-readable vocabulary name
Domain classification of the ancestor
Concept class identifier
Standard concept designation ('S', 'C', or null)
Distance from source concept (always present)
Minimum levels of separation from source concept
Maximum levels of separation from source concept
Relationship type ID (e.g., "Is a")
Relationship type name (e.g., "Is a")
Distance from source concept (only when include\_distance=true, same as level)
Path length from source concept (only when include\_paths=true, same as level)
Date when concept became valid (ISO format)
Date when concept became invalid (ISO format)
Reason for concept invalidation if applicable
Summary statistics about the ancestor hierarchy
Total number of ancestor concepts found
Maximum depth of the hierarchy tree
List of vocabulary IDs represented in ancestors
List of relationship type IDs found in hierarchy
Response metadata and pagination information
Current page numberItems per pageTotal ancestor conceptsTotal number of pagesWhether next page existsWhether previous page exists
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/201826/ancestors?include_paths=true&include_distance=true&max_levels=5" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/concepts/201826/ancestors?include_paths=true&include_distance=true&max_levels=5', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const ancestorData = await response.json();
console.log(`Found ${ancestorData.data.hierarchy_summary.total_ancestors} ancestors`);
console.log('Classification paths:', ancestorData.data.ancestors[0].classification_paths);
```
```python Python theme={null}
import requests
concept_id = 201826 # Type 2 diabetes mellitus
url = f"https://api.omophub.com/v1/concepts/{concept_id}/ancestors"
params = {
"include_paths": True,
"include_distance": True,
"max_levels": 5
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.get(url, params=params, headers=headers)
ancestor_data = response.json()
print(f"Concept: {ancestor_data['data']['concept_name']}")
print(f"Total ancestors: {ancestor_data['data']['hierarchy_summary']['total_ancestors']}")
for ancestor in ancestor_data['data']['ancestors'][:5]:
level = ancestor.get('level', 'Unknown')
print(f"Level {level}: {ancestor['concept_name']} ({ancestor['concept_id']})")
```
```json theme={null}
{
"success": true,
"data": {
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "SNOMED",
"ancestors": [
{
"concept_id": 73211009,
"concept_name": "Diabetes mellitus",
"concept_code": "73211009",
"vocabulary_id": "SNOMED",
"vocabulary_name": "SNOMED Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 1,
"min_levels_of_separation": 1,
"max_levels_of_separation": 1,
"relationship_id": "Is a",
"relationship_name": "Is a",
"hierarchy_level": 1,
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null
},
{
"concept_id": 362969004,
"concept_name": "Disorder of endocrine system",
"concept_code": "362969004",
"vocabulary_id": "SNOMED",
"vocabulary_name": "SNOMED Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 2,
"min_levels_of_separation": 2,
"max_levels_of_separation": 2,
"relationship_id": "Is a",
"relationship_name": "Is a",
"hierarchy_level": 2,
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31",
"invalid_reason": null
},
{
"concept_id": 64572001,
"concept_name": "Disease",
"concept_code": "64572001",
"vocabulary_id": "SNOMED",
"vocabulary_name": "SNOMED Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 3,
"min_levels_of_separation": 3,
"max_levels_of_separation": 3,
"relationship_id": "Is a",
"relationship_name": "Is a",
"hierarchy_level": 3,
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null
}
],
"hierarchy_summary": {
"total_ancestors": 3,
"max_hierarchy_depth": 3,
"unique_vocabularies": ["SNOMED"],
"relationship_types_used": ["Is a"]
}
},
"meta": {
"pagination": {
"page": 1,
"page_size": 100,
"total_items": 3,
"total_pages": 1,
"has_next": false,
"has_previous": false
},
"request_id": "req_ancestors_201826_20250104_103100",
"timestamp": "2025-01-04T10:31:00Z",
"vocab_release": "2025.1"
}
}
```
## Usage Examples
### Basic Ancestor Retrieval
Get all ancestors for a specific concept:
```javascript theme={null}
const ancestors = await fetch('/v1/concepts/201826/ancestors');
```
### Limited Hierarchy Depth
Retrieve ancestors up to a specific number of levels:
```javascript theme={null}
const nearAncestors = await fetch('/v1/concepts/201826/ancestors?max_levels=3');
```
### Classification Path Analysis
Get complete classification paths from concept to root:
```javascript theme={null}
const pathData = await fetch('/v1/concepts/201826/ancestors?include_paths=true&include_distance=true');
```
### Cross-Vocabulary Hierarchy
Analyze ancestors within specific vocabulary:
```javascript theme={null}
const snomedAncestors = await fetch('/v1/concepts/201826/ancestors?vocabulary_id=SNOMED');
```
### Multiple Relationship Types
Follow different types of hierarchical relationships:
```javascript theme={null}
const extendedHierarchy = await fetch('/v1/concepts/201826/ancestors?relationship_types=Is%20a,Part%20of');
```
## Related Endpoints
* [Get Concept Descendants](/api-reference/hierarchy/get-concept-descendants) - Retrieve child concepts
* [Get Concept Hierarchy](/api-reference/hierarchy/get-concept-hierarchy) - Complete hierarchy view
* [Get Concept Relationships](/api-reference/relationships/get-concept-relationships) - All concept relationships
* [Get Concept Details](/api-reference/concepts/get-concept-details) - Complete concept information
## Notes
* Hierarchy traversal follows "Is a" relationships by default, but can be customized
* Some concepts may have multiple classification paths to different root concepts
* Cross-vocabulary concepts may have ancestors in different vocabularies
* Standard concepts are prioritized in hierarchy traversal unless explicitly disabled
* Deprecated concepts are excluded from ancestry unless specifically requested
* Maximum hierarchy depth is typically 6-8 levels for most medical vocabularies
# Get Concept Descendants
Source: https://docs.omophub.com/api-reference/hierarchy/get-concept-descendants
GET /v1/concepts/{concept_id}/descendants
Retrieve all descendant concepts for a given concept, providing hierarchical children and specialized terms
This endpoint returns all descendant concepts (children, grandchildren, etc.) for a specific concept, allowing exploration of more specific terms and sub-classifications within the medical vocabulary hierarchy.
## Path Parameters
The unique identifier of the concept to retrieve descendants for
**Example:** `73211009` (Diabetes mellitus)
## Query Parameters
Filter descendants to specific vocabularies (comma-separated)
**Example:** `SNOMED,ICD10CM`
Filter descendants to specific domains (comma-separated)
**Example:** `Condition,Drug`
Maximum number of hierarchy levels to traverse downward
**Range:** `1-20`
Relationship types to follow for hierarchy traversal. Spaces must be URL-encoded (%20) in URLs.
**Default:** `Is a`
**Allowed values:** `Is a`, `Has part`, `Subsumes`, `Part of`, `Has ingredient`, `RxNorm has dose form`
**Example:** `Is%20a,Has%20part` (URL-encoded)
Include `hierarchy_level` field for each descendant (distance from source concept)
Include `path_length` field for each descendant
Include deprecated/invalid concepts in descendants (default excludes them)
Page number for pagination (1-based)
Number of descendant concepts to return per page (max 1000)
Specific vocabulary release version to query
**Example:** `2025.1`
## Response
The concept ID for which descendants were retrieved
Standard name of the source concept
Vocabulary containing the source concept
Array of descendant concepts in hierarchical order
Unique identifier for the descendant concept
Standard name of the descendant concept
Original code from the vocabulary
Vocabulary containing this descendant concept
Human-readable vocabulary name
Domain classification of the descendant
Concept class identifier
Standard concept designation ('S', 'C', or null)
Distance from source concept (always present)
Minimum levels of separation from source concept
Maximum levels of separation from source concept
Relationship type ID (e.g., "Subsumes")
Relationship type name (e.g., "Subsumes")
Distance from source concept (only when include\_distance=true, same as level)
Path length from source concept (only when include\_paths=true, same as level)
Date when concept became valid (ISO format)
Date when concept became invalid (ISO format)
Reason for concept invalidation if applicable
Summary statistics about the descendant hierarchy
Total number of descendant concepts found
Maximum depth of the descendant tree
List of vocabulary IDs represented in descendants
List of relationship type IDs found in hierarchy
Response metadata and pagination information
Current page numberItems per pageTotal descendant conceptsTotal number of pagesWhether next page existsWhether previous page exists
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/73211009/descendants?include_distance=true&max_levels=3" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
concept_id = 73211009 # Diabetes mellitus
url = f"https://api.omophub.com/v1/concepts/{concept_id}/descendants"
params = {
"include_distance": True,
"max_levels": 3,
"page_size": 50
}
headers = {
"Authorization": "Bearer YOUR_API_KEY"
}
response = requests.get(url, params=params, headers=headers)
descendant_data = response.json()
print(f"Concept: {descendant_data['concept_name']}")
print(f"Total descendants: {descendant_data['hierarchy_summary']['total_descendants']}")
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/concepts/73211009/descendants?include_distance=true&max_levels=3', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const descendantData = await response.json();
console.log(`Found ${descendantData.hierarchy_summary.total_descendants} descendants`);
console.log(`Max depth: ${descendantData.hierarchy_summary.max_hierarchy_depth}`);
```
```bash cURL (filtered by domain) theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/73211009/descendants?domain_ids=Condition&max_levels=2&page_size=25" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (with hierarchy grouping) theme={null}
import requests
from collections import defaultdict
concept_id = 73211009
response = requests.get(
f"https://api.omophub.com/v1/concepts/{concept_id}/descendants",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params={"include_distance": True, "max_levels": 3}
)
data = response.json()
by_level = defaultdict(list)
for desc in data['descendants']:
level = desc.get('hierarchy_level', 0)
by_level[level].append(desc['concept_name'])
for level in sorted(by_level.keys()):
print(f"Level {level}: {len(by_level[level])} concepts")
```
```json theme={null}
{
"success": true,
"data": {
"concept_id": 73211009,
"concept_name": "Diabetes mellitus",
"vocabulary_id": "SNOMED",
"descendants": [
{
"concept_id": 44054006,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"vocabulary_name": "SNOMED Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 1,
"min_levels_of_separation": 1,
"max_levels_of_separation": 1,
"relationship_id": "Subsumes",
"relationship_name": "Subsumes",
"hierarchy_level": 1,
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null
},
{
"concept_id": 46635009,
"concept_name": "Type 1 diabetes mellitus",
"concept_code": "46635009",
"vocabulary_id": "SNOMED",
"vocabulary_name": "SNOMED Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 1,
"min_levels_of_separation": 1,
"max_levels_of_separation": 1,
"relationship_id": "Subsumes",
"relationship_name": "Subsumes",
"hierarchy_level": 1,
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null
},
{
"concept_id": 237599002,
"concept_name": "Insulin dependent diabetes mellitus",
"concept_code": "237599002",
"vocabulary_id": "SNOMED",
"vocabulary_name": "SNOMED Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 2,
"min_levels_of_separation": 2,
"max_levels_of_separation": 2,
"relationship_id": "Subsumes",
"relationship_name": "Subsumes",
"hierarchy_level": 2,
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31",
"invalid_reason": null
}
],
"hierarchy_summary": {
"total_descendants": 847,
"max_hierarchy_depth": 6,
"unique_vocabularies": ["SNOMED"],
"relationship_types_used": ["Subsumes"]
}
},
"meta": {
"pagination": {
"page": 1,
"page_size": 100,
"total_items": 847,
"total_pages": 9,
"has_next": true,
"has_previous": false
},
"request_id": "req_descendants_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
**Note:** Examples assume a preconfigured client with base URL `https://api.omophub.com` and authentication headers. For direct usage:
```javascript theme={null}
const response = await fetch('https://api.omophub.com/v1/concepts/73211009/descendants', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json'
}
});
```
### Basic Descendant Retrieval
Get all descendants for a specific concept:
```javascript theme={null}
const descendants = await fetch('/v1/concepts/73211009/descendants');
```
### Limited Hierarchy Depth
Retrieve descendants up to a specific number of levels:
```javascript theme={null}
const nearDescendants = await fetch('/v1/concepts/73211009/descendants?max_levels=2');
```
### Domain-Specific Descendants
Filter descendants to specific medical domains:
```javascript theme={null}
const conditionDescendants = await fetch('/v1/concepts/73211009/descendants?domain_ids=Condition');
```
### Detailed Descendant Information
Get descendants with hierarchy levels and path information:
```javascript theme={null}
const detailedData = await fetch('/v1/concepts/73211009/descendants?include_distance=true&include_paths=true');
```
## Related Endpoints
* [Get Concept Ancestors](/api-reference/hierarchy/get-concept-ancestors) - Retrieve parent concepts
* [Get Concept Hierarchy](/api-reference/hierarchy/get-concept-hierarchy) - Complete hierarchy view
* [Get Concept Relationships](/api-reference/relationships/get-concept-relationships) - All concept relationships
* [Search Concepts](/api-reference/search/basic-search) - Search within specific hierarchies
## Notes
* Descendant traversal follows "Is a" relationships by default, but can include other relationship types
* Large hierarchies may contain thousands of descendants - use pagination and filtering appropriately
* Standard concepts are prioritized unless explicitly disabled
* Some medical concepts may have very deep hierarchies (6+ levels)
* Cross-vocabulary concepts may have descendants from multiple vocabularies
* Deprecated concepts are excluded from descendants unless specifically requested with `include_invalid=true`
# Get Concept Hierarchy
Source: https://docs.omophub.com/api-reference/hierarchy/get-concept-hierarchy
GET /v1/concepts/{concept_id}/hierarchy
Retrieve complete hierarchical context for a concept, including both ancestors and descendants in a unified view
This endpoint provides a comprehensive hierarchical view of a concept, showing its position within the medical vocabulary structure by including both ancestor (parent) and descendant (child) relationships in a single response.
## Path Parameters
The unique identifier of the concept to retrieve hierarchy for
**Example:** `73211009` (Diabetes mellitus)
## Query Parameters
Response format for hierarchy data
**Options:** `flat`, `graph`
* `flat`: Returns ancestors and descendants as separate arrays (default)
* `graph`: Returns nodes and edges for visualization (similar to OHDSI Athena)
Filter hierarchy to specific vocabularies (comma-separated)
**Example:** `SNOMED,ICD10CM`
Filter hierarchy to specific domains (comma-separated)
**Example:** `Condition,Drug`
Maximum number of hierarchy levels to traverse in both directions
**Range:** `1-20`
Maximum number of results to return per direction (ancestors/descendants) for performance optimization
**Range:** `1-5000`
**Recommended:** Use 100-500 for interactive queries, up to 1000 for bulk analysis
Comma-separated list of relationship types to follow for hierarchy traversal
**Example:** `Is a,Part of`
Include deprecated/invalid concepts in hierarchy
Specific vocabulary release version to query
**Example:** `2025.1`
## Response
### Flat Format (default)
The concept ID for which hierarchy was retrieved
Array of ancestor concepts in hierarchical order
Unique identifier for the ancestor concept
Standard name of the ancestor concept
Original code from the vocabulary
Vocabulary containing this ancestor
Human-readable vocabulary name
Domain classification
Concept class identifier
Standard concept designation ('S', 'C', or null)
Distance from source concept
Minimum levels of separation from source concept
Maximum levels of separation from source concept
Relationship type ID (e.g., "Is a")
Relationship type name
Array of descendant concepts in hierarchical order
Unique identifier for the descendant concept
Standard name of the descendant concept
Original code from the vocabulary
Vocabulary containing this descendant
Human-readable vocabulary name
Domain classification
Concept class identifier
Standard concept designation ('S', 'C', or null)
Distance from source concept
Minimum levels of separation from source concept
Maximum levels of separation from source concept
Relationship type ID (e.g., "Subsumes")
Relationship type name
Current concept's level in the hierarchy
Maximum hierarchy depth
Total number of ancestor concepts
Total number of descendant concepts
### Graph Format (format=graph)
The concept ID for which hierarchy was retrieved
Array of concept nodes for visualization
Unique identifier for the concept
Concept name
Hierarchical level (0 for central concept, negative for ancestors, positive for descendants)
Vocabulary containing this concept
Domain classification
Concept class identifier
Standard concept designation ('S', 'C', or null)
Array of relationship edges between concepts
Source concept ID
Target concept ID
Relationship type (e.g., "Is a", "Subsumes")
```bash cURL (flat format) theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/73211009/hierarchy?max_levels=3" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```bash cURL (graph format) theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/73211009/hierarchy?format=graph&max_levels=3" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (flat format) theme={null}
import requests
concept_id = 73211009 # Diabetes mellitus
url = f"https://api.omophub.com/v1/concepts/{concept_id}/hierarchy"
params = {
"max_levels": 3,
"vocabulary_ids": "SNOMED"
}
headers = {
"Authorization": "Bearer YOUR_API_KEY"
}
response = requests.get(url, params=params, headers=headers)
hierarchy_data = response.json()
print(f"Concept ID: {hierarchy_data['data']['concept_id']}")
print(f"Total ancestors: {hierarchy_data['data']['total_ancestors']}")
print(f"Total descendants: {hierarchy_data['data']['total_descendants']}")
```
```python Python (graph format for visualization) theme={null}
import requests
concept_id = 73211009
url = f"https://api.omophub.com/v1/concepts/{concept_id}/hierarchy"
params = {
"format": "graph",
"max_levels": 3
}
headers = {"Authorization": "Bearer YOUR_API_KEY"}
response = requests.get(url, params=params, headers=headers)
graph_data = response.json()
# Use for D3.js or other visualization libraries
nodes = graph_data['data']['nodes']
edges = graph_data['data']['edges']
print(f"Nodes: {len(nodes)}, Edges: {len(edges)}")
```
```javascript JavaScript (flat format) theme={null}
const response = await fetch('https://api.omophub.com/v1/concepts/73211009/hierarchy?max_levels=3', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const hierarchyData = await response.json();
console.log(`Ancestors: ${hierarchyData.data.total_ancestors}`);
console.log(`Descendants: ${hierarchyData.data.total_descendants}`);
```
```javascript JavaScript (graph format) theme={null}
const response = await fetch('https://api.omophub.com/v1/concepts/73211009/hierarchy?format=graph&max_levels=3', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const graphData = await response.json();
// Use with D3.js force-directed graph
const { nodes, edges } = graphData.data;
```
```json Flat Format Response theme={null}
{
"success": true,
"data": {
"concept_id": 73211009,
"ancestors": [
{
"concept_id": 362969004,
"concept_name": "Disorder of endocrine system",
"concept_code": "362969004",
"vocabulary_id": "SNOMED",
"vocabulary_name": "SNOMED Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 1,
"min_levels_of_separation": 1,
"max_levels_of_separation": 1,
"relationship_id": "Is a",
"relationship_name": "Is a"
},
{
"concept_id": 64572001,
"concept_name": "Disease",
"concept_code": "64572001",
"vocabulary_id": "SNOMED",
"vocabulary_name": "SNOMED Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 2,
"min_levels_of_separation": 2,
"max_levels_of_separation": 2,
"relationship_id": "Is a",
"relationship_name": "Is a"
}
],
"descendants": [
{
"concept_id": 44054006,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"vocabulary_name": "SNOMED Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 1,
"min_levels_of_separation": 1,
"max_levels_of_separation": 1,
"relationship_id": "Subsumes",
"relationship_name": "Subsumes"
},
{
"concept_id": 46635009,
"concept_name": "Type 1 diabetes mellitus",
"concept_code": "46635009",
"vocabulary_id": "SNOMED",
"vocabulary_name": "SNOMED Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 1,
"min_levels_of_separation": 1,
"max_levels_of_separation": 1,
"relationship_id": "Subsumes",
"relationship_name": "Subsumes"
}
],
"level": 3,
"max_level": 5,
"total_ancestors": 2,
"total_descendants": 2
},
"meta": {
"request_id": "req_hierarchy_123",
"timestamp": "2024-12-22T10:30:00Z",
"vocab_release": "2025.2"
}
}
```
```json Graph Format Response theme={null}
{
"success": true,
"data": {
"concept_id": 73211009,
"nodes": [
{
"id": 73211009,
"name": "Diabetes mellitus",
"level": 0,
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S"
},
{
"id": 362969004,
"name": "Disorder of endocrine system",
"level": -1,
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S"
},
{
"id": 44054006,
"name": "Type 2 diabetes mellitus",
"level": 1,
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S"
},
{
"id": 46635009,
"name": "Type 1 diabetes mellitus",
"level": 1,
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S"
}
],
"edges": [
{
"from": 73211009,
"to": 362969004,
"relationship_id": "Is a"
},
{
"from": 73211009,
"to": 44054006,
"relationship_id": "Subsumes"
},
{
"from": 73211009,
"to": 46635009,
"relationship_id": "Subsumes"
}
]
},
"meta": {
"request_id": "req_hierarchy_456",
"timestamp": "2024-12-22T10:30:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Hierarchy View
Get complete hierarchy context for a concept:
```javascript theme={null}
const hierarchy = await fetch('/v1/concepts/73211009/hierarchy');
```
### Limited Depth Hierarchy
Control the depth of ancestor and descendant traversal:
```javascript theme={null}
const limitedHierarchy = await fetch('/v1/concepts/73211009/hierarchy?max_levels=2&max_results=100');
```
### Graph Format for Visualization
Get hierarchy in graph structure for D3.js or similar visualization libraries:
```javascript theme={null}
const graphData = await fetch('/v1/concepts/73211009/hierarchy?format=graph&max_levels=3');
```
### Filtered Hierarchy
Filter to specific vocabularies:
```javascript theme={null}
const filteredHierarchy = await fetch('/v1/concepts/73211009/hierarchy?vocabulary_ids=SNOMED&domain_ids=Condition');
```
## Related Endpoints
* [Get Concept Ancestors](/api-reference/hierarchy/get-concept-ancestors) - Detailed ancestor information with pagination
* [Get Concept Descendants](/api-reference/hierarchy/get-concept-descendants) - Detailed descendant information with pagination
* [Get Concept Relationships](/api-reference/relationships/get-concept-relationships) - All concept relationships
* [Search Concepts](/api-reference/search/basic-search) - Search within hierarchies
## Notes
* The hierarchy endpoint combines ancestors and descendants in a single request for convenience
* Use the `graph` format when building visualizations (compatible with D3.js, Cytoscape, etc.)
* The `flat` format is better for data processing and analysis
* In graph format, level 0 is the central concept, negative levels are ancestors, positive are descendants
* Large hierarchies may be limited by `max_results` to ensure performance
* Cross-vocabulary concepts may show relationships spanning multiple vocabularies
# Introduction
Source: https://docs.omophub.com/api-reference/introduction
Understand general concepts, response codes, and authentication strategies.
## Base URL
The OMOPHub API is built on REST principles. We enforce HTTPS in every request to improve data security, integrity, and privacy. The API does not support HTTP.
All requests contain the following base URL:
```bash theme={null}
https://api.omophub.com/v1
```
## Authentication
To authenticate you need to add an *Authorization* header with the contents of the header being `Bearer oh_xxxxxxxxx` where `oh_xxxxxxxxx` is your API Key.
```bash theme={null}
Authorization: Bearer oh_xxxxxxxxx
```
Get your API key from the [OMOPHub Dashboard](https://dashboard.omophub.com/api-keys)
## Core Capabilities
**Search & Discovery**
* Search across 90+ medical vocabularies (SNOMED CT, ICD-10-CM, RxNorm, LOINC and more)
* Fuzzy, semantic, and phonetic search algorithms
* Autocomplete and suggestions endpoints
**Concept Navigation**
* Navigate concept hierarchies and relationships
* Get ancestors, descendants, and related concepts
* Explore "Is a", "Maps to", and custom relationships
**Vocabulary Mapping**
* Map concepts between different vocabulary systems
* Cross-reference codes across standards
* Validate and check mapping coverage
**OHDSI Compliance**
* Full OMOP CDM vocabulary support (license-free)
* Standardized concept classifications
* Version-controlled vocabulary releases
## API Structure
**RESTful Design**
* Resource-based endpoints (`/concepts`, `/vocabularies`, `/search`)
* Standard HTTP methods (GET, POST)
* Consistent response formats
**Field Naming**
* `snake_case` for all API fields (`concept_id`, `vocabulary_id`)
* Consistent parameter naming across endpoints
**Pagination**
* Page-based pagination (`page`, `page_size`)
* Metadata includes total counts and navigation flags
## API Categories
Look up medical concepts and their detailed information
Advanced search across vocabularies and concepts
Navigate concept hierarchies and relationships
Explore concept relationships and connections
Work with concept domains and classifications
Cross-vocabulary mappings and interoperability
Manage and explore medical vocabularies and their metadata
## Quick Start Examples
### Search for Medical Concepts
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/search/concepts?query=diabetes&vocabulary_ids=SNOMED&page_size=5" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Get Concept Details
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/201826" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Map Between Vocabularies
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/201826/mappings?target_vocabularies=ICD10CM" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Response Format
All API responses follow a consistent structure:
```json theme={null}
{
"success": true,
"data": {
// Your response data here
},
"meta": {
"request_id": "req_abc123",
"timestamp": "2024-01-15T10:30:00Z",
"vocab_release": "2024.2"
}
}
```
## Response codes
OMOPHub uses standard HTTP codes to indicate the success or failure of your requests.
In general, `2xx` HTTP codes correspond to success, `4xx` codes are for user-related failures, and `5xx` codes are for infrastructure issues.
| Status | Description |
| ------ | ---------------------------------------- |
| `200` | Successful request. |
| `400` | Check that the parameters were correct. |
| `401` | The API key used was missing. |
| `403` | The API key used was invalid. |
| `404` | The resource was not found. |
| `429` | The rate limit was exceeded. |
| `5xx` | Indicates an error with OMOPHub servers. |
Check [Error Codes](/api-reference/errors) for a comprehensive breakdown of
all possible API errors.
## Rate limit
The default maximum rate limit is **2 requests per second**. This number can be increased by request. After that, you'll hit the rate limit and receive a `429` response error code.
Learn more about our [rate limits](/api-reference/rate-limit).
## Next Steps
Configure your API key and start making requests
Try the API directly in your browser
Learn about vocabulary releases and versioning
Understand error codes and troubleshooting
# Batch Map Concepts
Source: https://docs.omophub.com/api-reference/mappings/batch-map-concepts
POST https://api.omophub.com/v1/concepts/map/batch
Perform multiple concept mapping operations in a single request
## Overview
This endpoint allows you to perform multiple concept mapping operations in a single API call. Each mapping request maps a set of source concepts to a target vocabulary, enabling efficient bulk processing.
## Request Body
Array of mapping requests (1-50 items)
Unique identifier for this mapping request (for tracking results)
Array of OMOP concept IDs to map (minimum 1 integer)
Target vocabulary ID to map to (e.g., "ICD10CM", "SNOMED", "ICD9CM")
## Query Parameters
Specific vocabulary release version (e.g., "2025.1")
## Response
Indicates if the batch request was processed successfully
Array of mapping results matching the input order
The request ID provided in the input
Array of source concept IDs that were mapped
The target vocabulary for this mapping request
Array of concept mappings found
The source OMOP concept ID that was mapped
The target OMOP concept ID in the target vocabulary
Human-readable name of the target concept
The code of the target concept in its vocabulary
The relationship type (e.g., "Maps to", "Maps to value")
Whether this individual mapping request succeeded
Error message if the mapping request failed (null if successful)
Index of this request in the original input array
Response metadata and API information
Unique request identifier for debugging
ISO 8601 timestamp of the response
Vocabulary release version used
```bash cURL theme={null}
curl -X POST "https://api.omophub.com/v1/concepts/map/batch" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mapping_requests": [
{
"request_id": "diabetes_icd10",
"source_concepts": [201826, 443735],
"target_vocabulary": "ICD10CM"
},
{
"request_id": "hypertension_snomed",
"source_concepts": [4182210],
"target_vocabulary": "SNOMED"
}
]
}'
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/concepts/map/batch', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
mapping_requests: [
{
request_id: "diabetes_icd10",
source_concepts: [201826, 443735],
target_vocabulary: "ICD10CM"
},
{
request_id: "hypertension_snomed",
source_concepts: [4182210],
target_vocabulary: "SNOMED"
}
]
})
});
const result = await response.json();
for (const item of result.data) {
console.log(`Request ${item.request_id}: ${item.success ? 'Success' : 'Failed'}`);
if (item.success) {
console.log(` Found ${item.mappings.length} mappings to ${item.target_vocabulary}`);
} else {
console.log(` Error: ${item.error}`);
}
}
```
```python Python theme={null}
import requests
response = requests.post(
"https://api.omophub.com/v1/concepts/map/batch",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
json={
"mapping_requests": [
{
"request_id": "diabetes_icd10",
"source_concepts": [201826, 443735],
"target_vocabulary": "ICD10CM"
},
{
"request_id": "hypertension_snomed",
"source_concepts": [4182210],
"target_vocabulary": "SNOMED"
}
]
}
)
result = response.json()
for item in result["data"]:
status = "Success" if item["success"] else "Failed"
print(f"Request {item['request_id']}: {status}")
if item["success"]:
print(f" Found {len(item['mappings'])} mappings to {item['target_vocabulary']}")
else:
print(f" Error: {item['error']}")
```
```json theme={null}
{
"success": true,
"data": [
{
"request_id": "diabetes_icd10",
"source_concepts": [201826, 443735],
"target_vocabulary": "ICD10CM",
"mappings": [
{
"source_concept_id": 201826,
"target_concept_id": 443735,
"target_concept_name": "Type 2 diabetes mellitus without complications",
"target_concept_code": "E11.9",
"relationship_id": "Maps to"
}
],
"success": true,
"error": null,
"request_index": 0
},
{
"request_id": "hypertension_snomed",
"source_concepts": [4182210],
"target_vocabulary": "SNOMED",
"mappings": [
{
"source_concept_id": 4182210,
"target_concept_id": 316866,
"target_concept_name": "Hypertensive disorder",
"target_concept_code": "38341003",
"relationship_id": "Maps to"
}
],
"success": true,
"error": null,
"request_index": 1
}
],
"meta": {
"request_id": "req_batch_abc123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Important Notes
* **Batch size limit**: Maximum 50 mapping requests per batch
* **Error isolation**: Individual mapping failures don't affect other mappings in the batch
* **Result ordering**: Results are returned in the same order as input requests
* **Request IDs**: Use unique `request_id` values to correlate results with your input
## Related Endpoints
* [Get Concept Mappings](/api-reference/mappings/get-concept-mappings) - Get mappings for a single concept
* [Validate Mappings](/api-reference/mappings/validate-mappings) - Validate existing mappings
# Get Concept Mappings
Source: https://docs.omophub.com/api-reference/mappings/get-concept-mappings
GET /concepts/{concept_id}/mappings
Get mappings from a concept to concepts in other vocabularies
## Overview
Retrieve all mappings from a concept to equivalent or related concepts in other vocabulary systems.
## Path Parameters
The OMOP concept ID to get mappings for
## Query Parameters
Filter mappings to a specific target vocabulary (e.g., "ICD10CM", "SNOMED")
Include invalid/deprecated mappings in results
Specific vocabulary release version to use (e.g., "2025.1", "2024.2").
When not specified, uses the default/latest vocabulary version.
## Response
Indicates if the request was successful
Contains the mapping results
Array of concept mappings
OMOP concept ID of the source concept
Name of the source concept
OMOP concept ID of the mapped target concept
Name of the target concept
Type of relationship (e.g., "Maps to")
Confidence score for the mapping (0.0 to 1.0)
Response metadata
Unique request identifier for tracing and support
ISO 8601 timestamp of when the request was processed
Vocabulary release version used for this request
## Examples
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/320128/mappings?target_vocabulary=ICD10CM" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/concepts/320128/mappings?target_vocabulary=ICD10CM', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const result = await response.json();
console.log(`Found ${result.data.mappings.length} mappings`);
```
```python Python theme={null}
import requests
params = {"target_vocabulary": "ICD10CM"}
response = requests.get(
"https://api.omophub.com/v1/concepts/320128/mappings",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params=params
)
result = response.json()
print(f"Found {len(result['data']['mappings'])} mappings")
```
```json Response theme={null}
{
"success": true,
"data": {
"mappings": [
{
"source_concept_id": 320128,
"source_concept_name": "Essential hypertension",
"target_concept_id": 319826,
"target_concept_name": "Essential hypertension",
"relationship_id": "Maps to",
"confidence": 1.0
}
]
},
"meta": {
"request_id": "req_abc123",
"timestamp": "2025-01-05T12:00:00.000Z",
"vocab_release": "2025.1"
}
}
```
## Related Endpoints
* [Map Concepts](/api-reference/mappings/map-concepts) - Map multiple concepts to a target vocabulary
* [Batch Map Concepts](/api-reference/mappings/batch-map-concepts) - Batch mapping operations
# Get Mapping Coverage
Source: https://docs.omophub.com/api-reference/mappings/get-mapping-coverage
GET /v1/mappings/coverage
Analyze mapping coverage between a source and target vocabulary
## Overview
This endpoint analyzes mapping coverage between two vocabularies, providing domain-level breakdown and identifying unmapped concepts. Useful for understanding interoperability between vocabulary pairs.
## Query Parameters
Source vocabulary ID to analyze mappings from
**Example:** `SNOMED`
Target vocabulary ID to analyze mappings to
**Example:** `ICD10CM`
Comma-separated domain IDs to filter the analysis
**Example:** `Condition,Drug`
Specific vocabulary release version (e.g., "2025.1")
## Response
Indicates if the request was successful
Coverage analysis results
Source vocabulary ID analyzed
Target vocabulary ID analyzed
Coverage breakdown by domain
Domain identifier
Human-readable domain name
Number of source concepts in this domain
Number of concepts with mappings to target vocabulary
Percentage of concepts with mappings (0-100)
Overall coverage percentage across all domains (0-100)
Sample of concepts without mappings (up to 100)
Concept identifier
Concept name
Domain of the concept
Reason for no mapping
Response metadata and API information
Unique request identifier for debugging
ISO 8601 timestamp of the response
Vocabulary release version used
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/mappings/coverage?source_vocabulary=SNOMED&target_vocabulary=ICD10CM" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/mappings/coverage?source_vocabulary=SNOMED&target_vocabulary=ICD10CM', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const result = await response.json();
console.log(`Overall coverage: ${result.data.overall_coverage}%`);
result.data.domain_coverage.forEach(domain => {
console.log(`${domain.domain_name}: ${domain.coverage_percentage}% (${domain.mapped_concepts}/${domain.source_concepts})`);
});
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/mappings/coverage",
params={
"source_vocabulary": "SNOMED",
"target_vocabulary": "ICD10CM"
},
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
result = response.json()
print(f"Overall coverage: {result['data']['overall_coverage']}%")
for domain in result['data']['domain_coverage']:
print(f"{domain['domain_name']}: {domain['coverage_percentage']}%")
```
```json theme={null}
{
"success": true,
"data": {
"source_vocabulary": "SNOMED",
"target_vocabulary": "ICD10CM",
"domain_coverage": [
{
"domain_id": "Condition",
"domain_name": "Condition",
"source_concepts": 234567,
"mapped_concepts": 178234,
"coverage_percentage": 75.9
},
{
"domain_id": "Procedure",
"domain_name": "Procedure",
"source_concepts": 89234,
"mapped_concepts": 45678,
"coverage_percentage": 51.2
},
{
"domain_id": "Observation",
"domain_name": "Observation",
"source_concepts": 45678,
"mapped_concepts": 12345,
"coverage_percentage": 27.0
}
],
"overall_coverage": 65.5,
"unmapped_concepts": [
{
"concept_id": 4087682,
"concept_name": "Finding related to ability to move",
"domain_id": "Condition",
"reason": "No direct mapping"
},
{
"concept_id": 4273391,
"concept_name": "Coronary artery bypass graft",
"domain_id": "Procedure",
"reason": "No direct mapping"
}
]
},
"meta": {
"request_id": "req_coverage_abc123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Coverage Analysis
Analyze coverage from SNOMED to ICD-10-CM:
```javascript theme={null}
const coverage = await fetch('/v1/mappings/coverage?source_vocabulary=SNOMED&target_vocabulary=ICD10CM');
```
### Filter by Domain
Analyze coverage for specific domains only:
```javascript theme={null}
const conditionCoverage = await fetch('/v1/mappings/coverage?source_vocabulary=SNOMED&target_vocabulary=ICD10CM&domain_ids=Condition,Procedure');
```
### Use Specific Vocabulary Version
Analyze coverage for a specific vocabulary release:
```javascript theme={null}
const coverage = await fetch('/v1/mappings/coverage?source_vocabulary=SNOMED&target_vocabulary=ICD10CM&vocab_release=2025.1');
```
## Related Endpoints
* [Get Concept Mappings](/api-reference/mappings/get-concept-mappings) - Mappings for specific concepts
* [Get Mapping Quality](/api-reference/mappings/get-mapping-quality) - Quality metrics between vocabularies
* [Map Concepts](/api-reference/mappings/map-concepts) - Map concepts to a target vocabulary
## Notes
* Both `source_vocabulary` and `target_vocabulary` are required parameters
* The `unmapped_concepts` array contains a sample of up to 100 unmapped concepts
* Coverage percentages are calculated based on valid, non-deprecated concepts
* Use `domain_ids` to focus analysis on specific medical domains
# Get Mapping Quality
Source: https://docs.omophub.com/api-reference/mappings/get-mapping-quality
GET /v1/mappings/quality/{source_vocabulary}/{target_vocabulary}
Analyze mapping quality between two vocabularies to assess reliability and trustworthiness of cross-vocabulary translations
This endpoint provides analysis of mapping quality between a specific vocabulary pair, essential for understanding the reliability of cross-vocabulary translations, identifying high-confidence mappings, and assessing the trustworthiness of mapping relationships for clinical and operational use.
## Path Parameters
The source vocabulary identifier
**Example:** `SNOMED`, `ICD10CM`, `RxNorm`
The target vocabulary identifier
**Example:** `ICD10CM`, `HCPCS`, `NDC`
## Query Parameters
Specific vocabulary release version to query
**Example:** `2025.1`
## Response
High-level summary of mapping quality
Total number of mappings included in analysis
Mappings that have quality metadata
Overall average confidence score across all mappings
Overall quality assessment
**Values:** `excellent`, `good`, `moderate`, `poor`
Percentage of mappings with confidence > 0.8 (0-100%)
Percentage of officially validated mappings (0-100%)
Percentage of disputed or problematic mappings
Quality metrics for each vocabulary pair
Quality metrics for each source vocabulary
Human-readable vocabulary name
Quality metrics by target vocabulary
Quality metrics for this vocabulary pair
Target vocabulary name
Number of mappings between these vocabularies
Average confidence score for this pair
Median confidence score
Standard deviation of confidence scores
Distribution of equivalence types
Rate of validated mappings (0.0-1.0 fraction)
Quality rating for this vocabulary pair
Main use cases for these mappings
Average quality of outgoing mappings
Quality analysis by medical domain
Quality metrics for each domain
Human-readable domain name
Number of mappings in this domain
Average confidence score for domain
Distribution of quality levels
Mappings with confidence score of 0.9 or higher
Mappings with confidence score between 0.7 and 0.9
Mappings with confidence score between 0.5 and 0.7
Mappings with confidence score below 0.5
Vocabulary pairs with highest quality in this domain
Vocabulary pairs with lowest quality in this domain
Common quality issues in this domain
Detailed quality metrics and distributions
Distribution of confidence scores
Confidence score ranges and counts
Key percentile values
Skewness of confidence distribution
Kurtosis of confidence distribution
Analysis of semantic similarity scores
Average semantic similarity score
Correlation between similarity and confidence
Mappings with low similarity but high confidence
Mappings with high similarity but low confidence
Quality breakdown by mapping source
Quality metrics for official mappings
Quality metrics for community mappings
Quality metrics for algorithmic mappings
Quality metrics for manually created mappings
Impact of validation on quality perception
Quality comparison between validated and unvalidated
Accuracy of validation process
Common characteristics of disputed mappings
Analysis of quality outliers and anomalies (when include\_outliers=true)
Mappings with unusually low quality scores
Source concept identifier
Target concept identifier
Confidence score
Why this is considered an outlier
Potential quality issues identified
Mappings with exceptionally high quality
Mappings with inconsistent quality indicators
Common patterns in outlier mappings
Historical quality trends (when include\_trends=true)
Time series of quality improvements
Date of measurement
Average confidence at this time
Validation rate at this time
Quality of newly added mappings
Annual rate of quality improvement (0.0-1.0 fraction)
Areas with most rapid quality improvement
Areas with declining quality
Recommendations for quality improvement (when include\_recommendations=true)
Type of quality improvement recommendation
Brief title of the recommendation
Detailed description
Implementation priority
Expected quality improvement
Vocabulary pairs that would benefit
Estimated implementation effort
Analysis metadata and processing information
Date when quality analysis was performed
Timestamp of underlying mapping data
Scope of the quality analysis
Time taken to perform analysis
Method used to calculate confidence scores
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/mappings/quality/SNOMED/ICD10CM" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```bash cURL (with vocab_release) theme={null}
curl -X GET "https://api.omophub.com/v1/mappings/quality/SNOMED/ICD10CM?vocab_release=2025.1" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/mappings/quality/SNOMED/ICD10CM', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const qualityData = await response.json();
const { quality_overview } = qualityData.data;
console.log('=== Mapping Quality Analysis ===');
console.log(`Total mappings analyzed: ${quality_overview.total_mappings_analyzed.toLocaleString()}`);
console.log(`Overall quality rating: ${quality_overview.overall_quality_rating}`);
console.log(`Average confidence: ${(quality_overview.overall_average_confidence * 100).toFixed(1)}%`);
console.log(`High-quality mappings: ${quality_overview.high_quality_percentage.toFixed(1)}%`);
console.log(`Validated mappings: ${quality_overview.validated_percentage.toFixed(1)}%`);
// Display domain quality
console.log('\n=== Quality by Domain ===');
Object.entries(quality_by_domain).forEach(([domainId, domainData]) => {
console.log(`${domainData.domain_name}:`);
console.log(` Average confidence: ${(domainData.average_confidence * 100).toFixed(1)}%`);
console.log(` Total mappings: ${domainData.total_mappings.toLocaleString()}`);
const dist = domainData.quality_distribution;
console.log(` Quality distribution:`);
console.log(` Excellent: ${dist.excellent.toLocaleString()}`);
console.log(` Good: ${dist.good.toLocaleString()}`);
console.log(` Moderate: ${dist.moderate.toLocaleString()}`);
console.log(` Poor: ${dist.poor.toLocaleString()}`);
if (domainData.most_reliable_vocabulary_pairs.length > 0) {
console.log(` Most reliable pairs: ${domainData.most_reliable_vocabulary_pairs.slice(0, 3).join(', ')}`);
}
if (domainData.quality_challenges.length > 0) {
console.log(` Quality challenges: ${domainData.quality_challenges.slice(0, 2).join(', ')}`);
}
});
// Display vocabulary pair quality
console.log('\n=== Top Quality Vocabulary Pairs ===');
const vocabPairs = [];
Object.entries(qualityData.data.quality_by_vocabulary_pair).forEach(([sourceVocab, sourceData]) => {
Object.entries(sourceData.target_vocabularies).forEach(([targetVocab, pairData]) => {
vocabPairs.push({
pair: `${sourceVocab} → ${targetVocab}`,
confidence: pairData.average_confidence,
mappings: pairData.total_mappings,
rating: pairData.quality_rating
});
});
});
vocabPairs.sort((a, b) => b.confidence - a.confidence)
.slice(0, 10)
.forEach(pair => {
console.log(`${pair.pair}: ${(pair.confidence * 100).toFixed(1)}% avg confidence (${pair.mappings.toLocaleString()} mappings, ${pair.rating})`);
});
// Display quality metrics
if (quality_metrics) {
console.log('\n=== Quality Metrics ===');
if (quality_metrics.confidence_score_distribution) {
const confDist = quality_metrics.confidence_score_distribution;
console.log('Confidence Score Statistics:');
console.log(` Mean: ${(qualityData.data.quality_overview.overall_average_confidence * 100).toFixed(1)}%`);
if (confDist.percentiles) {
console.log(` 25th percentile: ${(confDist.percentiles['25'] * 100).toFixed(1)}%`);
console.log(` 50th percentile: ${(confDist.percentiles['50'] * 100).toFixed(1)}%`);
console.log(` 75th percentile: ${(confDist.percentiles['75'] * 100).toFixed(1)}%`);
}
}
if (quality_metrics.mapping_source_analysis) {
console.log('\nQuality by Mapping Source:');
Object.entries(quality_metrics.mapping_source_analysis).forEach(([source, data]) => {
if (data.average_confidence) {
console.log(` ${source}: ${(data.average_confidence * 100).toFixed(1)}% avg confidence`);
}
});
}
}
// Display recommendations
if (qualityData.data.recommendations) {
console.log('\n=== Quality Improvement Recommendations ===');
qualityData.data.recommendations.slice(0, 5).forEach((rec, i) => {
console.log(`${i + 1}. ${rec.title} (${rec.priority} priority)`);
console.log(` ${rec.description}`);
console.log(` Expected improvement: ${rec.expected_improvement}`);
if (rec.affected_vocabulary_pairs.length > 0) {
console.log(` Affects: ${rec.affected_vocabulary_pairs.slice(0, 3).join(', ')}`);
}
});
}
```
```python Python theme={null}
import requests
source_vocab = "SNOMED"
target_vocab = "ICD10CM"
url = f"https://api.omophub.com/v1/mappings/quality/{source_vocab}/{target_vocab}"
headers = {
"Authorization": "Bearer YOUR_API_KEY"
}
response = requests.get(url, headers=headers)
data = response.json()
print("=== MAPPING QUALITY ANALYSIS ===")
overview = data['data']['quality_overview']
print(f"Total mappings analyzed: {overview['total_mappings_analyzed']:,}")
print(f"Overall quality rating: {overview['overall_quality_rating']}")
print(f"Average confidence: {overview['overall_average_confidence']:.3f}")
print(f"High-quality mappings: {overview['high_quality_percentage']:.1f}%")
print(f"Validated mappings: {overview['validated_percentage']:.1f}%")
print(f"Disputed mappings: {overview['disputed_percentage']:.1f}%")
# Domain quality analysis
print(f"\n=== QUALITY BY DOMAIN ===")
domain_quality = data['data']['quality_by_domain']
domain_df = pd.DataFrame([
{
'domain': info['domain_name'],
'total_mappings': info['total_mappings'],
'avg_confidence': info['average_confidence'],
'excellent': info['quality_distribution']['excellent'],
'good': info['quality_distribution']['good'],
'moderate': info['quality_distribution']['moderate'],
'poor': info['quality_distribution']['poor']
}
for domain_id, info in domain_quality.items()
])
domain_df = domain_df.sort_values('avg_confidence', ascending=False)
print(domain_df.to_string(index=False, float_format='%.3f'))
# Vocabulary pair quality analysis
print(f"\n=== VOCABULARY PAIR QUALITY ===")
vocab_pairs = []
for source_vocab, source_data in data['data']['quality_by_vocabulary_pair'].items():
for target_vocab, pair_data in source_data['target_vocabularies'].items():
vocab_pairs.append({
'source': source_vocab,
'target': target_vocab,
'pair': f"{source_vocab} → {target_vocab}",
'avg_confidence': pair_data['average_confidence'],
'total_mappings': pair_data['total_mappings'],
'validation_rate': pair_data['validation_rate'],
'quality_rating': pair_data['quality_rating']
})
vocab_pair_df = pd.DataFrame(vocab_pairs)
vocab_pair_df = vocab_pair_df.sort_values('avg_confidence', ascending=False)
print("Top 10 Quality Vocabulary Pairs:")
top_pairs = vocab_pair_df.head(10)
for _, row in top_pairs.iterrows():
print(f"{row['pair']}: {row['avg_confidence']:.3f} confidence, {row['total_mappings']:,} mappings ({row['quality_rating']})")
print("\nBottom 5 Quality Vocabulary Pairs:")
bottom_pairs = vocab_pair_df.tail(5)
for _, row in bottom_pairs.iterrows():
print(f"{row['pair']}: {row['avg_confidence']:.3f} confidence, {row['total_mappings']:,} mappings ({row['quality_rating']})")
# Quality metrics analysis
if 'quality_metrics' in data['data']:
metrics = data['data']['quality_metrics']
print(f"\n=== DETAILED QUALITY METRICS ===")
# Confidence distribution
if 'confidence_score_distribution' in metrics:
conf_dist = metrics['confidence_score_distribution']
print(f"Confidence Score Statistics:")
if 'percentiles' in conf_dist:
for percentile, value in conf_dist['percentiles'].items():
print(f" {percentile}th percentile: {value:.3f}")
if 'skewness' in conf_dist:
print(f" Skewness: {conf_dist['skewness']:.3f}")
if 'kurtosis' in conf_dist:
print(f" Kurtosis: {conf_dist['kurtosis']:.3f}")
# Semantic similarity analysis
if 'semantic_similarity_analysis' in metrics:
sem_analysis = metrics['semantic_similarity_analysis']
print(f"\nSemantic Similarity Analysis:")
print(f" Average similarity: {sem_analysis['average_similarity']:.3f}")
print(f" Similarity-confidence correlation: {sem_analysis['similarity_confidence_correlation']:.3f}")
print(f" Low similarity, high confidence: {sem_analysis['low_similarity_high_confidence']:,}")
print(f" High similarity, low confidence: {sem_analysis['high_similarity_low_confidence']:,}")
# Mapping source analysis
if 'mapping_source_analysis' in metrics:
source_analysis = metrics['mapping_source_analysis']
print(f"\nQuality by Mapping Source:")
for source, source_data in source_analysis.items():
if 'average_confidence' in source_data:
print(f" {source}: {source_data['average_confidence']:.3f} avg confidence")
# Outlier analysis
if 'quality_outliers' in data['data']:
outliers = data['data']['quality_outliers']
print(f"\n=== QUALITY OUTLIERS ===")
if outliers.get('low_quality_outliers'):
print(f"Low Quality Outliers ({len(outliers['low_quality_outliers'])}):")
for outlier in outliers['low_quality_outliers'][:5]:
print(f" Concepts {outlier['source_concept_id']} → {outlier['target_concept_id']}")
print(f" Confidence: {outlier['confidence_score']:.3f}")
print(f" Reason: {outlier['outlier_reason']}")
if outliers.get('outlier_patterns'):
print(f"\nCommon Outlier Patterns:")
for pattern in outliers['outlier_patterns'][:3]:
print(f" - {pattern}")
# Trends analysis
if 'quality_trends' in data['data']:
trends = data['data']['quality_trends']
print(f"\n=== QUALITY TRENDS ===")
print(f"Quality improvement rate: {trends['quality_improvement_rate']:.2f}% annually")
if trends.get('fastest_improving_areas'):
print(f"Fastest improving areas:")
for area in trends['fastest_improving_areas'][:3]:
print(f" - {area}")
# Plot quality trend
if trends.get('quality_over_time'):
quality_history = trends['quality_over_time']
dates = [point['date'] for point in quality_history]
confidences = [point['average_confidence'] for point in quality_history]
validation_rates = [point['validation_rate'] for point in quality_history]
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10))
# Confidence trend
ax1.plot(dates, confidences, marker='o', linewidth=2)
ax1.set_title('Average Confidence Score Over Time')
ax1.set_ylabel('Confidence Score')
ax1.tick_params(axis='x', rotation=45)
ax1.grid(True, alpha=0.3)
# Validation rate trend
ax2.plot(dates, validation_rates, marker='s', linewidth=2, color='green')
ax2.set_title('Validation Rate Over Time')
ax2.set_xlabel('Date')
ax2.set_ylabel('Validation Rate')
ax2.tick_params(axis='x', rotation=45)
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Recommendations
if 'recommendations' in data['data']:
print(f"\n=== QUALITY IMPROVEMENT RECOMMENDATIONS ===")
recommendations = data['data']['recommendations']
for i, rec in enumerate(recommendations[:7], 1):
print(f"{i}. {rec['title']} ({rec['priority']} Priority)")
print(f" {rec['description']}")
print(f" Expected improvement: {rec['expected_improvement']}")
print(f" Implementation effort: {rec['implementation_effort']}")
if rec.get('affected_vocabulary_pairs'):
print(f" Affects: {', '.join(rec['affected_vocabulary_pairs'][:3])}")
print()
# Create comprehensive quality dashboard
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(16, 12))
# 1. Domain quality comparison
domain_names = domain_df['domain']
domain_confidences = domain_df['avg_confidence']
ax1.barh(domain_names, domain_confidences, color='skyblue')
ax1.set_xlabel('Average Confidence Score')
ax1.set_title('Quality by Medical Domain')
ax1.set_xlim(0, 1)
# 2. Quality distribution pie chart
quality_counts = [
domain_df['excellent'].sum(),
domain_df['good'].sum(),
domain_df['moderate'].sum(),
domain_df['poor'].sum()
]
quality_labels = ['Excellent', 'Good', 'Moderate', 'Poor']
colors = ['#2ecc71', '#3498db', '#f39c12', '#e74c3c']
ax2.pie(quality_counts, labels=quality_labels, colors=colors, autopct='%1.1f%%')
ax2.set_title('Overall Quality Distribution')
# 3. Vocabulary pair quality heatmap
if len(vocab_pair_df) > 0:
# Create pivot table for heatmap
pivot_data = vocab_pair_df.pivot_table(
values='avg_confidence',
index='source',
columns='target',
fill_value=0
)
sns.heatmap(pivot_data, annot=True, fmt='.2f', cmap='RdYlGn', ax=ax3)
ax3.set_title('Vocabulary Pair Quality Matrix')
ax3.set_xlabel('Target Vocabulary')
ax3.set_ylabel('Source Vocabulary')
# 4. Confidence score histogram
if 'quality_metrics' in data['data'] and 'confidence_score_distribution' in data['data']['quality_metrics']:
conf_dist = data['data']['quality_metrics']['confidence_score_distribution']
if 'histogram_bins' in conf_dist:
bins = conf_dist['histogram_bins']
bin_centers = [(bin['min'] + bin['max']) / 2 for bin in bins]
bin_counts = [bin['count'] for bin in bins]
ax4.bar(bin_centers, bin_counts, width=0.05, alpha=0.7, color='lightcoral')
ax4.set_xlabel('Confidence Score')
ax4.set_ylabel('Number of Mappings')
ax4.set_title('Confidence Score Distribution')
ax4.set_xlim(0, 1)
plt.tight_layout()
plt.show()
# Summary report
print(f"\n=== SUMMARY REPORT ===")
print(f"• Analyzed {overview['total_mappings_analyzed']:,} mappings across {len(domain_quality)} domains")
print(f"• Overall system quality rating: {overview['overall_quality_rating'].upper()}")
print(f"• {overview['high_quality_percentage']:.1f}% of mappings are high-quality (confidence > 0.8)")
print(f"• {overview['validated_percentage']:.1f}% of mappings are officially validated")
print(f"• Top quality domain: {domain_df.iloc[0]['domain']} ({domain_df.iloc[0]['avg_confidence']:.3f} avg confidence)")
print(f"• Best vocabulary pair: {vocab_pair_df.iloc[0]['pair']} ({vocab_pair_df.iloc[0]['avg_confidence']:.3f} confidence)")
if 'recommendations' in data['data']:
high_priority_recs = [r for r in data['data']['recommendations'] if r['priority'] == 'high']
print(f"• {len(high_priority_recs)} high-priority improvement recommendations identified")
```
```json theme={null}
{
"success": true,
"data": {
"quality_overview": {
"total_mappings_analyzed": 4578923,
"mappings_with_quality_data": 3456789,
"overall_average_confidence": 0.847,
"overall_quality_rating": "good",
"high_quality_percentage": 72.3,
"validated_percentage": 68.9,
"disputed_percentage": 2.7
},
"quality_by_vocabulary_pair": {
"SNOMED": {
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"target_vocabularies": {
"ICD10CM": {
"target_vocabulary_name": "International Classification of Diseases, Tenth Revision, Clinical Modification",
"total_mappings": 156789,
"average_confidence": 0.923,
"median_confidence": 0.950,
"confidence_std_dev": 0.087,
"equivalence_distribution": {
"exact": 89234,
"broader": 45678,
"narrower": 12345,
"related": 9532
},
"validation_rate": 0.847,
"quality_rating": "excellent",
"primary_use_cases": ["billing", "quality_reporting", "clinical_documentation"]
},
"HCPCS": {
"target_vocabulary_name": "Healthcare Common Procedure Coding System",
"total_mappings": 67890,
"average_confidence": 0.756,
"median_confidence": 0.780,
"confidence_std_dev": 0.145,
"equivalence_distribution": {
"exact": 23456,
"broader": 34567,
"narrower": 6789,
"related": 3078
},
"validation_rate": 0.623,
"quality_rating": "good",
"primary_use_cases": ["billing", "procedure_coding"]
}
},
"overall_outgoing_quality": 0.875
}
},
"quality_by_domain": {
"Condition": {
"domain_name": "Condition",
"total_mappings": 2847562,
"average_confidence": 0.891,
"quality_distribution": {
"excellent": 1456789,
"good": 987654,
"moderate": 345678,
"poor": 57441
},
"most_reliable_vocabulary_pairs": [
"SNOMED → ICD10CM",
"ICD10CM → SNOMED",
"SNOMED → ICD10"
],
"least_reliable_vocabulary_pairs": [
"Read → ICPC2",
"Local → Standard"
],
"quality_challenges": [
"Complex conditions often map to broader categories",
"Regional terminology variations affect consistency",
"Rare diseases have limited mapping coverage"
]
},
"Drug": {
"domain_name": "Drug",
"total_mappings": 987654,
"average_confidence": 0.823,
"quality_distribution": {
"excellent": 456789,
"good": 345678,
"moderate": 123456,
"poor": 61731
},
"most_reliable_vocabulary_pairs": [
"RxNorm → NDC",
"NDC → RxNorm"
],
"least_reliable_vocabulary_pairs": [
"SNOMED → RxNorm",
"Local drug codes → RxNorm"
],
"quality_challenges": [
"Generic vs brand name mapping complexity",
"Dosage form variations affect precision",
"Discontinued medications create gaps"
]
}
},
"quality_metrics": {
"confidence_score_distribution": {
"histogram_bins": [
{"min": 0.0, "max": 0.1, "count": 12456},
{"min": 0.1, "max": 0.2, "count": 23456},
{"min": 0.8, "max": 0.9, "count": 456789},
{"min": 0.9, "max": 1.0, "count": 678901}
],
"percentiles": {
"25": 0.734,
"50": 0.847,
"75": 0.923,
"95": 0.978
},
"skewness": -0.342,
"kurtosis": 2.156
},
"semantic_similarity_analysis": {
"average_similarity": 0.823,
"similarity_confidence_correlation": 0.756,
"low_similarity_high_confidence": 23456,
"high_similarity_low_confidence": 12345
},
"mapping_source_analysis": {
"official_mappings": {
"average_confidence": 0.912,
"total_count": 2345678,
"validation_rate": 0.923
},
"community_mappings": {
"average_confidence": 0.734,
"total_count": 456789,
"validation_rate": 0.456
},
"algorithmic_mappings": {
"average_confidence": 0.678,
"total_count": 567890,
"validation_rate": 0.234
}
}
},
"quality_outliers": {
"low_quality_outliers": [
{
"source_concept_id": 12345,
"target_concept_id": 67890,
"confidence_score": 0.123,
"outlier_reason": "Very low confidence despite official mapping",
"potential_issues": [
"Semantic mismatch",
"Outdated mapping relationship"
]
}
],
"outlier_patterns": [
"Algorithmic mappings between distant concept classes",
"Legacy mappings not updated with vocabulary revisions",
"Cross-domain mappings with semantic drift"
]
},
"recommendations": [
{
"recommendation_type": "validation_review",
"title": "Review disputed SNOMED to HCPCS mappings",
"description": "Systematic review of 2,847 disputed mappings between SNOMED procedures and HCPCS codes to improve validation rate",
"priority": "high",
"expected_improvement": "Increase HCPCS mapping quality by 15-20%",
"affected_vocabulary_pairs": ["SNOMED → HCPCS"],
"implementation_effort": "moderate"
},
{
"recommendation_type": "algorithmic_improvement",
"title": "Enhance semantic similarity algorithms for drug mappings",
"description": "Improve algorithmic mapping quality between drug vocabularies using enhanced semantic similarity models",
"priority": "medium",
"expected_improvement": "Increase drug domain confidence by 10%",
"affected_vocabulary_pairs": ["RxNorm → NDC", "SNOMED → RxNorm"],
"implementation_effort": "high"
}
]
},
"meta": {
"request_id": "req_quality_analysis_234567",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2",
"analysis_scope": {
"vocabularies_included": 23,
"domains_analyzed": 15,
"mapping_relationships_analyzed": 4578923
},
"confidence_calculation_method": "weighted_semantic_similarity_with_validation"
}
}
```
## Usage Examples
### SNOMED to ICD10CM Quality
Analyze mapping quality between SNOMED and ICD10CM:
```javascript theme={null}
const quality = await fetch('/v1/mappings/quality/SNOMED/ICD10CM');
```
### RxNorm to NDC Quality
Analyze drug vocabulary mapping quality:
```javascript theme={null}
const drugQuality = await fetch('/v1/mappings/quality/RxNorm/NDC');
```
### With Specific Vocabulary Release
Query quality metrics for a specific vocabulary version:
```javascript theme={null}
const versionedQuality = await fetch('/v1/mappings/quality/SNOMED/HCPCS?vocab_release=2025.1');
```
## Related Endpoints
* [Get Concept Mappings](/api-reference/mappings/get-concept-mappings) - Individual concept mapping quality
* [Get Vocabulary Mappings](/api-reference/mappings/get-vocabulary-mappings) - Quality within vocabulary pairs
* [Get Mapping Coverage](/api-reference/mappings/get-mapping-coverage) - Coverage vs quality analysis
## Notes
* Quality analysis requires substantial computational resources for large datasets
* Confidence scores are calculated using multiple factors including semantic similarity and validation status
* Official mappings generally have higher quality than algorithmic or community mappings
* Quality can vary significantly between vocabulary pairs and domains
* Disputed mappings may indicate areas needing expert review
* Quality trends help identify improvement or degradation over time
* Outlier analysis reveals mappings that may need special attention
* Quality thresholds help focus on the most reliable mappings for production use
# Map Concepts
Source: https://docs.omophub.com/api-reference/mappings/map-concepts
POST https://api.omophub.com/v1/concepts/map
Map medical concepts between different vocabularies to find equivalent or related concepts across terminology systems.
## Overview
This endpoint maps medical concepts from source vocabularies to target vocabularies, finding equivalent or related concepts across different medical terminology systems. It's essential for healthcare data integration, cross-system interoperability, and clinical data standardization.
## Request Body
Array of source OMOP concept IDs to map (1-100 concepts). Use this OR `source_codes`, not both.
**Important**: These must be OMOP concept IDs (internal database IDs), NOT vocabulary-specific codes. Use `/v1/concepts/by-code/{vocabulary}/{code}` to look up concept IDs from codes, or use `source_codes` parameter instead.
**Example**: `[201826, 192671]` (OMOP concept IDs for Type 2 diabetes and Myocardial infarction)
Array of vocabulary/code pairs to map (1-100 concepts). Use this OR `source_concepts`, not both.
This allows mapping directly from source system codes (e.g., SNOMED codes) without first looking up the OMOP concept ID.
**Example**:
```json theme={null}
[
{"vocabulary_id": "SNOMED", "concept_code": "44054006"},
{"vocabulary_id": "SNOMED", "concept_code": "22298006"}
]
```
Target vocabulary identifier to map concepts to
**Examples**: `"ICD10CM"`, `"SNOMED"`, `"ICD9CM"`, `"LOINC"`, `"RXNORM"`
Type of concept mapping to filter results by. When not specified, all mapping types are returned.
**Options**:
* `"direct"` - Direct mappings only (Maps to, Mapped from)
* `"equivalent"` - Equivalent concepts (Maps to, Source - contains, Has precise ingredient)
* `"broader"` - Broader concepts (Is a, Subsumes, Has ingredient)
* `"narrower"` - Narrower concepts (Subsumes, Consists of)
Include invalid/deprecated concepts in mapping results
## Query Parameters
Specific vocabulary release version to use for mapping (e.g., "2025.1", "2024.2").
When not specified, uses the default/latest vocabulary version.
## Response
Indicates if the request was successful
Error information (present only on error responses)
Error code identifier (e.g., "INVALID\_VOCABULARY", "RATE\_LIMIT\_EXCEEDED")
Human-readable error message
Additional error details including supported vocabularies and validation failures
List of supported vocabulary names when vocabulary validation fails
Contains the concept mapping results
Array of concept mappings found
OMOP concept ID of the source concept
Name of the source concept
Vocabulary of the source concept
OMOP concept ID of the mapped target concept
Name of the target concept
Vocabulary of the target concept
Original source code in the target vocabulary
Type of relationship between concepts
Human-readable relationship description
Confidence score for the mapping (0.0 to 1.0)
Start date when the mapping became valid
End date when the mapping expires
Original source code in the source vocabulary
Reason if the mapping is invalid, null otherwise
Classification of the mapping: direct, equivalent, broader, narrower, or related
Response metadata
Unique request identifier for tracing and support
ISO 8601 timestamp of when the request was processed
Vocabulary release version used for this request
```bash cURL (with concept IDs) theme={null}
curl -X POST "https://api.omophub.com/v1/concepts/map" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source_concepts": [201826, 192671],
"target_vocabulary": "ICD10CM",
"mapping_type": "direct",
"include_invalid": false
}'
```
```bash cURL (with source codes) theme={null}
curl -X POST "https://api.omophub.com/v1/concepts/map" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source_codes": [
{"vocabulary_id": "SNOMED", "concept_code": "44054006"},
{"vocabulary_id": "SNOMED", "concept_code": "22298006"}
],
"target_vocabulary": "ICD10CM",
"mapping_type": "direct"
}'
```
```javascript JavaScript theme={null}
// Using concept IDs
const response = await fetch('https://api.omophub.com/v1/concepts/map', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
source_concepts: [201826, 192671],
target_vocabulary: "ICD10CM",
mapping_type: "direct",
include_invalid: false
})
});
const data = await response.json();
// Using source codes
const responseWithCodes = await fetch('https://api.omophub.com/v1/concepts/map', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
source_codes: [
{ vocabulary_id: "SNOMED", concept_code: "44054006" },
{ vocabulary_id: "SNOMED", concept_code: "22298006" }
],
target_vocabulary: "ICD10CM",
mapping_type: "direct"
})
});
```
```python Python theme={null}
import requests
url = "https://api.omophub.com/v1/concepts/map"
# Using concept IDs
payload_with_ids = {
"source_concepts": [201826, 192671],
"target_vocabulary": "ICD10CM",
"mapping_type": "direct",
"include_invalid": False
}
response = requests.post(
url,
json=payload_with_ids,
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
# Using source codes (vocabulary/code pairs)
payload_with_codes = {
"source_codes": [
{"vocabulary_id": "SNOMED", "concept_code": "44054006"},
{"vocabulary_id": "SNOMED", "concept_code": "22298006"}
],
"target_vocabulary": "ICD10CM",
"mapping_type": "direct"
}
response = requests.post(
url,
json=payload_with_codes,
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
```
```json Response theme={null}
{
"success": true,
"data": {
"mappings": [
{
"source_concept_id": 201826,
"source_concept_name": "Type 2 diabetes mellitus",
"source_concept_code": "44054006",
"source_vocabulary_id": "SNOMED",
"target_concept_id": 35208413,
"target_concept_name": "Type 2 diabetes mellitus",
"target_concept_code": "E11.9",
"target_vocabulary_id": "ICD10CM",
"relationship_id": "Maps to",
"relationship_name": "Maps to",
"valid_start_date": "2023-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null,
"mapping_confidence": 1.0,
"mapping_type": "direct"
},
{
"source_concept_id": 192671,
"source_concept_name": "Myocardial infarction",
"source_concept_code": "22298006",
"source_vocabulary_id": "SNOMED",
"target_concept_id": 35208604,
"target_concept_name": "Acute myocardial infarction, unspecified",
"target_concept_code": "I21.9",
"target_vocabulary_id": "ICD10CM",
"relationship_id": "Maps to",
"relationship_name": "Maps to",
"valid_start_date": "2023-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null,
"mapping_confidence": 1.0,
"mapping_type": "direct"
}
]
},
"meta": {
"request_id": "req_abc123",
"timestamp": "2025-01-05T12:00:00.000Z",
"vocab_release": "2025.1"
}
}
```
## Usage Examples
### Basic Concept Mapping
Map SNOMED concepts to ICD-10-CM:
```json theme={null}
{
"source_concepts": [201826],
"target_vocabulary": "ICD10CM",
"mapping_type": "direct"
}
```
### Cross-System Integration
Map multiple concepts for EHR integration:
```json theme={null}
{
"source_concepts": [201826, 192671, 443729],
"target_vocabulary": "ICD10CM",
"mapping_type": "direct",
"include_invalid": false
}
```
### Drug Mapping
Map drug concepts from SNOMED to RxNorm using OMOP concept IDs:
```json theme={null}
{
"source_concepts": [4306040, 4032577],
"target_vocabulary": "RxNorm",
"mapping_type": "equivalent"
}
```
Or using vocabulary codes directly (recommended when you have source system codes):
```json theme={null}
{
"source_codes": [
{"vocabulary_id": "SNOMED", "concept_code": "387517004"},
{"vocabulary_id": "SNOMED", "concept_code": "108774000"}
],
"target_vocabulary": "RxNorm",
"mapping_type": "equivalent"
}
```
The `source_concepts` parameter expects **OMOP concept IDs**, not vocabulary-specific codes like SNOMED codes.
If you have vocabulary codes (e.g., SNOMED code "387517004"), use the `source_codes` parameter instead,
or first look up the OMOP concept ID using `/v1/concepts/by-code/{vocabulary}/{code}`.
## Related Endpoints
* [Batch Map Concepts](/api-reference/mappings/batch-map-concepts) - Batch mapping operations
* [Get Concept Mappings](/api-reference/mappings/get-concept-mappings) - Get mappings for a specific concept
* [Validate Mappings](/api-reference/mappings/validate-mappings) - Validate mapping accuracy
# Validate Mappings
Source: https://docs.omophub.com/api-reference/mappings/validate-mappings
POST https://api.omophub.com/v1/mappings/validate
Validate the accuracy and quality of concept mappings between vocabularies
## Overview
This endpoint validates concept mappings between vocabularies, checking for concept validity, domain compatibility, and relationship correctness. It returns a validation score and identifies potential issues with each mapping.
## Request Body
Array of concept mappings to validate (1-100 items)
The source OMOP concept ID
The target OMOP concept ID to validate mapping to
The relationship type of the mapping (e.g., "Maps to", "Maps to value", "direct")
## Query Parameters
Specific vocabulary release version (e.g., "2025.1")
## Response
Indicates if the request was successful
Array of validation results matching the input order
The source concept ID that was validated
The target concept ID that was validated
The mapping relationship type that was validated
Whether the mapping is considered valid (validation\_score >= 0.50)
Overall validation score (0.0-1.0). Score starts at 1.0 and is reduced based on issues found.
Array of validation issue descriptions (strings)
Array of recommendations to improve the mapping (strings)
Response metadata and API information
Unique request identifier for debugging
ISO 8601 timestamp of the response
Vocabulary release version used
```bash cURL theme={null}
curl -X POST "https://api.omophub.com/v1/mappings/validate" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mappings": [
{
"source_concept_id": 201826,
"target_concept_id": 443735,
"mapping_type": "Maps to"
},
{
"source_concept_id": 4182210,
"target_concept_id": 320128,
"mapping_type": "Maps to"
}
]
}'
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/mappings/validate', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
mappings: [
{
source_concept_id: 201826,
target_concept_id: 443735,
mapping_type: "Maps to"
}
]
})
});
const result = await response.json();
for (const validation of result.data) {
console.log(`${validation.source_concept_id} → ${validation.target_concept_id}`);
console.log(` Valid: ${validation.is_valid}, Score: ${validation.validation_score}`);
if (validation.issues.length > 0) {
console.log(` Issues: ${validation.issues.join(', ')}`);
}
}
```
```python Python theme={null}
import requests
response = requests.post(
"https://api.omophub.com/v1/mappings/validate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
json={
"mappings": [
{
"source_concept_id": 201826,
"target_concept_id": 443735,
"mapping_type": "Maps to"
}
]
}
)
result = response.json()
for validation in result["data"]:
print(f"{validation['source_concept_id']} → {validation['target_concept_id']}")
print(f" Valid: {validation['is_valid']}, Score: {validation['validation_score']}")
if validation["issues"]:
print(f" Issues: {', '.join(validation['issues'])}")
```
```json theme={null}
{
"success": true,
"data": [
{
"source_concept_id": 201826,
"target_concept_id": 443735,
"mapping_type": "Maps to",
"is_valid": true,
"validation_score": 0.85,
"issues": [],
"recommendations": []
},
{
"source_concept_id": 4182210,
"target_concept_id": 320128,
"mapping_type": "Maps to",
"is_valid": false,
"validation_score": 0.40,
"issues": [
"No direct mapping relationship exists",
"Domain mismatch between concepts"
],
"recommendations": [
"Verify mapping relationship type is correct",
"Consider using a target concept in the same domain"
]
}
],
"meta": {
"request_id": "req_validate_abc123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Validation Scoring
The validation score starts at 1.0 (100%) and is reduced based on issues found:
| Issue | Score Reduction |
| --------------------------------------- | --------------- |
| Invalid source concept | -0.40 |
| Invalid target concept | -0.40 |
| Domain mismatch | -0.20 |
| Standard to non-standard mapping | -0.15 |
| No relationship exists between concepts | -0.25 |
A mapping is considered **valid** when `validation_score >= 0.50`.
## Related Endpoints
* [Get Concept Mappings](/api-reference/mappings/get-concept-mappings) - Get mappings for a concept
* [Batch Map Concepts](/api-reference/mappings/batch-map-concepts) - Map multiple concepts at once
# Rate Limit
Source: https://docs.omophub.com/api-reference/rate-limit
Understand rate limits and how to increase them.
The response headers describe your current rate limit following every request in conformance with the [IETF standard](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers):
| Header name | Description |
| --------------------- | ------------------------------------------------------------------- |
| `ratelimit-limit` | Maximum number of requests allowed within a window. |
| `ratelimit-remaining` | How many requests you have left within the current window. |
| `ratelimit-reset` | How many seconds until the limits are reset. |
| `ratelimit-window` | The window size in seconds for the rate limit. |
| `retry-after` | How many seconds you should wait before making a follow-up request. |
## Rate Limit Responses
The default maximum rate limit is 2 requests per second. This number can be increased upon request.
After that, you’ll hit the rate limit and receive a 429 response error code. You can find all 429 responses by filtering for 429 at the OMOPHub Logs page.
To prevent this, we recommend reducing the rate at which you request the API.
This can be done by introducing a queue mechanism or reducing the number of concurrent requests per second.
If you have specific requirements, [contact support](https://omophub.com/contact) to request a rate increase.
# Get Concept Relationships
Source: https://docs.omophub.com/api-reference/relationships/get-concept-relationships
GET /v1/concepts/{concept_id}/relationships
Retrieve all relationships for a specific concept
This endpoint provides relationship information for a concept, showing how it connects to other concepts through various relationship types such as "Is a", "Part of", "Has ingredient", and others in medical vocabularies.
## Path Parameters
The unique identifier of the concept to retrieve relationships for
**Example:** `201826` (Type 2 diabetes mellitus)
## Query Parameters
Comma-separated list of relationship IDs to filter by
**Example:** `Is a,Maps to,Has finding site`
Filter relationships to specific vocabularies
**Example:** `SNOMED,ICD10CM,RxNorm`
Filter related concepts to specific domains
**Example:** `Condition,Drug,Procedure`
Only return relationships to standard concepts
Include relationships to deprecated/invalid concepts
Include reverse relationships (where this concept is concept\_id\_2)
Page number for pagination (1-based)
Number of relationships to return per page (max 1000)
Specific vocabulary release version
**Example:** `2025.1`
## Response
Array of relationship objects
Source concept ID (the concept you queried)
Target concept ID (the related concept)
Relationship type identifier (e.g., "Is a", "Maps to")
Display name of the relationship
Reverse relationship identifier (e.g., "Subsumes" for "Is a")
Full details of the source concept
Concept identifierConcept nameOriginal vocabulary codeVocabulary identifierVocabulary display nameDomain classificationConcept classStandard concept flag (S, C, or null)Validity start dateValidity end dateReason if invalid (null if valid)
Full details of the target concept (same structure as concept\_1)
Date when relationship became valid
Date when relationship becomes invalid
Reason if relationship is invalid (null if valid)
Response metadata including pagination
Current page numberItems per pageTotal relationshipsTotal number of pagesWhether next page existsWhether previous page existsUnique request identifierRequest timestampVocabulary release version used
```bash cURL theme={null}
curl -G "https://api.omophub.com/v1/concepts/201826/relationships" \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "relationship_ids=Is a,Maps to" \
--data-urlencode "page_size=50"
```
```python Python theme={null}
import requests
concept_id = 201826
url = f"https://api.omophub.com/v1/concepts/{concept_id}/relationships"
params = {
"relationship_ids": "Is a,Maps to",
"vocabulary_ids": "SNOMED",
"page_size": 50
}
headers = {"Authorization": "Bearer YOUR_API_KEY"}
response = requests.get(url, params=params, headers=headers)
data = response.json()
print(f"Total relationships: {data['meta']['pagination']['total_items']}")
for rel in data['data']['relationships']:
print(f" {rel['relationship_id']}: {rel['concept_2']['concept_name']}")
```
```javascript JavaScript theme={null}
const params = new URLSearchParams({
'relationship_ids': 'Is a,Maps to',
'page_size': '50'
});
const response = await fetch(
`https://api.omophub.com/v1/concepts/201826/relationships?${params}`,
{
headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
}
);
const data = await response.json();
console.log(`Found ${data.meta.pagination.total_items} relationships`);
```
```json theme={null}
{
"success": true,
"data": {
"relationships": [
{
"concept_id_1": 201826,
"concept_id_2": 73211009,
"relationship_id": "Is a",
"relationship_name": "Is a",
"reverse_relationship_id": "Subsumes",
"concept_1": {
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"vocabulary_name": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31",
"invalid_reason": null
},
"concept_2": {
"concept_id": 73211009,
"concept_name": "Diabetes mellitus",
"concept_code": "73211009",
"vocabulary_id": "SNOMED",
"vocabulary_name": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31",
"invalid_reason": null
},
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31",
"invalid_reason": null
}
]
},
"meta": {
"request_id": "req_abc123",
"timestamp": "2025-01-04T10:30:00Z",
"vocab_release": "2025.1",
"version": "2025.1",
"duration": 45,
"pagination": {
"page": 1,
"page_size": 100,
"total_items": 47,
"total_pages": 1,
"has_next": false,
"has_previous": false
}
}
}
```
## Usage Examples
### All Relationships
Get all relationships for a concept:
```bash theme={null}
curl "https://api.omophub.com/v1/concepts/201826/relationships" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Hierarchical Relationships Only
Retrieve only "Is a" relationships:
```bash theme={null}
curl "https://api.omophub.com/v1/concepts/201826/relationships?relationship_ids=Is%20a" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Cross-Vocabulary Mappings
Find mappings to ICD-10:
```bash theme={null}
curl "https://api.omophub.com/v1/concepts/201826/relationships?relationship_ids=Maps%20to&vocabulary_ids=ICD10CM" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Including Reverse Relationships
Include relationships where this concept is the target:
```bash theme={null}
curl "https://api.omophub.com/v1/concepts/201826/relationships?include_reverse=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Related Endpoints
* [Get Relationship Types](/api-reference/relationships/get-relationship-types) - Available relationship types
* [Get Concept Ancestors](/api-reference/hierarchy/get-concept-ancestors) - Hierarchical parent relationships
* [Get Concept Descendants](/api-reference/hierarchy/get-concept-descendants) - Hierarchical child relationships
* [Get Concept Mappings](/api-reference/mappings/get-concept-mappings) - Cross-vocabulary mappings
## Notes
* Relationships are bidirectional in the database; use `include_reverse=true` to see both directions
* Standard concepts are not filtered by default; use `standard_only=true` to filter
* Large concepts may have hundreds of relationships - use pagination appropriately
* The response includes full concept details for both source and target concepts
# Get Relationship Types
Source: https://docs.omophub.com/api-reference/relationships/get-relationship-types
GET /v1/relationships/types
Retrieve all available relationship types in the OMOP CDM vocabulary system
This endpoint returns all relationship types from the OMOP CDM RELATIONSHIP table, which defines the types of connections that can exist between concepts.
## Query Parameters
Page number for pagination (1-based)
Number of relationship types to return per page (max 500)
Specific vocabulary release version
**Example:** `2025.1`
## Response
Array of relationship type objects from the OMOP CDM RELATIONSHIP table
Unique identifier for the relationship type (e.g., "Is a", "Maps to")
Display name of the relationship type
Whether this relationship creates hierarchical structure ("0" or "1")
Whether this relationship is used in CONCEPT\_ANCESTOR computation ("0" or "1")
The relationship\_id of the reverse relationship (e.g., "Subsumes" for "Is a")
Concept ID representing this relationship type in the CONCEPT table
Response metadata including pagination
Current page numberItems per pageTotal relationship typesTotal number of pagesWhether next page existsWhether previous page existsUnique request identifierVocabulary release version used
```bash cURL theme={null}
curl "https://api.omophub.com/v1/relationships/types?page_size=50" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
url = "https://api.omophub.com/v1/relationships/types"
params = {"page_size": 50}
headers = {"Authorization": "Bearer YOUR_API_KEY"}
response = requests.get(url, params=params, headers=headers)
data = response.json()
print(f"Total relationship types: {data['meta']['pagination']['total_items']}")
for rel_type in data['data']['relationship_types']:
hierarchical = "Yes" if rel_type['is_hierarchical'] == '1' else "No"
print(f" {rel_type['relationship_id']} -> {rel_type['reverse_relationship_id']} (Hierarchical: {hierarchical})")
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/relationships/types?page_size=50', {
headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
});
const data = await response.json();
console.log(`Found ${data.meta.pagination.total_items} relationship types`);
data.data.relationship_types.forEach(type => {
console.log(`${type.relationship_id} <-> ${type.reverse_relationship_id}`);
});
```
```json theme={null}
{
"success": true,
"data": {
"relationship_types": [
{
"relationship_id": "Is a",
"relationship_name": "Is a",
"is_hierarchical": "1",
"defines_ancestry": "1",
"reverse_relationship_id": "Subsumes",
"relationship_concept_id": 44818821
},
{
"relationship_id": "Maps to",
"relationship_name": "Maps to",
"is_hierarchical": "0",
"defines_ancestry": "0",
"reverse_relationship_id": "Mapped from",
"relationship_concept_id": 44818981
},
{
"relationship_id": "Has finding site",
"relationship_name": "Has finding site",
"is_hierarchical": "0",
"defines_ancestry": "0",
"reverse_relationship_id": "Finding site of",
"relationship_concept_id": 44818788
}
]
},
"meta": {
"request_id": "req_abc123",
"vocab_release": "2025.1",
"pagination": {
"page": 1,
"page_size": 100,
"total_items": 642,
"total_pages": 7,
"has_next": true,
"has_previous": false
}
}
}
```
## Usage Examples
### All Relationship Types
Get all available relationship types:
```bash theme={null}
curl "https://api.omophub.com/v1/relationships/types" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Paginated Results
Navigate through pages of relationship types:
```bash theme={null}
curl "https://api.omophub.com/v1/relationships/types?page=2&page_size=50" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Related Endpoints
* [Get Concept Relationships](/api-reference/relationships/get-concept-relationships) - Relationships for specific concepts
* [Get Concept Mappings](/api-reference/mappings/get-concept-mappings) - Cross-vocabulary mappings
* [Get Vocabularies](/api-reference/vocabulary/get-vocabularies) - Available vocabularies
## Notes
* Relationship types come from the OMOP CDM RELATIONSHIP table
* All relationships exist symmetrically (both directions)
* Hierarchical relationships (like "Is a") are used to build the CONCEPT\_ANCESTOR table
* The `defines_ancestry` field indicates which relationships contribute to ancestor computation
* Each relationship type has a corresponding concept in the CONCEPT table
# Advanced Search
Source: https://docs.omophub.com/api-reference/search/advanced-search
POST /v1/search/advanced
Perform multi-criteria search across healthcare vocabularies with advanced filtering
## Overview
The advanced search endpoint provides powerful multi-criteria search capabilities across healthcare vocabularies. Use complex queries, filters, and ranking to find the most relevant medical concepts.
**Best for:** Complex search scenarios requiring multiple filters, specific vocabularies, or advanced ranking criteria.
**Query Requirements:** Queries must be at least 3 characters long OR include at least one filter (`vocabulary_ids`, `domain_ids`, or `concept_class_ids`). This prevents overly broad searches that could timeout.
## Endpoint
/v1/search/advanced
## Authentication
Bearer token with your API key
## Request Body
The search query term
**Example:** `"diabetes mellitus type 2"`
**Validation:** Query must be at least 3 characters long, OR include at least one filter (`vocabulary_ids`, `domain_ids`, or `concept_class_ids`). Short queries without filters are rejected to prevent performance issues.
Array of vocabulary IDs to search within
**Options:** `SNOMED`, `ICD10CM`, `ICD9CM`, `RxNorm`, `LOINC`, `HCPCS`, etc.
**Default:** All vocabularies
Array of domain IDs to filter by
**Options:** `Condition`, `Drug`, `Procedure`, `Measurement`, `Observation`, etc.
Array of concept class IDs to filter by
**Examples:** `Clinical Finding`, `Pharmaceutical Substance`, `Procedure`
Whether to include only standard concepts
Whether to include invalid/deprecated concepts
Array of relationship filter objects
```json theme={null}
[{
"relationship_id": "Maps to",
"target_vocabularies": ["SNOMED"],
"required": true
}]
```
Filter by validity date range
```json theme={null}
{
"start_date": "2020-01-01",
"end_date": "2024-12-31"
}
```
Page number (1-based indexing)
Number of results per page (max: 1000)
## Response
Whether the request was successful
Array of matching concepts
Unique concept identifier
Human-readable concept name
Concept code within its vocabulary
Source vocabulary identifier
Human-readable vocabulary name
Concept domain classification
Concept class within vocabulary
Standard concept designation (S = Standard, C = Classification)
Date when concept became valid
Date when concept becomes invalid
Reason for invalidity (if applicable)
Search relevance score (0.0 to 1.0)
Available facets for further filtering
Vocabulary facets with counts
Domain facets with counts
Concept class facets with counts
Search execution metadata
Query execution time in milliseconds
Total number of matching concepts
Highest relevance score in results
Search algorithm used
Response metadata including pagination
Unique identifier for this request
ISO 8601 timestamp of the response
Vocabulary release version used
Search metadata
The search query used
Total number of matching concepts
Filters that were applied to the search
Pagination information
Current page number
Number of results per page
Total number of results
Total number of pages
Whether a next page exists
Whether a previous page exists
## Examples
### Basic Search
```bash cURL theme={null}
curl -X POST "https://api.omophub.com/v1/search/advanced" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "diabetes mellitus type 2",
"page_size": 10
}'
```
```python Python theme={null}
results = client.search.advanced_search(
query="diabetes mellitus type 2",
page_size=10
)
for concept in results.data:
print(f"{concept.concept_id}: {concept.concept_name}")
print(f" Vocabulary: {concept.vocabulary_id}")
print(f" Score: {concept.relevance_score:.3f}")
```
```javascript JavaScript theme={null}
const results = await client.search.advancedSearch({
query: 'diabetes mellitus type 2',
page_size: 10
});
results.data.forEach(concept => {
console.log(`${concept.concept_id}: ${concept.concept_name}`);
console.log(` Vocabulary: ${concept.vocabulary_id}`);
console.log(` Score: ${concept.relevance_score.toFixed(3)}`);
});
```
```r R theme={null}
results <- advanced_search_concepts(client,
query = "diabetes mellitus type 2",
page_size = 10
)
for (i in 1:nrow(results$data$concepts)) {
concept <- results$data$concepts[i, ]
cat(paste(concept$concept_id, concept$concept_name, sep = ": "), "\n")
cat(paste(" Vocabulary:", concept$vocabulary_id), "\n")
cat(paste(" Score:", round(concept$relevance_score, 3)), "\n")
}
```
```json Example Response theme={null}
{
"success": true,
"data": {
"concepts": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null,
"relevance_score": 0.953
},
{
"concept_id": 435216,
"concept_name": "Diabetes mellitus type 2 in obese",
"concept_code": "E11.0",
"vocabulary_id": "ICD10CM",
"vocabulary_name": "International Classification of Diseases, Tenth Revision, Clinical Modification",
"domain_id": "Condition",
"concept_class_id": "4-char billing code",
"standard_concept": null,
"valid_start_date": "2015-10-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null,
"relevance_score": 0.847
}
],
"facets": {
"vocabularies": [
{"vocabulary_id": "SNOMED", "vocabulary_name": "SNOMED CT", "count": 45},
{"vocabulary_id": "ICD10CM", "vocabulary_name": "ICD-10-CM", "count": 23},
{"vocabulary_id": "ICD9CM", "vocabulary_name": "ICD-9-CM", "count": 12}
],
"domains": [
{"domain_id": "Condition", "domain_name": "Condition", "count": 67},
{"domain_id": "Observation", "domain_name": "Observation", "count": 13}
],
"concept_classes": [
{"concept_class_id": "Clinical Finding", "count": 45},
{"concept_class_id": "4-char billing code", "count": 23}
]
},
"search_metadata": {
"query_time_ms": 127,
"total_results": 80,
"max_relevance_score": 0.953,
"search_algorithm": "full_text_with_ranking"
}
},
"meta": {
"request_id": "req_adv_search_abc123",
"timestamp": "2024-01-15T10:30:00Z",
"vocab_release": "2025.1",
"search": {
"query": "diabetes mellitus type 2",
"total_results": 80,
"filters_applied": {
"vocabulary_ids": [],
"domain_ids": [],
"standard_concepts_only": true
}
},
"pagination": {
"page": 1,
"page_size": 10,
"total_items": 80,
"total_pages": 8,
"has_next": true,
"has_previous": false
}
}
}
```
### Filtered Search
```bash cURL theme={null}
curl -X POST "https://api.omophub.com/v1/search/advanced" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "hypertension",
"vocabulary_ids": ["SNOMED", "ICD10CM"],
"domain_ids": ["Condition"],
"standard_concepts_only": true,
"date_range": {
"start_date": "2020-01-01",
"end_date": "2024-12-31"
},
"page_size": 20
}'
```
```python Python theme={null}
results = client.search.advanced_search(
query="hypertension",
vocabulary_ids=["SNOMED", "ICD10CM"],
domain_ids=["Condition"],
standard_concepts_only=True,
date_range={
"start_date": "2020-01-01",
"end_date": "2024-12-31"
},
page_size=20
)
print(f"Found {results.meta.pagination.total_items} concepts")
print(f"Query executed in {results.data.search_metadata.query_time_ms}ms")
```
```javascript JavaScript theme={null}
const results = await client.search.advancedSearch({
query: 'hypertension',
vocabulary_ids: ['SNOMED', 'ICD10CM'],
domain_ids: ['Condition'],
standard_concepts_only: true,
date_range: {
start_date: '2020-01-01',
end_date: '2024-12-31'
},
page_size: 20
});
console.log(`Found ${results.meta.pagination.total_items} concepts`);
console.log(`Query executed in ${results.data.search_metadata.query_time_ms}ms`);
```
```r R theme={null}
results <- advanced_search_concepts(client,
query = "hypertension",
vocabulary_ids = c("SNOMED", "ICD10CM"),
domain_ids = c("Condition"),
standard_concepts_only = TRUE,
date_range = list(
start_date = "2020-01-01",
end_date = "2024-12-31"
),
page_size = 20
)
cat(paste("Found", results$meta$pagination$total_items, "concepts\n"))
cat(paste("Query executed in", results$data$search_metadata$query_time_ms, "ms\n"))
```
### Complex Relationship Filtering
```bash cURL theme={null}
curl -X POST "https://api.omophub.com/v1/search/advanced" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "insulin",
"vocabulary_ids": ["RxNorm"],
"domain_ids": ["Drug"],
"relationship_filters": [
{
"relationship_id": "RxNorm has dose form",
"target_vocabularies": ["RxNorm"],
"required": true
}
],
"concept_class_ids": ["Clinical Drug"],
"page_size": 15
}'
```
```python Python theme={null}
results = client.search.advanced_search(
query="insulin",
vocabulary_ids=["RxNorm"],
domain_ids=["Drug"],
relationship_filters=[
{
"relationship_id": "RxNorm has dose form",
"target_vocabularies": ["RxNorm"],
"required": True
}
],
concept_class_ids=["Clinical Drug"],
page_size=15
)
for concept in results.data:
print(f"Drug: {concept.concept_name}")
print(f" Code: {concept.concept_code}")
print(f" Class: {concept.concept_class_id}")
```
```javascript JavaScript theme={null}
const results = await client.search.advancedSearch({
query: 'insulin',
vocabulary_ids: ['RxNorm'],
domain_ids: ['Drug'],
relationship_filters: [
{
relationship_id: 'RxNorm has dose form',
target_vocabularies: ['RxNorm'],
required: true
}
],
concept_class_ids: ['Clinical Drug'],
page_size: 15
});
results.data.forEach(concept => {
console.log(`Drug: ${concept.concept_name}`);
console.log(` Code: ${concept.concept_code}`);
console.log(` Class: ${concept.concept_class_id}`);
});
```
```r R theme={null}
results <- advanced_search_concepts(client,
query = "insulin",
vocabulary_ids = c("RxNorm"),
domain_ids = c("Drug"),
relationship_filters = list(
list(
relationship_id = "RxNorm has dose form",
target_vocabularies = c("RxNorm"),
required = TRUE
)
),
concept_class_ids = c("Clinical Drug"),
page_size = 15
)
for (i in 1:nrow(results$data$concepts)) {
concept <- results$data$concepts[i, ]
cat(paste("Drug:", concept$concept_name), "\n")
cat(paste(" Code:", concept$concept_code), "\n")
cat(paste(" Class:", concept$concept_class_id), "\n")
}
```
## Search Features
### Full-Text Search
* **Multi-field search**: Searches concept names, synonyms, and descriptions
* **Phrase matching**: Use quotes for exact phrases: `"myocardial infarction"`
* **Wildcard support**: Use `*` for partial matching: `diabet*`
* **Boolean operators**: Use AND, OR, NOT: `diabetes AND type 2`
### Relevance Scoring
Results are ranked by relevance using:
1. **Exact matches**: Exact concept name matches score highest
2. **Phrase matches**: Complete phrase matches in names or synonyms
3. **Term frequency**: Frequency of query terms in concept text
4. **Vocabulary priority**: Standard concepts ranked higher
5. **Clinical relevance**: Healthcare-specific ranking adjustments
### Faceted Search
Use facets to understand result distribution:
```python theme={null}
# Access facets for filtering insights
facets = results.data.facets
print("Available vocabularies:")
for vocab in facets.vocabularies:
print(f" {vocab.vocabulary_name}: {vocab.count} concepts")
print("\nAvailable domains:")
for domain in facets.domains:
print(f" {domain.domain_name}: {domain.count} concepts")
```
## Performance Tips
1. **Use specific vocabulary\_ids**: Limit search to relevant vocabularies only
2. **Filter by domain\_ids**: Reduce result set with domain filters
3. **Reasonable page sizes**: Use page\_size of 20-100 for best performance
4. **Cache results**: Cache frequently accessed searches
5. **Use standard concepts**: Set `standard_concepts_only: true` for faster queries
6. **Avoid broad queries**: Short queries (\< 3 characters) without filters are rejected. Use more specific terms or add filters for better results.
## Related Endpoints
Simple concept search with minimal parameters
Real-time search suggestions
Find semantically similar concepts
Get available search facets
# Basic Concept Search
Source: https://docs.omophub.com/api-reference/search/basic-search
GET /search/concepts
Search for medical concepts across vocabularies
## Overview
Search for medical concepts using text queries. This endpoint provides fast, relevant results across all supported vocabularies using advanced search algorithms.
## Query Parameters
Search query text (1-100 characters)
Filter search to specific vocabularies (comma-separated)
**Examples**: `SNOMED`, `SNOMED,ICD10CM`, `RXNORM,NDC`
Filter by medical domains (comma-separated)
**Examples**: `Condition`, `Condition,Procedure`, `Drug,Device`
Page number (1-based indexing)
Number of results per page (max: 1000)
Specific vocabulary release version to query
**Example:** `2025.1`
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/search/concepts?query=hypertension&page_size=10" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
params = {"query": "hypertension", "page_size": 10}
response = requests.get(
"https://api.omophub.com/v1/search/concepts",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params=params
)
concepts = response.json()
```
```javascript JavaScript (Node.js) theme={null}
const params = new URLSearchParams({
query: 'hypertension',
page_size: '10'
});
const response = await fetch(`https://api.omophub.com/v1/search/concepts?${params}`, {
headers: {
'Authorization': `Bearer ${process.env.OMOPHUB_API_KEY}`
}
});
const concepts = await response.json();
```
```bash cURL (filtered) theme={null}
curl -X GET "https://api.omophub.com/v1/search/concepts?query=diabetes&vocabulary_ids=SNOMED&domain_ids=Condition" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (filtered) theme={null}
import requests
params = {
"query": "diabetes",
"vocabulary_ids": "SNOMED",
"domain_ids": "Condition"
}
response = requests.get(
"https://api.omophub.com/v1/search/concepts",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params=params
)
filtered_concepts = response.json()
```
```json theme={null}
{
"success": true,
"data": [
{
"concept_id": 320128,
"concept_name": "Essential hypertension",
"concept_code": "59621000",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"match_score": 0.95,
"match_type": "exact",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31"
},
{
"concept_id": 316866,
"concept_name": "Hypertensive disorder",
"concept_code": "38341003",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"match_score": 0.89,
"match_type": "partial",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31"
},
{
"concept_id": 4124681,
"concept_name": "Accelerated hypertension",
"concept_code": "48146000",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"match_score": 0.85,
"match_type": "fuzzy",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31"
}
],
"meta": {
"pagination": {
"page": 1,
"page_size": 10,
"total_items": 247,
"total_pages": 25,
"has_next": true,
"has_previous": false
},
"search": {
"query": "hypertension",
"total_results": 247,
"filters_applied": {
"vocabulary_ids": ["SNOMED", "ICD10CM"]
}
},
"request_id": "req_search_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Search Features
### 1. **Fuzzy Matching**
Handles typos and misspellings automatically.
### 2. **Semantic Search**
Finds conceptually similar terms even with different wording.
### 3. **Relevance Scoring**
Results ranked by relevance with match scores.
### 4. **Multi-vocabulary**
Searches across all vocabularies simultaneously.
### 5. **Fast Performance**
Optimized for sub-second response times.
## Common Use Cases
### 1. Clinical Coding
Find appropriate codes for clinical documentation.
### 2. Data Standardization
Map free-text clinical notes to standard concepts.
### 3. Quality Assurance
Validate and standardize existing coded data.
### 4. Research Applications
Find concepts for cohort definitions and outcome measures.
# Bulk Search
Source: https://docs.omophub.com/api-reference/search/bulk-search
POST https://api.omophub.com/v1/search/bulk
Perform search operations on multiple queries simultaneously with optimized batch processing.
## Overview
This endpoint allows you to submit multiple search queries in a single request, enabling efficient batch processing of search operations. It's ideal for applications that need to search for many terms simultaneously, such as data processing pipelines, bulk concept mapping, or batch validation workflows.
Each query uses full-text search with a default limit of 20 results per query. Up to 50 queries can be submitted per request.
Each query returns a fixed maximum of 20 results. Pagination, filtering by vocabulary/domain, and alternative search modes are not supported on this endpoint. If you need paginated results or advanced filtering, use the [Basic Search](/api-reference/search/basic-search) or [Advanced Search](/api-reference/search/advanced-search) endpoints instead.
## Request Body
Array of search query objects (1-50 items)
Unique identifier for this search within the batch
Search term or phrase
Enable bulk-optimized search mode for better performance
## Query Parameters
Specific vocabulary release version (defaults to latest)
## Response
Indicates if the request was successful
Array of search results, one per query
Identifier matching the request's search\_id
Original search query
Query execution status: `completed` or `failed`
Array of matching concepts
Unique concept identifier
Primary concept name
Concept code
Source vocabulary
Domain classification
Concept class
Standard concept indicator
Search relevance score
Error message (if query failed)
Processing time for this query in milliseconds
Unique identifier for the request
Request timestamp
Vocabulary release version used
```bash cURL theme={null}
curl -X POST "https://api.omophub.com/v1/search/bulk" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"searches": [
{
"search_id": "s1",
"query": "diabetes"
},
{
"search_id": "s2",
"query": "hypertension"
},
{
"search_id": "s3",
"query": "cardiac catheterization"
}
]
}'
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/search/bulk', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
searches: [
{
search_id: 's1',
query: 'diabetes mellitus'
},
{
search_id: 's2',
query: 'appendectomy'
},
{
search_id: 's3',
query: 'metformin'
}
]
})
});
const data = await response.json();
```
```python Python theme={null}
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
payload = {
'searches': [
{
'search_id': 'diabetes_search',
'query': 'type 2 diabetes'
},
{
'search_id': 'procedure_search',
'query': 'knee replacement'
},
{
'search_id': 'drug_search',
'query': 'insulin glargine'
}
]
}
response = requests.post(
'https://api.omophub.com/v1/search/bulk',
headers=headers,
json=payload
)
data = response.json()
```
```json Response theme={null}
{
"success": true,
"data": [
{
"search_id": "s1",
"query": "diabetes",
"status": "completed",
"results": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"search_score": 0.95
},
{
"concept_id": 4000678,
"concept_name": "Diabetes mellitus",
"concept_code": "73211009",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"search_score": 0.92
}
],
"duration": 856
},
{
"search_id": "s2",
"query": "hypertension",
"status": "completed",
"results": [
{
"concept_id": 320128,
"concept_name": "Essential hypertension",
"concept_code": "59621000",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"search_score": 0.98
},
{
"concept_id": 316866,
"concept_name": "Hypertensive disorder",
"concept_code": "38341003",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"search_score": 0.94
}
],
"duration": 1034
},
{
"search_id": "s3",
"query": "cardiac catheterization",
"status": "completed",
"results": [
{
"concept_id": 4006969,
"concept_name": "Cardiac catheterization",
"concept_code": "41976001",
"vocabulary_id": "SNOMED",
"domain_id": "Procedure",
"concept_class_id": "Procedure",
"standard_concept": "S",
"search_score": 1.0
},
{
"concept_id": 4139525,
"concept_name": "Left heart cardiac catheterization",
"concept_code": "309814006",
"vocabulary_id": "SNOMED",
"domain_id": "Procedure",
"concept_class_id": "Procedure",
"standard_concept": "S",
"search_score": 0.91
}
],
"duration": 957
}
],
"meta": {
"request_id": "req_bulk_search_123",
"timestamp": "2024-01-15T10:30:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Bulk Search
Search multiple terms simultaneously:
```bash theme={null}
curl -X POST "https://api.omophub.com/v1/search/bulk" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"searches": [
{"search_id": "s1", "query": "diabetes"},
{"search_id": "s2", "query": "hypertension"},
{"search_id": "s3", "query": "asthma"}
]
}'
```
### Searching Related Terms
Search for related terms to find overlapping concepts:
```bash theme={null}
curl -X POST "https://api.omophub.com/v1/search/bulk" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"searches": [
{"search_id": "s1", "query": "heart attack"},
{"search_id": "s2", "query": "myocardial infarction"},
{"search_id": "s3", "query": "MI"}
]
}'
```
### High-Volume Processing
Process larger batches from a file:
```bash theme={null}
curl -X POST "https://api.omophub.com/v1/search/bulk" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d @bulk_queries.json
```
Example `bulk_queries.json`:
```json theme={null}
{
"searches": [
{"search_id": "q1", "query": "diabetes mellitus"},
{"search_id": "q2", "query": "essential hypertension"},
{"search_id": "q3", "query": "chronic kidney disease"},
{"search_id": "q4", "query": "atrial fibrillation"},
{"search_id": "q5", "query": "congestive heart failure"}
]
}
```
## Optimization Strategies
### Query Grouping
* **Batch Size**: Optimal batch size is 10-50 queries per request
* **Bulk Mode**: The `bulk_mode` parameter (default `true`) enables optimized query execution for better throughput
* **Result Limit**: Each query returns up to 20 results
### Performance Tips
* Use concise, specific search terms for faster results
* Split very large query sets into multiple requests of 50 queries each
* Use the `search_id` field to correlate results with your source data
### Error Handling
* Each query in the batch is processed independently
* Failed queries return `status: "failed"` with an `error` message
* Successful queries are not affected by failures in other queries
## Related Endpoints
* [Basic Search](/api-reference/search/basic-search) - Single query search
* [Advanced Search](/api-reference/search/advanced-search) - Complex search with filters
* [Semantic Search](/api-reference/search/semantic-search) - Contextual search
# Get Similar Concepts by ID
Source: https://docs.omophub.com/api-reference/search/get-search-similar-by-id
GET https://api.omophub.com/v1/search/similar/{conceptId}
Find medical concepts similar to a specific concept identified by its OMOP concept ID using semantic similarity algorithms.
## Overview
This endpoint finds medical concepts that are semantically similar to a specific concept identified by its OMOP concept ID. It uses the same advanced machine learning algorithms as the POST version but optimized for concept-to-concept similarity matching, making it ideal for discovering related concepts when you have a specific starting point.
## Path Parameters
The OMOP concept ID to find similar concepts for
## Query Parameters
Target vocabularies to search within (comma-separated)
**Examples**: `SNOMED`, `SNOMED,ICD10CM`, `RXNORM,NDC`
Clinical domains to focus the similarity search (comma-separated)
**Examples**: `Condition,Procedure`, `Drug,Device`
Concept classes to include in similarity search (comma-separated)
**Examples**: `Clinical Finding,Procedure`, `Ingredient,Brand Name`
Minimum similarity score threshold (0.0 to 1.0)
**Higher values** = More strict similarity matching
Number of similar concepts to return per page
Page number (1-based indexing)
Include similarity scores in the response
Include explanations for why concepts are considered similar
Filter to standard concepts only
**Options**: `S` (standard), `C` (classification), `NULL` (non-standard)
Include invalid/deprecated concepts in similarity search
Similarity algorithm to use
**Options**: `semantic` (embedding-based), `lexical` (text-based), `hybrid` (combined)
Exclude the source concept from results
## Response
Indicates if the request was successful
Error information (present only on error responses)
Error code identifier
Human-readable error message
Additional error details or context
Contains the similar concepts search results
Information about the source concept used for similarity search
OMOP concept ID of the source concept
Primary name of the source concept
Vocabulary-specific code
Source vocabulary identifier
Clinical domain classification
Concept class identifier
Standard concept designation ('S', 'C', or null for non-standard)
Array of concepts similar to the source concept
OMOP concept ID
Primary concept name
Vocabulary-specific concept code
Source vocabulary identifier
Clinical domain classification
Concept class identifier
Similarity score between 0.0 and 1.0
Reason for similarity match (if requested)
Standard concept designation ('S', 'C', or null for non-standard)
Alternative names for the concept
Information about the similarity search
The concept ID used as the similarity source
Similarity algorithm applied
Minimum similarity threshold applied
Total concepts evaluated for similarity
Number of similar concepts returned
Search processing time in milliseconds
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/search/similar/44054006?vocabulary_ids=SNOMED,ICD10CM&similarity_threshold=0.8&page_size=10&include_scores=true&include_explanations=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.omophub.com/v1/search/similar/44054006?vocabulary_ids=SNOMED,ICD10CM&similarity_threshold=0.8&page_size=10&include_scores=true&include_explanations=true',
{
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
}
);
const data = await response.json();
```
```python Python theme={null}
import requests
url = "https://api.omophub.com/v1/search/similar/44054006"
params = {
"vocabulary_ids": "SNOMED,ICD10CM",
"similarity_threshold": 0.8,
"page_size": 10,
"include_scores": "true",
"include_explanations": "true"
}
response = requests.get(
url,
params=params,
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
```
```json Response theme={null}
{
"success": true,
"data": {
"source_concept": {
"concept_id": 44054006,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S"
},
"similar_concepts": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus without complications",
"concept_code": "E11.9",
"vocabulary_id": "ICD10CM",
"domain_id": "Condition",
"concept_class_id": "4-char nonbill code",
"similarity_score": 0.92,
"explanation": "High similarity - more specific variant of the same condition",
"standard_concept": null,
"synonyms": ["Type II diabetes mellitus uncomplicated"]
},
{
"concept_id": 443729,
"concept_name": "Diabetes mellitus type 2 with hyperglycemia",
"concept_code": "443729",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"similarity_score": 0.89,
"explanation": "High similarity - related complication of the same condition",
"standard_concept": "S",
"synonyms": ["Type 2 DM with elevated glucose"]
},
{
"concept_id": 432367,
"concept_name": "Diabetes mellitus type 2 with diabetic nephropathy",
"concept_code": "E11.21",
"vocabulary_id": "ICD10CM",
"domain_id": "Condition",
"concept_class_id": "5-char billing code",
"similarity_score": 0.86,
"explanation": "High similarity - complication of the same condition",
"standard_concept": null,
"synonyms": ["Type 2 DM with kidney disease"]
}
],
"search_metadata": {
"source_concept_id": 44054006,
"algorithm_used": "hybrid",
"similarity_threshold": 0.8,
"total_candidates": 125000,
"results_returned": 3,
"processing_time_ms": 187
}
},
"meta": {
"request_id": "req_sim_xyz789",
"timestamp": "2024-01-15T10:30:00Z",
"vocab_release": "2025.1",
"pagination": {
"page": 1,
"page_size": 20,
"total_items": 3,
"total_pages": 1,
"has_next": false,
"has_previous": false
},
"search": {
"query": "",
"total_results": 3,
"filters_applied": {
"similarity_type": "concept"
}
}
}
}
```
```json Error Response theme={null}
{
"success": false,
"error": {
"code": "CONCEPT_NOT_FOUND",
"message": "The specified concept ID does not exist",
"details": {
"concept_id": 99999999,
"suggestion": "Verify the concept ID exists in the OMOP vocabularies"
}
}
}
```
## Usage Examples
### Basic Similarity by ID
Find concepts similar to Type 2 Diabetes (concept ID 44054006):
```bash theme={null}
GET /v1/search/similar/44054006?page_size=5&similarity_threshold=0.7
```
### Cross-Vocabulary Similarity
Find similar concepts across multiple vocabularies:
```bash theme={null}
GET /v1/search/similar/1308216?vocabulary_ids=SNOMED,ICD10CM,LOINC&domain_ids=Condition,Measurement&include_explanations=true
```
### Drug Similarity Search
Find similar pharmaceutical concepts:
```bash theme={null}
GET /v1/search/similar/1503297?vocabulary_ids=RXNORM&domain_ids=Drug&concept_class_ids=Ingredient,Clinical Drug&similarity_threshold=0.8
```
### High-Precision Similarity
Get only highly similar concepts with detailed scoring:
```bash theme={null}
GET /v1/search/similar/443390?similarity_threshold=0.9&page_size=10&include_scores=true&algorithm=semantic
```
## Performance Notes
* **Concept-to-concept similarity** is typically faster than query-based similarity
* **Hybrid algorithm** provides best balance of accuracy and performance
* **Semantic algorithm** is most accurate but computationally intensive
* **Caching** is applied for frequently requested concept similarities
## Related Endpoints
* [POST Search Similar](/api-reference/search/search-similar) - Find similar concepts using query text
* [Get Concept](/api-reference/concepts/get-concept) - Get detailed information about a specific concept
# Search Autocomplete
Source: https://docs.omophub.com/api-reference/search/search-autocomplete
GET /v1/search/autocomplete
Get real-time autocomplete suggestions for medical terminology searches with optional phonetic matching.
## Overview
This endpoint provides autocomplete functionality for medical terminology searches. It offers real-time suggestions as users type, helping them find the correct medical terms quickly. The endpoint supports both standard prefix matching and phonetic (Soundex) matching for handling common misspellings.
## Query Parameters
Search query for autocompletion (minimum 2 characters, maximum 100)
Filter suggestions to specific vocabularies (comma-separated)
**Examples**: `SNOMED`, `ICD10CM,LOINC`, `RXNORM`
Filter suggestions to specific domains (comma-separated)
**Examples**: `Condition`, `Drug,Procedure`
Page number (1-based indexing)
Number of suggestions to return per page (1-20)
Matching algorithm to use
**Options**: `default`, `soundex`
Use `soundex` for phonetic matching to handle misspellings
Specific vocabulary release version (defaults to latest)
## Response
Array of autocomplete suggestions with concept details
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/search/autocomplete?query=diab&page_size=5" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/search/autocomplete",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params={
"query": "diab",
"vocabulary_ids": "SNOMED,ICD10CM",
"page_size": 10
}
)
data = response.json()
suggestions = data["suggestions"]
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.omophub.com/v1/search/autocomplete?query=diab&page_size=5',
{
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
}
);
const suggestions = await response.json();
```
```r R theme={null}
library(omophub)
client <- OMOPHub$new()
suggestions <- client$search$autocomplete("diab", page_size = 5)
```
```json Response theme={null}
{
"suggestions": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"score": 0.95
},
{
"concept_id": 201254,
"concept_name": "Type 1 diabetes mellitus",
"concept_code": "46635009",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"score": 0.92
},
{
"concept_id": 443238,
"concept_name": "Diabetic retinopathy",
"concept_code": "4855003",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"score": 0.88
}
],
"meta": {
"query": "diab",
"algorithm": "default",
"total_suggestions": 3,
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Autocomplete
Get suggestions for a partial query:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/search/autocomplete?query=asth&page_size=10" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Vocabulary-Filtered Autocomplete
Focus suggestions on specific vocabularies:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/search/autocomplete?query=hyper&vocabulary_ids=SNOMED,ICD10CM&page_size=10" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Domain-Filtered Autocomplete
Get suggestions only for specific domains:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/search/autocomplete?query=aspir&domain_ids=Drug&page_size=10" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Phonetic Matching (Soundex)
Use phonetic matching to handle misspellings:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/search/autocomplete?query=diabetis&algorithm=soundex&page_size=10" \
-H "Authorization: Bearer YOUR_API_KEY"
```
This will find "diabetes" even when spelled incorrectly as "diabetis".
## Algorithms
### Default Algorithm
* Standard prefix matching
* Fast and accurate for correctly spelled queries
* Best for real-time autocomplete UX
### Soundex Algorithm
* Phonetic matching based on pronunciation
* Handles common misspellings and typos
* Useful for voice-to-text input or uncertain spellings
* Example: "noomonia" matches "pneumonia"
## Performance Notes
* **Minimum query length**: 2 characters required
* **Response time**: Optimized for real-time use (\<100ms typical)
* **Caching**: Results are cached for 24 hours per vocabulary release
* **Rate limiting**: Standard rate limits apply
## Related Endpoints
* [Basic Search](/api-reference/search/basic-search) - Full-text search with filters
* [Similarity Search](/api-reference/search/semantic-search) - Find semantically similar concepts
# Search Facets
Source: https://docs.omophub.com/api-reference/search/search-facets
GET https://api.omophub.com/v1/search/facets
Retrieve search facets for filtering medical terminology search results by vocabulary, domain, concept class, and standard concept status.
## Overview
This endpoint provides faceted search capabilities for medical terminology, returning aggregated counts that can be used to filter and refine search results. Facets help users understand the distribution of concepts across different dimensions.
## Query Parameters
Search query to generate facets for. When provided, facets reflect the distribution of matching concepts.
Filter facets to specific vocabularies (comma-separated)
**Examples**: `SNOMED`, `ICD10CM,LOINC`, `RXNORM`
Specific vocabulary release version (defaults to latest)
**Examples**: `2025.1`, `2024.2`
## Response
Indicates if the request was successful
Contains the faceted search results
The search query provided (empty string if none)
Object containing facet categories and their values
Vocabulary facets with concept counts
Vocabulary identifier (e.g., "SNOMED", "ICD10CM")
Number of concepts in this vocabulary matching the query
Domain facets with concept counts
Domain identifier (e.g., "Condition", "Drug", "Procedure")
Number of concepts in this domain matching the query
Concept class facets with counts
Concept class identifier (e.g., "Clinical Finding", "Procedure")
Number of concepts in this class matching the query
Standard concept status facets
Standard concept indicator: `S` (Standard), `C` (Classification), `N` (Non-standard)
Number of concepts with this status matching the query
Response metadata
Unique identifier for the request
Vocabulary release version used
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/search/facets?query=diabetes&vocabulary_ids=SNOMED,ICD10CM" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.omophub.com/v1/search/facets?query=diabetes&vocabulary_ids=SNOMED,ICD10CM',
{
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
}
);
const data = await response.json();
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/search/facets",
params={
"query": "diabetes",
"vocabulary_ids": "SNOMED,ICD10CM"
},
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
```
```json Response theme={null}
{
"success": true,
"data": {
"query": "diabetes",
"facets": {
"vocabularies": [
{ "vocabulary_id": "SNOMED", "count": 245 },
{ "vocabulary_id": "ICD10CM", "count": 67 }
],
"domains": [
{ "domain_id": "Condition", "count": 198 },
{ "domain_id": "Observation", "count": 87 },
{ "domain_id": "Drug", "count": 27 }
],
"concept_classes": [
{ "concept_class_id": "Clinical Finding", "count": 156 },
{ "concept_class_id": "Disorder", "count": 87 },
{ "concept_class_id": "Procedure", "count": 42 }
],
"standard_concepts": [
{ "standard_concept": "S", "count": 245 },
{ "standard_concept": "C", "count": 34 },
{ "standard_concept": "N", "count": 33 }
]
}
},
"meta": {
"request_id": "req_abc123",
"vocab_release": "2025.1"
}
}
```
## Usage Examples
### Get Facets for a Search Query
```bash theme={null}
GET /v1/search/facets?query=hypertension
```
### Filter Facets by Vocabulary
```bash theme={null}
GET /v1/search/facets?query=medication&vocabulary_ids=RXNORM,NDC
```
### General Facets Without Query
Get overall distribution of concepts:
```bash theme={null}
GET /v1/search/facets
```
## Facet Types
### Vocabularies
Shows distribution across medical vocabularies (SNOMED, ICD10CM, LOINC, RxNorm, etc.). Use to filter searches to specific terminology systems.
### Domains
Shows distribution across clinical domains (Condition, Drug, Procedure, Measurement, etc.). Use to focus on specific types of medical concepts.
### Concept Classes
Shows distribution across concept classifications (Clinical Finding, Disorder, Ingredient, etc.). Provides more granular filtering than domains.
### Standard Concepts
Shows distribution by OMOP standard concept status:
* **S (Standard)**: Preferred concepts for OMOP CDM analytics
* **C (Classification)**: Classification concepts for grouping
* **N (Non-standard)**: Source concepts that map to standard concepts
## Related Endpoints
* [Basic Search](/api-reference/search/basic-search) - Apply facet filters to search
* [Advanced Search](/api-reference/search/advanced-search) - Complex search with multiple filters
# Search Similar Concepts
Source: https://docs.omophub.com/api-reference/search/search-similar
POST https://api.omophub.com/v1/search/similar
Find semantically similar medical concepts using advanced machine learning algorithms with flexible search criteria and body parameters.
## Overview
This endpoint identifies medical concepts that are semantically similar to a provided query or set of criteria. It leverages advanced machine learning models trained on medical terminology to discover related concepts that may not share exact keywords but are clinically relevant and contextually similar.
## Request Body
Primary search query or concept description to find similar concepts for
Target vocabularies to search within (array of strings)
**Examples**: `["SNOMED", "ICD10CM"]`, `["RXNORM", "NDC"]`
Clinical domains to focus the similarity search (array of strings)
**Examples**: `["Condition", "Procedure"]`, `["Drug", "Device"]`
Concept classes to include in similarity search (array of strings)
**Examples**: `["Clinical Finding", "Procedure"]`, `["Ingredient", "Brand Name"]`
Minimum similarity score threshold (0 to 1.0)
**Higher values** = More strict similarity matching
Number of similar concepts to return per page
Page number (1-based indexing)
Include similarity scores in the response
Include explanations for why concepts are considered similar
Filter to standard concepts only
**Options**: `S` (standard), `C` (classification), `N` (non-standard)
Include invalid/deprecated concepts in similarity search
Similarity algorithm to use
**Options**:
* `semantic` - Neural embedding-based similarity. Best for finding conceptually similar terms (e.g., "heart attack" → "Myocardial infarction").
* `lexical` - Text-based Jaccard word similarity. Good for fuzzy text matching and typo tolerance.
* `hybrid` (default) - Combines word and character similarity for balanced matching.
## Response
Indicates if the request was successful
Contains the similar concepts search results
Array of concepts similar to the query
OMOP concept ID
Primary concept name
Vocabulary-specific concept code
Source vocabulary identifier
Clinical domain classification
Concept class identifier
Similarity score between 0.0 and 1.0
Reason for similarity match (if requested)
Standard concept flag (S, C, or N)
Alternative names for the concept
Information about the similarity search
The original search query provided
Similarity algorithm applied (`semantic`, `lexical`, or `hybrid`)
Minimum similarity threshold applied
Approximate count of concepts evaluated for similarity. For `semantic` algorithm, this is a lower bound based on sampled results.
Number of similar concepts returned
Search processing time in milliseconds
Time spent on embedding generation (only present when `algorithm_used` is `semantic`)
```bash cURL theme={null}
curl -X POST "https://api.omophub.com/v1/search/similar" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "type 2 diabetes mellitus",
"vocabulary_ids": ["SNOMED", "ICD10CM"],
"domain_ids": ["Condition"],
"similarity_threshold": 0.8,
"page_size": 10,
"include_scores": true,
"include_explanations": true,
"standard_concept": "S"
}'
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/search/similar', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: "type 2 diabetes mellitus",
vocabulary_ids: ["SNOMED", "ICD10CM"],
domain_ids: ["Condition"],
similarity_threshold: 0.8,
page_size: 10,
include_scores: true,
include_explanations: true,
standard_concept: "S"
})
});
const data = await response.json();
```
```python Python theme={null}
import requests
url = "https://api.omophub.com/v1/search/similar"
payload = {
"query": "type 2 diabetes mellitus",
"vocabulary_ids": ["SNOMED", "ICD10CM"],
"domain_ids": ["Condition"],
"similarity_threshold": 0.8,
"page_size": 10,
"include_scores": True,
"include_explanations": True,
"standard_concept": "S"
}
response = requests.post(
url,
json=payload,
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
```
```json Response theme={null}
{
"success": true,
"data": {
"similar_concepts": [
{
"concept_id": 44054006,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"similarity_score": 1.0,
"explanation": "Exact match - same clinical concept",
"standard_concept": "S",
"synonyms": ["Non-insulin dependent diabetes mellitus", "Adult-onset diabetes"]
},
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus without complications",
"concept_code": "E11.9",
"vocabulary_id": "ICD10CM",
"domain_id": "Condition",
"concept_class_id": "4-char nonbill code",
"similarity_score": 0.92,
"explanation": "High similarity - more specific variant of the same condition",
"standard_concept": "S",
"synonyms": ["Type II diabetes mellitus uncomplicated"]
},
{
"concept_id": 443729,
"concept_name": "Diabetes mellitus type 2 with hyperglycemia",
"concept_code": "443729",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"similarity_score": 0.89,
"explanation": "High similarity - related complication of the same condition",
"standard_concept": "S",
"synonyms": ["Type 2 DM with elevated glucose"]
}
],
"search_metadata": {
"original_query": "type 2 diabetes mellitus",
"algorithm_used": "hybrid",
"similarity_threshold": 0.8,
"total_candidates": 125000,
"results_returned": 3,
"processing_time_ms": 245
}
},
"meta": {
"request_id": "req_sim_abc123",
"timestamp": "2024-01-15T10:30:00Z",
"vocab_release": "2025.1",
"pagination": {
"page": 1,
"page_size": 20,
"total_items": 3,
"total_pages": 1,
"has_next": false,
"has_previous": false
},
"search": {
"query": "type 2 diabetes mellitus",
"total_results": 3,
"filters_applied": {
"similarity_type": "concept"
}
}
}
}
```
```json Error Response theme={null}
{
"success": false,
"error": {
"code": "INVALID_THRESHOLD",
"message": "Similarity threshold must be between 0 and 1.0",
"details": {
"provided_threshold": 1.5,
"valid_range": "0-1.0"
}
}
}
```
## Usage Examples
### Basic Similarity Search
Find concepts similar to a medical condition:
```json theme={null}
{
"query": "hypertension",
"page_size": 5,
"similarity_threshold": 0.7
}
```
### Cross-Vocabulary Similarity
Find similar concepts across multiple vocabularies:
```json theme={null}
{
"query": "myocardial infarction",
"vocabulary_ids": ["SNOMED", "ICD10CM", "ICD9CM"],
"domain_ids": ["Condition"],
"similarity_threshold": 0.8,
"include_explanations": true
}
```
### Pharmacological Similarity
Find similar drug concepts with detailed scoring:
```json theme={null}
{
"query": "metformin hydrochloride",
"vocabulary_ids": ["RXNORM"],
"domain_ids": ["Drug"],
"concept_class_ids": ["Ingredient", "Clinical Drug"],
"similarity_threshold": 0.75,
"include_scores": true,
"algorithm": "semantic"
}
```
### Semantic Search for Clinical Terms
Use neural embeddings to find conceptually similar terms (even when words don't match):
```json theme={null}
{
"query": "heart attack",
"vocabulary_ids": ["SNOMED"],
"domain_ids": ["Condition"],
"similarity_threshold": 0.5,
"algorithm": "semantic",
"page_size": 10
}
```
This will find "Myocardial infarction" and related concepts even though the words "heart" and "attack" don't appear in the medical term.
## Algorithm Comparison
| Feature | `semantic` | `lexical` | `hybrid` (default) |
| --------------------- | --------------------- | ----------------------- | --------------------------- |
| Model | Neural embeddings | Jaccard word similarity | Word + character similarity |
| Best for | Conceptual similarity | Fuzzy text matching | Balanced matching |
| "heart attack" → "MI" | Excellent | Poor (word mismatch) | Poor |
| Typo tolerance | Moderate | Good | Good |
| Total counts | Approximate | Exact | Exact |
| Speed | Fast (15-50ms) | Fast (50-200ms) | Fast (50-200ms) |
| Requirements | Embedding service | None | None |
**Semantic Algorithm Pagination:** When using `algorithm: "semantic"`, the `total_candidates` and pagination counts are approximate values optimized for performance. Use `has_next` in the response to reliably determine if more results exist.
## Related Endpoints
* [GET Search Similar by ID](/api-reference/search/get-search-similar-by-id) - Get similar concepts for a specific concept ID
* [Semantic Search](/api-reference/search/semantic-search) - Dedicated semantic search endpoint using neural embeddings
* [Advanced Search](/api-reference/search/advanced-search) - Advanced search with multiple criteria
# Search Suggest
Source: https://docs.omophub.com/api-reference/search/search-suggest
GET https://api.omophub.com/v1/search/suggest
Get intelligent search suggestions and alternative query recommendations based on user intent, medical context, and search patterns.
## Overview
This endpoint provides intelligent search suggestions and query recommendations to help users refine their medical terminology searches. It analyzes user queries, medical context, and search patterns to suggest alternative search terms, related concepts, and improved query formulations.
## Query Parameters
Original search query for which suggestions are needed
Target vocabularies for suggestions (comma-separated)
**Examples**: `SNOMED`, `ICD10CM,LOINC`, `RXNORM,NDC`
Filter suggestions to specific domains (comma-separated)
**Examples**: `Condition,Procedure`, `Drug,Device`
Filter to specific concept classes (comma-separated)
Types of suggestions to include (comma-separated)
**Options**: `spelling`, `synonym`, `related`, `broader`, `narrower`, `alternative`, `trending`, `popular`, `all`
Medical specialty context for relevant suggestions
**Examples**: `cardiology`, `oncology`, `pediatrics`, `emergency_medicine`
User type for suggestion personalization
**Options**: `physician`, `nurse`, `pharmacist`, `researcher`, `patient`, `student`
Detected or specified search intent
**Options**: `diagnostic`, `therapeutic`, `procedural`, `research`, `educational`, `administrative`
Include spelling corrections and typo fixes
Include alternative phrasings and synonyms
Include conceptually related terms
Include broader and narrower concepts
Include trending medical terms
Include popular search terms
Include explanations for why suggestions are relevant
Enable personalized suggestions based on user history
Cultural or regional context for suggestions
**Examples**: `US`, `UK`, `international`, `multicultural`
Language for suggestions (ISO 639-1 code)
Preferred complexity level for suggestions
**Options**: `simple`, `intermediate`, `advanced`, `technical`, `balanced`
Whether suggestions should be patient-appropriate
Filter by standard concept status: `S`, `N`, `C`
Include suggestions for invalid/deprecated concepts
Page number (1-based indexing)
Number of suggestions to return per page (max 100)
Minimum relevance score for suggestions (0.0-1.0)
Specific vocabulary release version (defaults to latest)
## Response
Indicates if the request was successful
Original search query
Automatically detected search intent
Confidence in intent detection (0.0-1.0)
Assessment of query quality (Excellent, Good, Fair, Poor)
Medical concepts identified in the query
Potential issues with the query
Ways the query could be improved
Spelling and typo corrections
Original potentially misspelled term
Suggested correction
Confidence in the correction (0.0-1.0)
Type of correction (typo, spelling, grammar)
Explanation of the correction (if include\_explanations=true)
Synonym suggestions
Original term from query
Suggested synonym
Relevance score (0.0-1.0)
Associated concept ID (if applicable)
Source vocabulary
Typical usage context
Level of formality (Technical, Clinical, Lay)
Why this synonym is relevant (if include\_explanations=true)
Conceptually related suggestions
Suggested related term
Type of relationship (associated, causes, treats, etc.)
Relevance score (0.0-1.0)
Concept ID
Source vocabulary
Domain classification
Clinical context or use case
Relationship explanation (if include\_explanations=true)
More general concept suggestions
Suggested broader term
Levels up in hierarchy
Relevance score (0.0-1.0)
Concept ID
Source vocabulary
Type of generalization (category, class, system)
When to use this broader term
More specific concept suggestions
Suggested narrower term
Levels down in hierarchy
Relevance score (0.0-1.0)
Concept ID
Source vocabulary
Type of specialization (subtype, specific case, variant)
Level of clinical specificity
Alternative ways to phrase the query
Alternative query phrasing
Type of alternative (formal, informal, technical, lay)
Appropriateness score (0.0-1.0)
Intended audience for this phrasing
Expected number of search results
Advantages of this phrasing
Trending medical terms related to the query
Trending term
Trend strength score (0.0-1.0)
Context for the trend (research, clinical, news)
Relevance to original query (0.0-1.0)
How long it's been trending
Medical specialties driving the trend
Popular search terms related to the query
Popular term
Popularity score (0.0-1.0)
Relative search volume (High, Medium, Low)
Primary user base for this term
Seasonal usage pattern (if applicable)
Stability of popularity (Stable, Growing, Declining)
Recommended search strategy
Recommended vocabularies for this query
Recommended domains to focus on
Suggested filters to apply
General search tips for this type of query
Personalized recommendations (if personalization=true)
Detected user search patterns
User's preferred vocabularies
User's commonly searched domains
Suggested medical specialties based on history
Suggested learning opportunities
Unique identifier for the request
Request timestamp
Suggestion engine version
Total number of suggestions returned
Time taken to generate suggestions
Whether personalization was used
Contextual factors influencing suggestions
Vocabulary release version used
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/search/suggest?query=hart%20atack&vocabulary_ids=SNOMED,ICD10CM&suggestion_types=spelling,synonym,related&medical_context=cardiology&include_explanations=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/search/suggest?query=diabetis&user_context=physician&include_corrections=true&include_alternatives=true&include_related=true&include_hierarchical=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
```python Python theme={null}
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
params = {
'query': 'chest pain breathing problems',
'vocabulary_ids': 'SNOMED,ICD10CM',
'domain_ids': 'Condition,Procedure',
'suggestion_types': 'spelling,synonym,related,broader,narrower',
'medical_context': 'emergency_medicine',
'user_context': 'physician',
'search_intent': 'diagnostic',
'include_corrections': 'true',
'include_alternatives': 'true',
'include_related': 'true',
'include_hierarchical': 'true',
'include_trending': 'true',
'include_explanations': 'true',
'complexity_level': 'advanced',
'page_size': 30,
'relevance_threshold': 0.7
}
response = requests.get(
'https://api.omophub.com/v1/search/suggest',
headers=headers,
params=params
)
data = response.json()
```
```json Response theme={null}
{
"success": true,
"data": {
"query": "hart atack",
"query_analysis": {
"detected_intent": "diagnostic",
"confidence": 0.89,
"query_quality": "Poor",
"medical_concepts": [
{
"concept": "heart attack",
"confidence": 0.85,
"issues": ["spelling error in 'hart'", "spelling error in 'atack'"]
}
],
"potential_issues": [
"Multiple spelling errors detected",
"Informal terminology used",
"Could be more specific"
],
"improvement_opportunities": [
"Use correct spelling: 'heart attack'",
"Consider medical terminology: 'myocardial infarction'",
"Specify type: 'acute myocardial infarction'"
]
},
"suggestions": {
"spelling_corrections": [
{
"original": "hart",
"correction": "heart",
"confidence": 0.95,
"correction_type": "spelling",
"explanation": "Common misspelling of 'heart' in medical queries"
},
{
"original": "atack",
"correction": "attack",
"confidence": 0.92,
"correction_type": "spelling",
"explanation": "Missing 't' in 'attack' - common typo"
}
],
"synonyms": [
{
"original_term": "heart attack",
"synonym": "myocardial infarction",
"relevance_score": 0.98,
"concept_id": 22298006,
"vocabulary_id": "SNOMED",
"usage_context": "Clinical documentation",
"formality_level": "Technical",
"explanation": "Standard medical terminology for heart attack, preferred in clinical settings"
},
{
"original_term": "heart attack",
"synonym": "MI",
"relevance_score": 0.92,
"concept_id": 22298006,
"vocabulary_id": "SNOMED",
"usage_context": "Clinical abbreviation",
"formality_level": "Clinical",
"explanation": "Common clinical abbreviation for myocardial infarction"
},
{
"original_term": "heart attack",
"synonym": "acute coronary syndrome",
"relevance_score": 0.85,
"concept_id": 394659003,
"vocabulary_id": "SNOMED",
"usage_context": "Emergency medicine",
"formality_level": "Clinical",
"explanation": "Broader clinical term encompassing heart attacks and related conditions"
}
],
"related_concepts": [
{
"suggested_term": "angina pectoris",
"relationship_type": "related_condition",
"relevance_score": 0.87,
"concept_id": 194828000,
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"clinical_context": "Precursor or related chest pain condition",
"explanation": "Related cardiac condition that can precede or mimic heart attack"
},
{
"suggested_term": "coronary artery disease",
"relationship_type": "underlying_cause",
"relevance_score": 0.82,
"concept_id": 53741008,
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"clinical_context": "Underlying pathology leading to heart attack",
"explanation": "Primary underlying condition that causes most heart attacks"
},
{
"suggested_term": "cardiac arrest",
"relationship_type": "potential_complication",
"relevance_score": 0.79,
"concept_id": 410429000,
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"clinical_context": "Serious complication of heart attack",
"explanation": "Potential life-threatening complication of myocardial infarction"
}
],
"broader_concepts": [
{
"suggested_term": "cardiovascular disease",
"hierarchy_level": 2,
"relevance_score": 0.76,
"concept_id": 49601007,
"vocabulary_id": "SNOMED",
"generalization_type": "system",
"use_case": "For broader cardiovascular condition searches"
},
{
"suggested_term": "heart disease",
"hierarchy_level": 1,
"relevance_score": 0.81,
"concept_id": 56265001,
"vocabulary_id": "SNOMED",
"generalization_type": "category",
"use_case": "For general cardiac condition searches"
}
],
"narrower_concepts": [
{
"suggested_term": "ST elevation myocardial infarction",
"hierarchy_level": 1,
"relevance_score": 0.89,
"concept_id": 401314000,
"vocabulary_id": "SNOMED",
"specialization_type": "subtype",
"clinical_specificity": "High - specific type based on ECG findings"
},
{
"suggested_term": "non-ST elevation myocardial infarction",
"hierarchy_level": 1,
"relevance_score": 0.87,
"concept_id": 401303003,
"vocabulary_id": "SNOMED",
"specialization_type": "subtype",
"clinical_specificity": "High - specific type based on ECG findings"
},
{
"suggested_term": "acute anterior myocardial infarction",
"hierarchy_level": 1,
"relevance_score": 0.84,
"concept_id": 54329005,
"vocabulary_id": "SNOMED",
"specialization_type": "anatomical_location",
"clinical_specificity": "Very High - location-specific type"
}
],
"alternative_phrasings": [
{
"alternative_query": "heart attack",
"phrasing_type": "corrected_lay",
"appropriateness_score": 0.95,
"target_audience": "General public, patients",
"expected_results": 234,
"advantages": [
"Correct spelling",
"Widely understood",
"Patient-friendly"
]
},
{
"alternative_query": "myocardial infarction",
"phrasing_type": "medical_technical",
"appropriateness_score": 0.92,
"target_audience": "Healthcare professionals",
"expected_results": 187,
"advantages": [
"Precise medical terminology",
"Preferred in clinical documentation",
"Standardized term"
]
},
{
"alternative_query": "acute MI",
"phrasing_type": "clinical_abbreviation",
"appropriateness_score": 0.87,
"target_audience": "Clinical staff",
"expected_results": 156,
"advantages": [
"Efficient clinical communication",
"Commonly used abbreviation",
"Time-saving"
]
}
],
"trending_suggestions": [
{
"suggested_term": "troponin elevation",
"trend_score": 0.78,
"trend_context": "diagnostic_biomarker",
"relevance_to_query": 0.82,
"trend_duration": "6 months",
"specialty_focus": [
"emergency_medicine",
"cardiology"
]
}
],
"popular_suggestions": [
{
"suggested_term": "chest pain",
"popularity_score": 0.94,
"search_volume": "High",
"user_base": "Healthcare professionals and patients",
"seasonal_pattern": "Stable year-round",
"stability": "Stable"
},
{
"suggested_term": "cardiac symptoms",
"popularity_score": 0.86,
"search_volume": "Medium",
"user_base": "Healthcare professionals",
"seasonal_pattern": "None",
"stability": "Stable"
}
]
},
"contextual_recommendations": {
"search_strategy": "Start with corrected spelling, then explore specific types if needed",
"vocabulary_recommendations": [
{
"vocabulary": "SNOMED",
"reason": "Comprehensive cardiac terminology",
"priority": "High"
},
{
"vocabulary": "ICD10CM",
"reason": "Administrative coding and billing",
"priority": "Medium"
}
],
"domain_focus": [
"Condition",
"Observation"
],
"filter_suggestions": [
{
"filter": "domain=Condition",
"reason": "Focus on diagnostic conditions"
},
{
"filter": "specialty=cardiology",
"reason": "Cardiac-specific context"
}
],
"search_tips": [
"Use correct spelling for better results",
"Try both lay terms and medical terminology",
"Consider specific types (STEMI, NSTEMI) for detailed information",
"Include related symptoms (chest pain, shortness of breath) for broader context"
]
},
"personalization_insights": null
},
"meta": {
"request_id": "req_search_suggest_123",
"timestamp": "2024-01-15T10:30:00Z",
"suggestion_engine_version": "v3.1.0",
"total_suggestions": 45,
"processing_time_ms": 178,
"personalization_applied": false,
"context_factors": [
"medical_context: cardiology",
"spelling_errors_detected",
"intent: diagnostic",
"formality: mixed"
],
"vocab_release": "2024.2"
}
}
```
## Usage Examples
### Spelling Correction Suggestions
Get suggestions for queries with spelling errors:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/search/suggest?query=pnemonia&suggestion_types=spelling,synonym&include_corrections=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Comprehensive Query Suggestions
Get comprehensive suggestions for query improvement:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/search/suggest?query=diabetes&suggestion_types=all&medical_context=endocrinology&user_context=physician&include_explanations=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Alternative Phrasing Suggestions
Get alternative ways to phrase a medical query:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/search/suggest?query=high%20blood%20pressure&suggestion_types=alternative,synonym&complexity_level=technical&patient_facing=false" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Hierarchical Concept Suggestions
Get broader and narrower concept suggestions:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/search/suggest?query=pneumonia&suggestion_types=broader,narrower&include_hierarchical=true&vocabulary_ids=SNOMED" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Patient-Friendly Suggestions
Get patient-appropriate suggestions:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/search/suggest?query=myocardial%20infarction&user_context=patient&patient_facing=true&complexity_level=simple&include_alternatives=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Trending and Popular Suggestions
Get trending and popular related terms:
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/search/suggest?query=covid&suggestion_types=trending,popular,related&include_trending=true&include_popular=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Suggestion Types
### Spelling Corrections
* **Purpose**: Fix typos and spelling errors
* **Confidence**: High for obvious corrections
* **Use Case**: Improve query accuracy
* **Examples**: "hart" → "heart", "diabetis" → "diabetes"
### Synonyms
* **Purpose**: Alternative terms with same meaning
* **Formality Levels**: Technical, Clinical, Lay
* **Use Case**: Find standard terminology
* **Examples**: "heart attack" → "myocardial infarction"
### Related Concepts
* **Relationships**: Associated, causes, treats, complicates
* **Clinical Context**: Related conditions and procedures
* **Use Case**: Explore connected medical concepts
* **Examples**: "diabetes" → "insulin", "hyperglycemia"
### Broader Concepts
* **Hierarchy**: Move up concept hierarchies
* **Generalization**: Category, system, class level
* **Use Case**: Widen search scope
* **Examples**: "pneumonia" → "lung disease" → "respiratory condition"
### Narrower Concepts
* **Specialization**: Subtypes, variants, specific cases
* **Clinical Specificity**: Increase diagnostic precision
* **Use Case**: Get more specific results
* **Examples**: "diabetes" → "type 2 diabetes" → "insulin-resistant diabetes"
### Alternative Phrasings
* **Formality**: Formal, informal, technical, lay
* **Target Audience**: Professionals, patients, researchers
* **Use Case**: Reach different audiences
* **Examples**: "MI" vs "heart attack" vs "myocardial infarction"
### Trending Suggestions
* **Temporal**: Currently popular or emerging
* **Context**: Research, clinical practice, news
* **Use Case**: Discover emerging topics
* **Examples**: New treatments, recent outbreaks, research terms
### Popular Suggestions
* **Usage**: Commonly searched terms
* **Stability**: Stable, growing, or declining popularity
* **Use Case**: Find common related searches
* **Examples**: High-volume search terms
## Search Intent Detection
### Diagnostic Intent
* **Characteristics**: Symptom descriptions, condition names
* **Suggestions**: Differential diagnoses, related symptoms
* **Context**: Clinical assessment, patient evaluation
* **Examples**: "chest pain" → diagnostic conditions
### Therapeutic Intent
* **Characteristics**: Treatment queries, medication searches
* **Suggestions**: Treatment options, drug alternatives
* **Context**: Treatment planning, medication selection
* **Examples**: "diabetes treatment" → therapeutic options
### Procedural Intent
* **Characteristics**: Procedure names, intervention queries
* **Suggestions**: Related procedures, alternatives
* **Context**: Procedure selection, surgical planning
* **Examples**: "cardiac catheterization" → related procedures
### Research Intent
* **Characteristics**: Technical terms, study-related queries
* **Suggestions**: Research terminology, methodological terms
* **Context**: Academic research, literature review
* **Examples**: "biomarkers" → research concepts
### Educational Intent
* **Characteristics**: Learning-focused queries
* **Suggestions**: Educational resources, explanatory terms
* **Context**: Medical education, patient education
* **Examples**: "anatomy" → educational concepts
### Administrative Intent
* **Characteristics**: Coding, billing, documentation queries
* **Suggestions**: Administrative codes, documentation terms
* **Context**: Medical coding, billing processes
* **Examples**: "ICD codes" → administrative terminology
## Personalization Features
### User Pattern Analysis
* **Search History**: Commonly searched terms and domains
* **Vocabulary Preferences**: Preferred medical vocabularies
* **Specialty Focus**: Medical specialties of interest
* **Complexity Level**: Preferred terminology complexity
### Personalized Suggestions
* **Relevant Terms**: Terms matching user's interests
* **Vocabulary Bias**: Suggestions from preferred vocabularies
* **Specialty Context**: Specialty-specific recommendations
* **Learning Path**: Educational progression suggestions
### Privacy Considerations
* **Opt-in**: Personalization requires explicit consent
* **Data Security**: Secure storage of user patterns
* **Anonymization**: Personal data anonymization
* **User Control**: Users can disable personalization
## Cultural and Regional Context
### Regional Terminology
* **US Medical Terms**: American medical terminology preferences
* **UK Medical Terms**: British medical terminology variations
* **International**: WHO and international standard terms
* **Multicultural**: Inclusive terminology for diverse populations
### Cultural Sensitivity
* **Inclusive Language**: Culturally appropriate medical terms
* **Accessibility**: Terms accessible to diverse populations
* **Local Practices**: Regional medical practice variations
* **Language Variants**: Regional language preferences
## Related Endpoints
* [Search Autocomplete](/api-reference/search/search-autocomplete) - Real-time query completion
* [Basic Search](/api-reference/search/basic-search) - Execute suggested searches
* [Semantic Search](/api-reference/search/semantic-search) - Meaning-based search
* [Fuzzy Search](/api-reference/search/fuzzy-search) - Typo-tolerant search
# Search Trending
Source: https://docs.omophub.com/api-reference/search/search-trending
GET /v1/search/trending
Retrieve trending and popular search queries based on usage patterns and analytics data
This endpoint provides insights into trending medical concepts and popular search queries, helping users discover relevant terminology based on community usage patterns and temporal trends.
## Query Parameters
Comma-separated list of vocabulary IDs to filter trending searches
**Example:** `SNOMED,ICD10CM,LOINC`
Comma-separated list of domain IDs to focus trending analysis
**Example:** `Condition,Drug,Procedure`
Time period for trending analysis
**Options:** `1d`, `7d`, `30d`, `90d`
Type of trending metric to analyze
**Options:** `search_volume`, `new_concepts`, `rising_queries`, `seasonal_patterns`
Include detailed trending statistics and growth metrics
Include related trending concepts and co-occurrence patterns
Number of trending items to return (max 100)
Page number for pagination (1-based)
## Response
Array of trending search items with analytics data
The trending search query or concept term
Associated concept ID if trending item is a specific concept (returned as string to prevent precision loss)
Standard concept name if applicable
Vocabulary containing the trending concept
Domain classification of the trending concept
Normalized trending score (0-100)
Number of searches in the specified time period
Percentage growth rate compared to previous period
Direction of the trend
**Values:** `rising`, `declining`, `stable`, `new`
Detailed trending statistics (when include\_statistics=true)
Highest search volume in the period
Average daily search volume
Trend consistency score (0-1)
Seasonal adjustment factor
ISO timestamp when trend was first detected
Rate of change in search volume
Related trending concepts (when include\_related=true)
Related concept identifier (returned as string to prevent precision loss)
Related concept name
Correlation strength with main trending item (0-1)
Rate of co-occurrence in searches
Geographic distribution of trend (when available)
Top geographic regions for this trend
Regional trending scores
Response metadata and pagination information
Current page numberItems per pageTotal trending items availableTotal number of pagesWhether next page existsWhether previous page exists
Time period used for trend analysis
Analysis start date (ISO format)Analysis end date (ISO format)Type of period analyzed
Timestamp of last trending data update
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/search/trending?time_period=7d&trend_type=search_volume&include_statistics=true&page_size=10" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript theme={null}
const response = await fetch('https://api.omophub.com/v1/search/trending?time_period=7d&trend_type=search_volume&include_statistics=true&page_size=10', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const trendingData = await response.json();
console.log('Trending searches:', trendingData.data.data);
```
```python theme={null}
import requests
url = "https://api.omophub.com/v1/search/trending"
params = {
"time_period": "7d",
"trend_type": "search_volume",
"include_statistics": True,
"page_size": 10
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.get(url, params=params, headers=headers)
trending_data = response.json()
print(f"Found {len(trending_data['data']['data'])} trending items")
for item in trending_data['data']['data']:
print(f"- {item['query']}: {item['trend_score']} score, {item['growth_rate']}% growth")
```
```json theme={null}
{
"success": true,
"data": {
"data": [
{
"query": "covid-19 symptoms",
"concept_id": "840539006",
"concept_name": "Disease caused by severe acute respiratory syndrome coronavirus 2",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"trend_score": 95.8,
"search_volume": 15420,
"growth_rate": 234.7,
"trend_direction": "rising",
"statistics": {
"peak_volume": 2180,
"average_volume": 1850.0,
"consistency_score": 0.82,
"seasonal_factor": 1.15,
"first_seen": "2024-12-15T08:00:00Z",
"velocity": 12.3
},
"related_concepts": [
{
"concept_id": "49727002",
"concept_name": "Cough",
"correlation_score": 0.78,
"co_occurrence_rate": 0.45
},
{
"concept_id": "386661006",
"concept_name": "Fever",
"correlation_score": 0.72,
"co_occurrence_rate": 0.41
}
]
},
{
"query": "diabetes management",
"concept_id": "73211009",
"concept_name": "Diabetes mellitus",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"trend_score": 87.3,
"search_volume": 8920,
"growth_rate": 45.2,
"trend_direction": "rising",
"statistics": {
"peak_volume": 1420,
"average_volume": 1274.3,
"consistency_score": 0.91,
"seasonal_factor": 1.03,
"first_seen": "2024-12-10T14:30:00Z",
"velocity": 8.7
}
},
{
"query": "hypertension treatment",
"concept_id": "38341003",
"concept_name": "Hypertensive disorder, systemic arterial",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"trend_score": 79.1,
"search_volume": 6750,
"growth_rate": 28.9,
"trend_direction": "stable"
}
],
"meta": {
"pagination": {
"page": 1,
"page_size": 10,
"total_items": 156,
"total_pages": 16,
"has_next": true,
"has_previous": false
},
"analysis_period": {
"start_date": "2024-12-15T00:00:00Z",
"end_date": "2024-12-22T00:00:00Z",
"period_type": "7d"
},
"data_freshness": "2024-12-22T12:00:00Z",
"request_id": "req_search_trending_7d_analytics_001",
"timestamp": "2024-12-22T10:30:00Z",
"vocab_release": "2025.2"
}
}
}
```
## Usage Examples
### Basic Trending Analysis
Get the most popular trending searches in the past week:
```javascript theme={null}
const trending = await fetch('/v1/search/trending?time_period=7d&page_size=20');
```
### Domain-Specific Trends
Find trending searches within specific medical domains:
```javascript theme={null}
const conditionTrends = await fetch('/v1/search/trending?domain_ids=Condition,Drug&time_period=30d');
```
### Rising Query Detection
Identify rapidly growing search queries:
```javascript theme={null}
const risingQueries = await fetch('/v1/search/trending?trend_type=rising_queries&include_statistics=true');
```
### Vocabulary-Specific Analysis
Analyze trends within specific vocabularies:
```javascript theme={null}
const snomedTrends = await fetch('/v1/search/trending?vocabulary_ids=SNOMED&time_period=90d&include_related=true');
```
### Seasonal Pattern Analysis
Detect seasonal trending patterns:
```javascript theme={null}
const seasonalTrends = await fetch('/v1/search/trending?trend_type=seasonal_patterns&time_period=90d');
```
## Related Endpoints
* [Search Concepts](/api-reference/search/basic-search) - Primary concept search functionality
* [Search Autocomplete](/api-reference/search/search-autocomplete) - Real-time search suggestions
* [Search Facets](/api-reference/search/search-facets) - Search result faceting and filtering
* [Search Suggest](/api-reference/search/search-suggest) - Intelligent search suggestions
* [Get Vocabulary Statistics](/api-reference/vocabulary/get-vocabulary-statistics) - Overall vocabulary usage stats
## Notes
* Trending data is updated every 4 hours with the latest search analytics
* Trend scores are normalized across all vocabularies and time periods
* Geographic data may not be available for all trending items due to privacy considerations
* Rising queries are identified using proprietary algorithms that account for baseline search volume
* Seasonal patterns require at least 90 days of historical data for accurate detection
* Some trending data may be filtered to exclude potentially sensitive health information
# Semantic Search
Source: https://docs.omophub.com/api-reference/search/semantic-search
GET /concepts/semantic-search
Find medical concepts using natural language and AI-powered semantic similarity
## Overview
Semantic search uses LLM generated embeddings to find OMOP concepts that are semantically similar to your query, even when exact keyword matches don't exist. This is ideal for natural language queries and clinical text processing.
**Example queries:**
* "heart attack" → finds "Myocardial infarction"
* "sugar diabetes" → finds "Type 2 diabetes mellitus"
* "high blood pressure" → finds "Essential hypertension"
* "belly pain" → finds "Abdominal pain"
## When to Use Semantic Search
| Scenario | Use Semantic Search | Use Basic Search |
| ------------------------- | ------------------- | ---------------- |
| Natural language queries | Yes | No |
| Patient-reported symptoms | Yes | No |
| Clinical shorthand/slang | Yes | No |
| Exact code lookup | No | Yes |
| Browsing vocabularies | No | Yes |
## Query Parameters
Natural language search query (1-500 characters)
Page number (1-based)
Results per page (1-100)
Minimum similarity score (0.0-1.0). Higher values = stricter matching.
**Recommended thresholds:** - `0.7` - Very high confidence matches only -
`0.5` - Balanced, high precision (default) - `0.3` - More exploratory results
Filter to specific vocabularies (comma-separated)
**Examples:** `SNOMED`, `SNOMED,ICD10CM`, `SNOMED,ICD10CM,RXNORM`
Filter to specific domains (comma-separated)
**Examples:** `Condition`, `Drug`, `Condition,Drug,Procedure`
Filter by standard concept status
**Values:** `S` (Standard), `C` (Classification)
Vocabulary version (e.g., `2025v2`). Uses default if not specified.
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/semantic-search?query=heart%20attack&page_size=5" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/concepts/semantic-search",
params={"query": "heart attack", "page_size": 5},
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
results = response.json()
for concept in results["data"]["results"]:
print(f"{concept['similarity_score']:.2f} - {concept['concept_name']}")
```
```javascript JavaScript (Node.js) theme={null}
const params = new URLSearchParams({
query: 'heart attack',
page_size: '5',
});
const response = await fetch(
`https://api.omophub.com/v1/concepts/semantic-search?${params}`,
{
headers: {
Authorization: `Bearer ${process.env.OMOPHUB_API_KEY}`,
},
}
);
const results = await response.json();
```
```bash cURL (with filters) theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/semantic-search?query=chest%20pain&vocabulary_ids=SNOMED,ICD10CM&domain_ids=Condition&threshold=0.5" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (with filters) theme={null}
import requests
params = {
"query": "chest pain",
"vocabulary_ids": "SNOMED,ICD10CM",
"domain_ids": "Condition",
"threshold": 0.5,
"standard_concept": "S"
}
response = requests.get(
"https://api.omophub.com/v1/concepts/semantic-search",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params=params
)
filtered_results = response.json()
```
```python Python (pagination) theme={null}
import requests
# Get page 2 of results
params = {
"query": "diabetes",
"page": 2,
"page_size": 20
}
response = requests.get(
"https://api.omophub.com/v1/concepts/semantic-search",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params=params
)
page_2_results = response.json()
pagination = page_2_results["meta"]["pagination"]
print(f"Page {pagination['page']} of {pagination['total_pages']}")
```
```json theme={null}
{
"success": true,
"data": {
"query": "heart attack",
"results": [
{
"concept_id": 4329847,
"concept_name": "Myocardial infarction",
"domain_id": "Condition",
"vocabulary_id": "SNOMED",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"concept_code": "22298006",
"similarity_score": 0.92,
"matched_text": "Myocardial infarction"
},
{
"concept_id": 434376,
"concept_name": "Acute myocardial infarction",
"domain_id": "Condition",
"vocabulary_id": "SNOMED",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"concept_code": "57054005",
"similarity_score": 0.89,
"matched_text": "Acute myocardial infarction"
},
{
"concept_id": 4108217,
"concept_name": "Old myocardial infarction",
"domain_id": "Condition",
"vocabulary_id": "SNOMED",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"concept_code": "1755008",
"similarity_score": 0.85,
"matched_text": "Old myocardial infarction"
}
],
"total_results": 45,
"latency_ms": 28
},
"meta": {
"pagination": {
"page": 1,
"page_size": 20,
"total_items": 45,
"total_pages": 3,
"has_next": true,
"has_previous": false
},
"request_id": "req_sem_abc123",
"vocab_release": "2025v1",
"timestamp": "2025-01-15T10:30:00Z",
"search": {
"query": "heart attack",
"total_results": 45,
"filters_applied": {}
}
}
}
```
## Response Fields
### Data Object
Original search query
Array of matching concepts with similarity scores
Approximate number of matching concepts. This is a lower bound based on
sampled results, not an exact count. Use `has_next` for reliable pagination.
Processing time in milliseconds
### Result Object
OMOP concept\_id
Standard concept name
Semantic similarity score (0.0-1.0). Higher = more similar.
The text that matched (concept name or synonym)
OMOP domain (e.g., Condition, Drug, Procedure)
Source vocabulary (e.g., SNOMED, ICD10CM, RxNorm)
Concept classification within the vocabulary
Standard concept flag: S (Standard), C (Classification), or null
Original code from the source vocabulary
### Pagination Object (in meta)
Current page number
Number of results per page
Approximate total number of matching items (lower bound). Use `has_next` for
reliable pagination.
Approximate total number of pages. Use `has_next` to determine if more pages
exist.
**Reliable indicator** of whether more results are available. Use this for
pagination loops.
**Reliable indicator** of whether previous pages exist.
**Pagination Note:** For performance reasons, `total_items` and `total_pages`
are approximate values based on sampled results. Always use `has_next` to
determine if more pages exist rather than comparing `page` to `total_pages`.
## How It Works
1. **Query Embedding**: Your query is converted to a 768-dimensional vector using neural embeddings
2. **Vector Search**: The query vector is compared against pre-computed concept embeddings
3. **Ranking**: Results are ranked by cosine similarity score
4. **Filtering**: Optional filters (vocabulary, domain) are applied
5. **Deduplication**: Results are deduplicated by concept\_id (keeping highest score)
## Similarity Score Interpretation
| Score Range | Interpretation |
| ----------- | --------------------------------------- |
| 0.9 - 1.0 | Excellent match, high confidence |
| 0.7 - 0.9 | Good match, likely relevant |
| 0.5 - 0.7 | Moderate match, review recommended |
| 0.3 - 0.5 | Weak match, may be tangentially related |
| \< 0.3 | Poor match, likely not relevant |
## Performance
* **Latency**: \~15-50ms typical
* **Throughput**: \~100 requests/second
## Use Cases
### 1. Natural Language Processing
Process patient-reported symptoms and clinical notes:
```python theme={null}
# Patient says: "I've been having trouble breathing"
results = client.semantic_search(query="trouble breathing")
# Returns: Dyspnea, Shortness of breath, Respiratory distress
```
### 2. Clinical Decision Support
Map clinical observations to standard codes:
```python theme={null}
# Nurse notes: "pt appears confused and agitated"
results = client.semantic_search(
query="confused and agitated",
domain_ids="Condition"
)
# Returns: Delirium, Acute confusional state, Agitation
```
### 3. Code Mapping Assistance
Find mappings for non-standard terminology:
```python theme={null}
# Legacy code description: "DM2 uncontrolled"
results = client.semantic_search(
query="DM2 uncontrolled",
vocabulary_ids="SNOMED",
standard_concept="S"
)
# Returns: Type 2 diabetes mellitus without complications
```
### 4. Paginating Through Results
Iterate through large result sets:
```python theme={null}
page = 1
while True:
results = client.semantic_search(
query="diabetes",
page=page,
page_size=50
)
# Process results
for concept in results["data"]["results"]:
print(concept["concept_name"])
# Check if there are more pages
if not results["meta"]["pagination"]["has_next"]:
break
page += 1
```
## Related Endpoints
* [Basic Concept Search](/api-reference/search/basic-search) - Keyword-based search
* [Similar Concepts](/api-reference/search/search-similar) - Find concepts similar to a given concept (supports `algorithm: "semantic"` to use the same embedding model)
* [Autocomplete](/api-reference/search/search-autocomplete) - Type-ahead suggestions
**Tip:** The `/search/similar` endpoint also supports semantic search via the
`algorithm: "semantic"` parameter. Use that endpoint when you need additional
features like starting from a `concept_id` or getting detailed similarity
explanations.
# Get Vocabulary Details
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary
GET /vocabularies/{vocabularyId}
Get detailed information about a specific vocabulary
## Overview
Retrieve details about a specific vocabulary including its name, reference, version, and concept identifier.
## Path Parameters
The vocabulary identifier (e.g., "SNOMED", "ICD10CM", "LOINC", "RxNorm")
## Query Parameters
Specific vocabulary release version (e.g., "2025.1")
## Response
Indicates whether the request was successful
Vocabulary details
Vocabulary identifier (e.g., "SNOMED", "ICD10CM")
Human-readable vocabulary name
Reference or source organization for the vocabulary
Version identifier for the vocabulary
Concept ID representing this vocabulary in the concept table
Response metadata
Unique identifier for request tracing
ISO 8601 timestamp
Vocabulary release version used
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/vocabularies/SNOMED",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
vocabulary = response.json()
print(f"Name: {vocabulary['data']['vocabulary_name']}")
print(f"Version: {vocabulary['data']['vocabulary_version']}")
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/vocabularies/SNOMED', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const vocabulary = await response.json();
```
```json theme={null}
{
"success": true,
"data": {
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"vocabulary_reference": "SNOMED International",
"vocabulary_version": "2024-07-01",
"vocabulary_concept_id": 45756746
},
"meta": {
"request_id": "req_vocab_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.1"
}
}
```
## Notes
* Use the vocabulary ID returned from the [List Vocabularies](/api-reference/vocabularies/list-vocabularies) endpoint
* For detailed statistics, use the [Get Vocabulary Stats](/api-reference/vocabularies/get-vocabulary-stats) endpoint
* For domain information, use the [Get Vocabulary Domains](/api-reference/vocabularies/get-vocabulary-domains) endpoint
# Get Vocabulary Concept Classes
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary-concept-classes
GET https://api.omophub.com/v1/vocabularies/concept-classes
Retrieve all concept classes from the vocabulary system
## Overview
This endpoint returns all OMOP concept classes, providing insights into the classification structure of medical concepts.
## Query Parameters
Specific vocabulary release version (e.g., "2025.1")
## Response
Indicates whether the request was successful
Array of concept classes
Unique identifier for the concept class
Human-readable name of the concept class
The concept ID representing this concept class
Response metadata
Unique identifier for request tracing
ISO 8601 timestamp
Vocabulary release version used
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/vocabularies/concept-classes" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/vocabularies/concept-classes",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
```
```json theme={null}
{
"success": true,
"data": [
{
"concept_class_id": "Clinical Finding",
"concept_class_name": "Clinical Finding",
"concept_class_concept_id": 404684003
},
{
"concept_class_id": "Procedure",
"concept_class_name": "Procedure",
"concept_class_concept_id": 71388002
},
{
"concept_class_id": "Substance",
"concept_class_name": "Substance",
"concept_class_concept_id": 105590001
}
],
"meta": {
"request_id": "req_abc123",
"timestamp": "2024-12-22T10:30:00Z",
"vocab_release": "2025.1"
}
}
```
## Notes
* Returns all concept classes from the vocabulary database
* Results are ordered alphabetically by concept class name
* The `concept_class_concept_id` field contains the concept ID that represents this concept class
# Get Vocabulary Concepts
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary-concepts
GET /v1/vocabularies/{vocabularyId}/concepts
Get concepts within a specific vocabulary
Retrieve concepts that belong to a specific vocabulary with optional filtering and pagination.
## Path Parameters
The vocabulary identifier (e.g., "SNOMED", "ICD10CM", "LOINC", "RxNorm").
## Query Parameters
Search term to filter concepts by name or code.
Page number for pagination (1-based, max 1000). For large vocabularies, use the `search` parameter to filter results instead of deep pagination.
Number of concepts per page (max 1000).
Filter by standard concept status. Options: `S` (standard), `C` (classification), `all`.
Include invalid or deprecated concepts.
Include concept relationships in response.
Include concept synonyms in response.
Sort field. Options: `name`, `concept_id`, `concept_code`.
Sort order. Options: `asc`, `desc`.
Specific vocabulary release version to query
**Example:** `2025.1`
## Response
Indicates if the request was successful.
Response data container.
Array of concept objects.
Unique concept identifier.
Standard name of the concept.
Original code from the vocabulary.
Vocabulary containing this concept.
Domain of the concept.
Concept class identifier.
Standard concept designation ('S', 'C', or null).
Reason for invalidation if applicable.
Date when concept became valid.
Date when concept becomes invalid.
Response metadata.
Unique identifier for this request.
Vocabulary release version used.
Response timestamp (ISO format).
Current page number.Items per page.Total concepts available.Total number of pages.Whether next page exists.Whether previous page exists.
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/concepts?page_size=10" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/vocabularies/SNOMED/concepts",
params={"page_size": 10, "standard_concept": "S"},
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
for concept in data["data"]["concepts"]:
print(f"{concept['concept_name']}: {concept['concept_id']}")
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.omophub.com/v1/vocabularies/SNOMED/concepts?page_size=10&standard_concept=S',
{
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
}
);
const data = await response.json();
console.log(`Found ${data.meta.pagination.total_items} concepts`);
```
```bash cURL (with search) theme={null}
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/concepts?search=diabetes&standard_concept=S&page_size=20" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json theme={null}
{
"success": true,
"data": {
"concepts": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"invalid_reason": null,
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31"
},
{
"concept_id": 201254,
"concept_name": "Hypertensive disorder",
"concept_code": "38341003",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"invalid_reason": null,
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31"
}
]
},
"meta": {
"request_id": "req_abc123",
"vocab_release": "2025.1",
"timestamp": "2025-01-05T10:00:00Z",
"pagination": {
"page": 1,
"page_size": 10,
"total_items": 125847,
"total_pages": 12585,
"has_next": true,
"has_previous": false
}
}
}
```
## Usage Examples
### Basic List
Get concepts from a vocabulary:
```bash theme={null}
GET /v1/vocabularies/SNOMED/concepts?page_size=50
```
### Search Within Vocabulary
Search for concepts by name:
```bash theme={null}
GET /v1/vocabularies/SNOMED/concepts?search=diabetes&standard_concept=S
```
### With Sorting
Sort by concept name descending:
```bash theme={null}
GET /v1/vocabularies/RxNorm/concepts?sort_by=name&sort_order=desc
```
### Include Additional Data
Include relationships and synonyms:
```bash theme={null}
GET /v1/vocabularies/SNOMED/concepts?include_relationships=true&include_synonyms=true
```
## Related Endpoints
* [List Vocabularies](/api-reference/vocabularies/list-vocabularies) - Get all vocabularies
* [Get Vocabulary](/api-reference/vocabularies/get-vocabulary) - Get vocabulary details
* [Get Concept](/api-reference/concepts/get-concept) - Get individual concept details
# Get Vocabulary Domain Statistics
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary-domain-stats
GET https://api.omophub.com/v1/vocabularies/{vocabularyId}/stats/domains/{domainId}
Get statistics for a specific domain within a vocabulary
## Overview
This endpoint provides statistics about concepts within a specific domain of a particular vocabulary, including concept counts and class distribution.
## Path Parameters
The vocabulary identifier (e.g., "SNOMED", "ICD10CM", "LOINC")
The domain identifier (e.g., "Condition", "Drug", "Procedure", "Measurement")
## Query Parameters
Specific vocabulary release version (e.g., "2025.1")
## Response
Indicates whether the request was successful
Domain statistics data
Vocabulary identifier
Human-readable vocabulary name
Domain identifier
Human-readable domain name
Total number of concepts in this vocabulary-domain combination
Number of standard concepts (standard\_concept = 'S')
Number of classification concepts (standard\_concept = 'C')
Number of invalid/deprecated concepts
Number of currently active concepts
Top concept classes in this domain (up to 10)
Concept class identifier
Concept class name
Number of concepts in this class
ISO 8601 timestamp of when statistics were calculated
Response metadata
Unique identifier for request tracing
ISO 8601 timestamp
Vocabulary release version used
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/stats/domains/Condition" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/vocabularies/SNOMED/stats/domains/Condition",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
print(f"Total concepts: {data['data']['total_concepts']}")
print(f"Standard concepts: {data['data']['standard_concepts']}")
```
```json theme={null}
{
"success": true,
"data": {
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"domain_name": "Condition",
"total_concepts": 125847,
"standard_concepts": 118943,
"classification_concepts": 4521,
"invalid_concepts": 2391,
"active_concepts": 123456,
"concept_classes": [
{
"concept_class_id": "Clinical Finding",
"concept_class_name": "Clinical Finding",
"concept_count": 87234
},
{
"concept_class_id": "Disorder",
"concept_class_name": "Disorder",
"concept_count": 23456
},
{
"concept_class_id": "Finding",
"concept_class_name": "Finding",
"concept_count": 15157
}
],
"last_updated": "2024-12-22T10:32:00Z"
},
"meta": {
"request_id": "req_abc123",
"timestamp": "2024-12-22T10:32:00Z",
"vocab_release": "2025.1"
}
}
```
## Notes
* Returns statistics for a specific vocabulary-domain combination
* The `concept_classes` array shows the top 10 concept classes by count
* Use this endpoint to understand concept distribution within a domain
# Get Vocabulary Domains
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary-domains
GET /v1/vocabularies/domains
Retrieve all standard OHDSI domains available in the vocabulary system.
Retrieve the list of standard OHDSI domains. Domains categorize concepts by their clinical or administrative function (e.g., Condition, Drug, Procedure).
## Query Parameters
Specific vocabulary release version to use. If not provided, uses the latest available release.
**Format**: `YYYY.Q` (e.g., `2025.1`)
## Response
Indicates if the request was successful.
Array of domain objects.
Unique domain identifier (e.g., "Condition", "Drug", "Procedure").
Human-readable domain name.
Brief description of the domain.
Unique identifier for the request.
Request timestamp.
Vocabulary release version used.
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/vocabularies/domains" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/vocabularies/domains",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
for domain in data["data"]["domains"]:
print(f"{domain['domain_id']}: {domain['domain_name']}")
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/vocabularies/domains', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data.data.domains);
```
```json Response theme={null}
{
"success": true,
"data": {
"domains": [
{
"domain_id": "Condition",
"domain_name": "Condition",
"description": "OHDSI standard domain: Condition"
},
{
"domain_id": "Drug",
"domain_name": "Drug",
"description": "OHDSI standard domain: Drug"
},
{
"domain_id": "Procedure",
"domain_name": "Procedure",
"description": "OHDSI standard domain: Procedure"
},
{
"domain_id": "Measurement",
"domain_name": "Measurement",
"description": "OHDSI standard domain: Measurement"
},
{
"domain_id": "Observation",
"domain_name": "Observation",
"description": "OHDSI standard domain: Observation"
},
{
"domain_id": "Device",
"domain_name": "Device",
"description": "OHDSI standard domain: Device"
},
{
"domain_id": "Specimen",
"domain_name": "Specimen",
"description": "OHDSI standard domain: Specimen"
},
{
"domain_id": "Visit",
"domain_name": "Visit",
"description": "OHDSI standard domain: Visit"
}
]
},
"meta": {
"request_id": "req_abc123def456",
"timestamp": "2025-01-05T10:30:00Z",
"vocab_release": "2025.1"
}
}
```
## Available Domains
The endpoint returns all standard OHDSI domains:
### Clinical Domains
* **Condition** - Diseases, disorders, and clinical findings
* **Procedure** - Medical procedures and interventions
* **Observation** - Clinical observations and assessments
* **Measurement** - Quantitative test results and vital signs
* **Drug** - Medications and pharmaceutical products
* **Device** - Medical devices and equipment
* **Specimen** - Biological specimens and samples
### Administrative Domains
* **Visit** - Healthcare encounters and visit types
* **Provider** - Healthcare provider roles and specialties
* **Location** - Geographic locations
* **Care Site** - Healthcare facilities
* **Payer** - Insurance and payment-related concepts
* **Plan** - Insurance plans
* **Sponsor** - Study sponsors
* **Cost** - Healthcare cost information
* **Revenue** - Revenue codes
### Other Domains
* **Episode** - Clinical episodes
* **Regimen** - Treatment regimens
* **Metadata** - Vocabulary metadata
* **Type Concept** - Type concepts
* **Relationship** - Relationship types
* **Gender** - Gender concepts
* **Race** - Race concepts
* **Ethnicity** - Ethnicity concepts
## Related Endpoints
* [Get Domains](/api-reference/domains/get-domains) - Detailed domain information with statistics
* [Get Domain Concepts](/api-reference/domains/get-domain-concepts) - Concepts within a specific domain
* [List Vocabularies](/api-reference/vocabularies/list-vocabularies) - Available vocabularies
# Get Vocabulary Releases
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary-releases
GET /v1/vocabularies/releases
Retrieve a list of all available OHDSI vocabulary dataset releases.
Retrieve information about all OHDSI vocabulary dataset releases available in the system. Each release represents a complete vocabulary dataset version (e.g., "2025.1") containing all OMOP vocabularies at a specific point in time.
## Query Parameters
Include inactive releases in the response. By default, only active releases are returned.
## Response
Indicates if the request was successful.
Array of vocabulary release objects.
Unique identifier for the release.
Release identifier (e.g., "2025.1", "2024.2").
Version number.
ATHENA vocabulary version this release is based on.
ISO 8601 formatted release date.
Release status (e.g., "ready", "pending", "deprecated").
Whether this is the default release used when no version is specified.
Whether this release is currently active and available for use.
Total number of concepts across all vocabularies in this release.
Number of vocabularies included in this release.
Total number of concept relationships in this release.
Unique identifier for the request.
Request timestamp.
Current default vocabulary release.
Pagination information.
Current page number.
Number of items returned.
Total number of releases.
Total number of pages.
Whether there are more pages.
Whether there are previous pages.
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/vocabularies/releases" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```bash cURL (with options) theme={null}
curl -X GET "https://api.omophub.com/v1/vocabularies/releases?include_inactive=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/vocabularies/releases",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
for release in data["data"]:
print(f"{release['vocab_release']}: {release['total_concepts']} concepts")
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/vocabularies/releases', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data.data);
```
```json Response theme={null}
{
"success": true,
"data": [
{
"id": 1,
"vocab_release": "2025.1",
"version_number": "1",
"athena_version": "v5.3.1",
"release_date": "2025-01-01T00:00:00Z",
"status": "ready",
"is_default": true,
"is_active": true,
"total_concepts": 8547321,
"total_vocabularies": 87,
"total_relationships": 45678234
},
{
"id": 2,
"vocab_release": "2024.2",
"version_number": "4",
"athena_version": "v5.3.0",
"release_date": "2024-10-01T00:00:00Z",
"status": "ready",
"is_default": false,
"is_active": true,
"total_concepts": 8234567,
"total_vocabularies": 85,
"total_relationships": 44567123
}
],
"meta": {
"request_id": "req_abc123def456",
"timestamp": "2025-01-05T10:30:00Z",
"vocab_release": "2025.1",
"pagination": {
"page": 1,
"page_size": 2,
"total_items": 2,
"total_pages": 1,
"has_next": false,
"has_previous": false
}
}
}
```
## Usage Examples
### Get Active Releases
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/vocabularies/releases" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Include Inactive Releases
```bash theme={null}
curl -X GET "https://api.omophub.com/v1/vocabularies/releases?include_inactive=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Notes
* Each release represents a complete OHDSI vocabulary dataset at a point in time
* The `is_default` release is used when no specific version is requested
* Use the `vocab_release` value (e.g., "2025.1") in the `vocab_release` query parameter of other endpoints to query specific versions
## Related Endpoints
* [List Vocabularies](/api-reference/vocabularies/list-vocabularies) - Get all vocabularies in a release
# Get Vocabulary Statistics
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary-stats
GET /v1/vocabularies/{vocabularyId}/stats
Retrieve comprehensive statistics for a specific vocabulary including concept counts and domain distribution.
Retrieve detailed statistical information about a vocabulary, including the total number of concepts, breakdown by standard/classification status, and domain distribution.
## Path Parameters
The unique identifier for the vocabulary (e.g., "SNOMED", "ICD10CM", "LOINC").
## Query Parameters
Specific vocabulary release version to query
**Example:** `2025.1`
## Response
Indicates if the request was successful.
The vocabulary identifier.
Full vocabulary name.
Total number of concepts in the vocabulary.
Number of standard concepts (standard\_concept = 'S').
Number of classification concepts (standard\_concept = 'C').
Number of invalid/deprecated concepts.
Number of currently active concepts.
Earliest valid start date among concepts.
Latest valid end date among concepts.
Timestamp of when statistics were last updated.
Array of domain statistics.
Domain identifier.
Domain name.
Number of concepts in this domain.
Unique identifier for the request.
Request timestamp.
Vocabulary release version used.
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/stats" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/vocabularies/SNOMED/stats",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
print(f"Total concepts: {data['data']['total_concepts']}")
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/vocabularies/SNOMED/stats', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const data = await response.json();
console.log(`Total concepts: ${data.data.total_concepts}`);
```
```json Response theme={null}
{
"success": true,
"data": {
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"total_concepts": 354652,
"standard_concepts": 298456,
"classification_concepts": 56196,
"invalid_concepts": 12543,
"active_concepts": 342109,
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31",
"last_updated": "2025-01-05T10:30:00Z",
"domain_distribution": [
{
"domain_id": "Condition",
"domain_name": "Condition",
"concept_count": 112543
},
{
"domain_id": "Procedure",
"domain_name": "Procedure",
"concept_count": 87321
},
{
"domain_id": "Observation",
"domain_name": "Observation",
"concept_count": 65432
}
]
},
"meta": {
"request_id": "req_abc123def456",
"timestamp": "2025-01-05T10:30:00Z",
"vocab_release": "2025.1"
}
}
```
```json Error Response theme={null}
{
"success": false,
"error": {
"code": "VOCABULARY_NOT_FOUND",
"message": "Vocabulary 'INVALID_VOCAB' not found"
},
"meta": {
"request_id": "req_error123",
"timestamp": "2025-01-05T10:30:00Z"
}
}
```
## Usage Examples
### Get Statistics for SNOMED
```bash theme={null}
GET /v1/vocabularies/SNOMED/stats
```
### Get Statistics for ICD-10-CM
```bash theme={null}
GET /v1/vocabularies/ICD10CM/stats
```
### Get Statistics for a Specific Release
```bash theme={null}
GET /v1/vocabularies/SNOMED/stats?vocab_release=2025.1
```
## Related Endpoints
* [List Vocabularies](/api-reference/vocabularies/list-vocabularies) - Get all available vocabularies
* [Get Vocabulary Details](/api-reference/vocabularies/get-vocabulary) - Get detailed vocabulary information
* [Get Vocabulary Concepts](/api-reference/vocabularies/get-vocabulary-concepts) - Get concepts within a vocabulary
# List Vocabularies
Source: https://docs.omophub.com/api-reference/vocabularies/list-vocabularies
GET /v1/vocabularies
Get a paginated list of all available medical vocabularies
This endpoint returns a paginated list of all available medical vocabularies in the system, including SNOMED CT, ICD-10-CM, LOINC, RxNorm, and others.
## Query Parameters
Page number for pagination (1-based).
Number of vocabularies to return per page (max 1000).
Include concept count statistics for each vocabulary.
Include inactive or deprecated vocabularies in the results.
Sort field. Options: `name`, `priority`, `updated`
Sort order. Options: `asc`, `desc`
Specific vocabulary release version (e.g., "2025.1"). Defaults to the latest available release.
## Response
Indicates if the request was successful.
Response data container.
Array of vocabulary objects.
Unique identifier for the vocabulary (e.g., "SNOMED", "ICD10CM").
Full name of the vocabulary.
Reference or source organization for the vocabulary.
Version identifier of the vocabulary.
OMOP concept ID representing this vocabulary.
Response metadata and pagination information.
Unique identifier for this request.
Vocabulary release version used.
Response timestamp (ISO format).
Current page number.Items per page.Total vocabularies available.Total number of pages.Whether next page exists.Whether previous page exists.
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/vocabularies" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/vocabularies",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
for vocab in data["data"]["vocabularies"]:
print(f"{vocab['vocabulary_id']}: {vocab['vocabulary_name']}")
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/vocabularies', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const data = await response.json();
data.data.vocabularies.forEach(vocab => {
console.log(`${vocab.vocabulary_id}: ${vocab.vocabulary_name}`);
});
```
```bash cURL (with options) theme={null}
curl -X GET "https://api.omophub.com/v1/vocabularies?include_stats=true&sort_by=name&sort_order=asc&page_size=50" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (with options) theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/vocabularies",
params={
"include_stats": True,
"sort_by": "name",
"sort_order": "asc",
"page_size": 50
},
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
```
```json theme={null}
{
"success": true,
"data": {
"vocabularies": [
{
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"vocabulary_reference": "SNOMED CT",
"vocabulary_version": "US Edition 20240301",
"vocabulary_concept_id": 45756746
},
{
"vocabulary_id": "ICD10CM",
"vocabulary_name": "International Classification of Diseases, Tenth Revision, Clinical Modification",
"vocabulary_reference": "CMS",
"vocabulary_version": "2024",
"vocabulary_concept_id": 45756681
},
{
"vocabulary_id": "LOINC",
"vocabulary_name": "Logical Observation Identifiers Names and Codes",
"vocabulary_reference": "Regenstrief Institute",
"vocabulary_version": "2.76",
"vocabulary_concept_id": 45756747
},
{
"vocabulary_id": "RxNorm",
"vocabulary_name": "RxNorm",
"vocabulary_reference": "National Library of Medicine",
"vocabulary_version": "2024-03-04",
"vocabulary_concept_id": 45756748
}
]
},
"meta": {
"request_id": "req_abc123",
"vocab_release": "2025.1",
"timestamp": "2025-01-05T10:00:00Z",
"pagination": {
"page": 1,
"page_size": 20,
"total_items": 25,
"total_pages": 2,
"has_next": true,
"has_previous": false
}
}
}
```
## Usage Examples
### Basic List
Get all vocabularies with default pagination:
```bash theme={null}
GET /v1/vocabularies
```
### With Statistics
Include concept counts for each vocabulary:
```bash theme={null}
GET /v1/vocabularies?include_stats=true
```
### Sorted by Name
Get vocabularies sorted alphabetically:
```bash theme={null}
GET /v1/vocabularies?sort_by=name&sort_order=asc
```
### Include Inactive
Include deprecated vocabularies:
```bash theme={null}
GET /v1/vocabularies?include_inactive=true
```
## Related Endpoints
* [Get Vocabulary](/api-reference/vocabularies/get-vocabulary) - Get details for a specific vocabulary
* [Search Vocabularies](/api-reference/vocabularies/search-vocabularies) - Search vocabularies by name
* [Get Vocabulary Concepts](/api-reference/vocabularies/get-vocabulary-concepts) - Get concepts within a vocabulary
# Search Vocabularies
Source: https://docs.omophub.com/api-reference/vocabularies/search-vocabularies
GET /v1/vocabularies/search
Search across vocabulary names and descriptions
Search for vocabularies by name or description. Returns matching vocabularies with relevance scoring.
## Query Parameters
Search query string. Searches across vocabulary names and descriptions.
Page number for pagination (1-based).
Number of results per page (max 100).
Include inactive vocabularies in search results.
Specific vocabulary release version to query
**Example:** `2025.1`
## Response
Indicates if the request was successful.
Array of matching vocabulary objects.
Unique identifier for the vocabulary.
Full name of the vocabulary.
Reference or source for the vocabulary.
Version of the vocabulary.
OMOP concept ID for the vocabulary.
Response metadata.
Unique identifier for this request.
Vocabulary release version used.
Response timestamp (ISO format).
Search query used.Total matching vocabularies.Filters that were applied.Current page number.Items per page.Total items available.Total number of pages.Whether next page exists.Whether previous page exists.
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/vocabularies/search?query=SNOMED" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.omophub.com/v1/vocabularies/search",
params={"query": "SNOMED"},
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
for vocab in data["data"]:
print(f"{vocab['vocabulary_id']}: {vocab['vocabulary_name']}")
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.omophub.com/v1/vocabularies/search?query=SNOMED',
{
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
}
);
const data = await response.json();
data.data.forEach(vocab => {
console.log(`${vocab.vocabulary_id}: ${vocab.vocabulary_name}`);
});
```
```bash cURL (with options) theme={null}
curl -X GET "https://api.omophub.com/v1/vocabularies/search?query=drug&include_inactive=true&page_size=50" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json theme={null}
{
"success": true,
"data": [
{
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"vocabulary_reference": "SNOMED International",
"vocabulary_version": "US Edition 20240301",
"vocabulary_concept_id": 45756746
},
{
"vocabulary_id": "Nebraska Lexicon",
"vocabulary_name": "Nebraska Lexicon",
"vocabulary_reference": "University of Nebraska",
"vocabulary_version": "2024-01",
"vocabulary_concept_id": 32675
}
],
"meta": {
"request_id": "req_abc123",
"vocab_release": "2025.1",
"timestamp": "2025-01-05T10:00:00Z",
"search": {
"query": "SNOMED",
"total_results": 2,
"filters_applied": {
"search_type": "full_text_search",
"include_inactive": false
}
},
"pagination": {
"page": 1,
"page_size": 20,
"total_items": 2,
"total_pages": 1,
"has_next": false,
"has_previous": false
}
}
}
```
## Usage Examples
### Basic Search
Search for vocabularies by name:
```bash theme={null}
GET /v1/vocabularies/search?query=SNOMED
```
### With Pagination
Search with custom page size:
```bash theme={null}
GET /v1/vocabularies/search?query=ICD&page=1&page_size=50
```
### Include Inactive
Include deprecated vocabularies:
```bash theme={null}
GET /v1/vocabularies/search?query=classification&include_inactive=true
```
## Related Endpoints
* [List Vocabularies](/api-reference/vocabularies/list-vocabularies) - List all vocabularies
* [Get Vocabulary](/api-reference/vocabularies/get-vocabulary) - Get specific vocabulary details
# Overview
Source: https://docs.omophub.com/authentication/overview
Understanding authentication options for API access
## API Key Authentication
API keys are used for all programmatic access to the OMOPHub API. They provide:
* **Long-lived access**: No expiration for continuous integration
* **Granular permissions**: Control access to specific resources
* **Usage tracking**: Monitor API consumption per key
* **Multiple keys**: Create keys for different environments
### API Key Types
Individual developer keys
* Tied to your user account
* Inherit your account permissions
* Usage counts toward personal quota
* Can be revoked anytime
Shared keys for team collaboration (Coming Soon)
* Associated with team/organization
* Shared usage limits
* Role-based access control
* Audit logging per team member
## Authentication Flow
### API Authentication Flow
```mermaid theme={null}
sequenceDiagram
participant Client
participant API
participant Auth Service
Client->>API: Request with API key
API->>Auth Service: Validate API key
Auth Service->>API: Return permissions
API->>Client: Process request
```
### API Key Security
1. **Environment Variables**: Store keys in environment variables
```bash theme={null}
export OMOPHUB_API_KEY="oh_xxxxxxxxx"
```
2. **Key Rotation**: Regularly rotate keys (every 90 days recommended)
3. **Separate Environments**: Use different keys for dev/staging/production
4. **Monitor Usage**: Check API key usage regularly for anomalies
## Authentication Headers
### API Key Authentication
Include your API key in the Authorization header:
```bash cURL theme={null}
curl -H "Authorization: Bearer oh_xxxxxxxxx" \
https://api.omophub.com/v1/vocabularies
```
```python Python theme={null}
client = OMOPHubClient(api_key="oh_xxxxxxxxx")
```
```r R theme={null}
client <- omophub_client(api_key = "oh_xxxxxxxxx")
```
## Troubleshooting
* Check the key hasn't been revoked
* Verify you're using the correct environment
* Confirm proper Authorization header format
* Check X-RateLimit headers in responses
* Implement exponential backoff
* Consider upgrading your plan
* Use caching to reduce requests
## Next Steps
Create keys for API access
# EHR Integration
Source: https://docs.omophub.com/guides/integration/ehr-integration
Making data invisible and useful. Resolve EHR-native codes to standardized OMOP concepts for real-time clinical decision support, lab normalization, and bidirectional EHR integration
## 1. The "Tab Fatigue" Problem
A doctor needs to check if a new prescription will interact with the patient's current medications. The drug interaction database wants RxNorm codes. The EHR has the medications stored with local formulary codes. The patient's research record uses OMOP concept IDs. Three systems, three vocabularies, zero interoperability. The doctor opens another tab.
This is "Tab Fatigue" or, as clinicians call it, "Death by a Thousand Clicks." The insights exist: drug interaction alerts, care gap notifications, clinical trial eligibility signals. But they're trapped in separate systems that don't speak the same vocabulary language. Getting data *out* of an EHR for research is hard. Getting standardized insights *back into* the EHR at the point of care is the real "Last Mile" problem.
The vocabulary translation layer is where **OMOPHub** fits. When an EHR-integrated application (like a SMART on FHIR app) needs to convert local or FHIR-native codes into standardized OMOP concepts, OMOPHub provides the lookup: search for a code, get back the OMOP concept ID, access its hierarchy and relationships. It's one component in a larger integration pipeline, not the middleware itself, but the vocabulary resolution step that makes the rest possible.
The full FHIR-to-OMOP pipeline looks like this:
1. **FHIR client** reads patient data from the EHR (medications, conditions, labs)
2. **Your application code** parses the FHIR resources and extracts coded elements
3. **OMOPHub** resolves those codes to standardized OMOP concept IDs
4. **Your CDS / analytics engine** runs logic against the standardized concepts
OMOPHub handles step 3. That's a narrow but critical role: without it, every integration project maintains its own vocabulary mapping tables, which drift out of date and diverge across installations.
## 2. The Core Concept: Code Resolution at the Point of Care
EHR data arrives in many vocabulary flavors. A medication might be coded in RxNorm, NDC, a local formulary code, or just free text. A diagnosis might be ICD-10-CM in billing, SNOMED in the problem list, or a local shorthand in a quick note. A lab result could use LOINC, a vendor-specific code, or an institution-specific abbreviation.
For any clinical decision support to work across EHR installations, these codes need to resolve to a common vocabulary. OMOP provides that common layer - and OMOPHub provides programmatic access to it.
The resolution patterns:
* **Known standard code** (e.g., RxNorm "1049502") → search OMOPHub by vocabulary + code → get OMOP concept ID
* **Local display name** (e.g., "Creatinine, Serum") → search OMOPHub with semantic search → get best LOINC match
* **Free text** (e.g., "Chest pain") → search OMOPHub semantically → get candidate SNOMED concepts
* **Proprietary code** (e.g., "MED\_LOCAL\_4827") → OMOPHub can't help directly; requires a pre-built local mapping table
Once you have OMOP concept IDs, you can reuse the same concept sets, phenotypes, and clinical logic across every EHR installation: no per-site mapping tables needed.
## 3. Use Case A: Real-Time Clinical Decision Support
A SMART on FHIR app needs to check a newly prescribed medication against the patient's current conditions and medications: all standardized to OMOP concepts.
**The Scenario:** A doctor prescribes Amoxicillin. The app extracts the medication from the FHIR `MedicationRequest` resource, resolves it to an OMOP concept via OMOPHub, and feeds it to a CDS engine that checks for interactions against the patient's OMOP-standardized history.
```bash theme={null}
pip install omophub
```
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
# Sample FHIR MedicationRequest (simplified)
# In production, this comes from the EHR via FHIR API
fhir_medication_request = {
"resourceType": "MedicationRequest",
"id": "medrx001",
"medicationCodeableConcept": {
"coding": [
{
"system": "http://www.nlm.nih.gov/research/umls/rxnorm",
"code": "1049502",
"display": "Amoxicillin 500 MG Oral Capsule",
}
],
"text": "Amoxicillin 500mg capsule",
},
"subject": {"reference": "Patient/example", "display": "Peter James Chalmers"},
}
print("Processing FHIR MedicationRequest for CDS...\n")
# --- Step 1: Parse the FHIR resource (this is YOUR code, not OMOPHub) ---
med_code = None
med_vocab = None
med_text = None
if "medicationCodeableConcept" in fhir_medication_request:
concept = fhir_medication_request["medicationCodeableConcept"]
for coding in concept.get("coding", []):
if coding.get("system") == "http://www.nlm.nih.gov/research/umls/rxnorm":
med_code = coding.get("code")
med_vocab = "RxNorm"
break
med_text = concept.get("text")
# --- Step 2: Resolve to OMOP concept via OMOPHub ---
omop_drug_id = None
if med_code and med_vocab:
print(f" FHIR code: {med_code} ({med_vocab})")
try:
# Search for the RxNorm code directly
results = client.search.basic(
med_code,
vocabulary_ids=[med_vocab],
page_size=1,
)
candidates = results.get("concepts", []) if results else []
if candidates:
drug_concept = candidates[0]
omop_drug_id = drug_concept["concept_id"]
print(f" -> OMOP: {drug_concept.get('concept_name')} (ID: {omop_drug_id})")
else:
print(f" -> No OMOP match for {med_vocab} code {med_code}")
except omophub.APIError as e:
print(f" -> API error: {e.message}")
elif med_text:
# Fallback: search by display text if no standard code available
print(f" FHIR text (no standard code): '{med_text}'")
try:
results = client.search.semantic(med_text, vocabulary_ids=["RxNorm"], domain_ids=["Drug"], page_size=3)
candidates = (results.get("results", results.get("concepts", [])) if results else [])
if candidates:
drug_concept = candidates[0]
omop_drug_id = drug_concept["concept_id"]
print(f" -> OMOP (semantic): {drug_concept.get('concept_name')} (ID: {omop_drug_id})")
else:
print(f" -> No RxNorm match found via semantic search")
except omophub.APIError as e:
print(f" -> API error: {e.message}")
else:
print(" No medication code or text found in FHIR resource.")
# --- Step 3: Use the OMOP concept ID for CDS ---
if omop_drug_id:
print(f"\n Ready for CDS: OMOP Drug Concept ID {omop_drug_id}")
print(f" -> Pass to interaction checker, allergy checker, or trial matcher")
print(f" -> Compare against patient's OMOP-standardized medication/condition history")
# Optionally: get the drug ingredient via hierarchy for ingredient-level checks
try:
ancestors = client.hierarchy.ancestors(omop_drug_id, max_levels=3, relationship_types=["Is a"])
anc_list = (
ancestors if isinstance(ancestors, list)
else ancestors.get("concepts", [])
) if ancestors else []
ingredients = [a for a in anc_list if a.get("concept_class_id") == "Ingredient"]
if ingredients:
print(f" -> Ingredient: {ingredients[0].get('concept_name')} (ID: {ingredients[0]['concept_id']})")
except omophub.APIError:
pass
else:
print("\n Could not resolve medication - CDS check cannot proceed.")
```
**The Key Insight:** The FHIR parsing is your code. The CDS logic is your engine. OMOPHub's role is the vocabulary resolution in between - turning the RxNorm code from the FHIR resource into an OMOP concept ID that your CDS engine can work with. That's a narrow role, but without it, every CDS deployment needs custom mapping tables per EHR installation. With it, the same concept IDs and clinical logic work everywhere.
## 4. Use Case B: Normalizing Local Lab Codes for a Sidecar App
EHR-embedded "sidecar" apps (SMART on FHIR apps for specialty workflows) often need to display standardized trends from lab data that arrives in 50 different local code variants.
**The Scenario:** A CKD management app needs to show creatinine trends. The EHR uses "Cr\_Serum," "Creat\_Blood," and "Kidney\_Fx\_Creat" - all meaning the same thing. The app needs a single LOINC concept to unify them.
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
# Lab results from EHR (via FHIR Observation resources, parsed by your code)
local_labs = [
{"local_code": "Cr_Serum", "display": "Creatinine, Serum", "value": 1.2, "unit": "mg/dL"},
{"local_code": "Creat_Blood", "display": "Blood Creatinine", "value": 106, "unit": "umol/L"},
{"local_code": "Kidney_Fx_Creat", "display": "Creatinine Level", "value": 0.9, "unit": "mg/dL"},
]
print("Normalizing local lab codes for sidecar app...\n")
standardized = []
for lab in local_labs:
display = lab["display"]
print(f" '{lab['local_code']}' ({display})")
# Search OMOPHub using the human-readable display name
# (OMOPHub cannot resolve proprietary local codes directly)
try:
# Try basic search first
results = client.search.basic(
display,
vocabulary_ids=["LOINC"],
domain_ids=["Measurement"],
page_size=3,
)
candidates = results.get("concepts", []) if results else []
# If basic search misses, try semantic search
if not candidates:
semantic = client.search.semantic(display, vocabulary_ids=["LOINC"], domain_ids=["Measurement"], page_size=3)
candidates = (semantic.get("results", semantic.get("concepts", [])) if semantic else [])
if candidates:
best = candidates[0]
loinc_id = best["concept_id"]
loinc_name = best.get("concept_name", "Unknown")
loinc_code = best.get("concept_code", "N/A")
print(f" -> {loinc_name} (OMOP: {loinc_id}, LOINC: {loinc_code})")
standardized.append({
"original_code": lab["local_code"],
"value": lab["value"],
"unit": lab["unit"],
"loinc_concept_id": loinc_id,
"loinc_name": loinc_name,
})
else:
print(f" -> No LOINC match found (may need manual mapping)")
except omophub.APIError as e:
print(f" -> API error: {e.message}")
# All three local codes should now map to the same LOINC creatinine concept
print(f"\n--- Standardized for App Display ---")
unique_loinc = set()
for s in standardized:
unique_loinc.add(s["loinc_concept_id"])
print(f" {s['loinc_name']}: {s['value']} {s['unit']} (from '{s['original_code']}')")
print(f"\nUnique LOINC concepts: {len(unique_loinc)}")
if len(unique_loinc) == 1:
print("All local codes resolved to the same LOINC concept - ready for trend display.")
else:
print("Multiple LOINC concepts found - review mappings for consistency.")
```
**The Key Insight:** The sidecar app doesn't need to maintain a mapping table for every EHR installation's local lab codes. It sends the human-readable display names to OMOPHub, gets back LOINC concept IDs, and all three "Creatinine" variants collapse to a single concept for trend display. This pattern scales: deploy the same app at 50 hospitals, and OMOPHub handles the vocabulary differences at each one.
**Caveat:** This only works when the local display name is descriptive enough for search to match. Highly abbreviated codes (like "Cr\_S" or "KFC\_001") won't match anything. Those need a pre-built local mapping maintained by the site's data team.
## 5. The "Feedback Loop": Writing Back to the EHR
True integration is bidirectional. When your OMOPHub-powered app identifies something - a more precise diagnosis code, a care gap, a trial match - that information should flow back to the EHR.
**Example:** A clinician types "chest pain" in a free-text field. Your app searches OMOPHub and suggests "Angina pectoris" (a specific SNOMED condition concept) with its concept ID and code. The clinician confirms, and the app writes the structured SNOMED code back to the EHR via a FHIR Condition resource.
The mechanics of the write-back are handled by the EHR's FHIR write API - OMOPHub doesn't write to EHRs. But OMOPHub provides the vocabulary backbone: the standardized concept ID, name, and code that get written back. This closes the loop: messy data comes out of the EHR → OMOPHub standardizes it → your app adds clinical intelligence → clean, structured data goes back in.
Over time, this feedback loop improves the EHR data itself - more precise codes, fewer local abbreviations, better downstream analytics. The vocabulary standardization that started as a research need becomes a data quality improvement engine.
## 6. Conclusion: Making Data Invisible (and Useful)
The best clinical technology is invisible. The clinician doesn't see "FHIR-to-OMOP transformation" or "vocabulary resolution" - they see a drug interaction alert that fires at the right moment, a lab trend that makes sense across visits, a trial match that arrives without a separate login.
OMOPHub's role in this is specific: it resolves codes from EHR-native vocabularies to standardized OMOP concepts via API. It doesn't parse FHIR resources, run CDS logic, or write back to EHRs. But it provides the vocabulary resolution layer that every other component depends on. And it does so without requiring per-site mapping tables or local vocabulary databases.
If you're building SMART on FHIR apps or EHR-integrated clinical tools, start with the vocabulary problem. Take a FHIR MedicationRequest from your test environment, extract the coded medication, and resolve it through OMOPHub. That single API call - billing code in, standard concept out - is the foundation everything else builds on. The data complexity becomes invisible. The clinical insight becomes useful.
# FHIR Integration
Source: https://docs.omophub.com/guides/integration/fhir-integration
A resource-by-resource cookbook for OMOPHub. Resolve FHIR CodeableConcepts to standard OMOP concept IDs - from Observations to Measurements, MedicationStatements to Drug Exposures, and beyond
## 1. Exchanging Data vs. Exchanging Meaning
FHIR solved the data exchange problem. A hospital can now send a FHIR `Observation` resource 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** handles that resolution step. It's a vocabulary API - you give it a code or a search term, it gives you back the standard OMOP concept. It doesn't parse FHIR resources, transform data structures, or load OMOP tables. But it answers the question every FHIR-to-OMOP pipeline needs answered: "What OMOP concept ID does this `CodeableConcept` map to?"
This article is a cookbook: specific FHIR resource types, specific OMOPHub API calls, specific OMOP CDM outputs.
> **Note:** This article complements the EHR Integration article, which covers the architectural patterns. This one goes resource-by-resource with concrete examples.
## 2. The Core Pattern: Resolving a FHIR `CodeableConcept`
Every FHIR clinical resource contains `CodeableConcept` fields. The resolution pattern is the same regardless of resource type:
**Step 1: Check the `coding` array for known vocabularies.**
The `coding` array may contain codes from LOINC, SNOMED, RxNorm, ICD-10-CM, NDC, or local systems. You need an explicit mapping from FHIR system URIs to OMOP vocabulary IDs:
```python Python theme={null}
FHIR_TO_OMOP_VOCAB = {
"http://loinc.org": "LOINC",
"http://snomed.info/sct": "SNOMED",
"http://www.nlm.nih.gov/research/umls/rxnorm": "RxNorm",
"http://hl7.org/fhir/sid/icd-10-cm": "ICD10CM",
"http://hl7.org/fhir/sid/ndc": "NDC",
"http://www.ama-assn.org/go/cpt": "CPT4",
}
```
For each recognized coding entry, search OMOPHub by code + vocabulary to get the OMOP concept ID.
**Step 2: If only local/unknown codes exist, fallback to the `text` field.**
Search OMOPHub with the human-readable text using `search.basic()` or `search.semantic()`, filtered to the expected vocabulary and domain.
**Step 3: Get the standard concept.**
If the found concept is non-standard (e.g., an ICD-10-CM source concept), call `mappings.get()` to find the "Maps to" standard equivalent (usually SNOMED for conditions, RxNorm for drugs, LOINC for measurements).
## 3. Use Case A: FHIR Observation → OMOP Measurement (Labs & Vitals)
The most common FHIR-to-OMOP mapping. Lab results and vital signs arrive as `Observation` resources and need to become OMOP `measurement` records.
**The Scenario:** A blood glucose `Observation` arrives with LOINC code 2339-0. Resolve it to an OMOP concept and build a measurement record.
```bash theme={null}
pip install omophub
```
```python Python theme={null}
import omophub
import json
client = omophub.OMOPHub()
# FHIR system URI → OMOP vocabulary ID
FHIR_TO_OMOP_VOCAB = {
"http://loinc.org": "LOINC",
"http://snomed.info/sct": "SNOMED",
"http://www.nlm.nih.gov/research/umls/rxnorm": "RxNorm",
"http://hl7.org/fhir/sid/ndc": "NDC",
}
# Sample FHIR Observation (simplified R4)
fhir_obs = {
"resourceType": "Observation",
"id": "glucose-obs",
"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",
"system": "http://unitsofmeasure.org",
"code": "mg/dL",
},
"effectiveDateTime": "2023-10-26T09:30:00Z",
}
print("Resolving FHIR Observation → OMOP Measurement\n")
# --- Step 1: Parse FHIR resource (YOUR code, not OMOPHub) ---
fhir_codings = fhir_obs["code"].get("coding", [])
fhir_text = fhir_obs["code"].get("text", "")
omop_concept_id = None
omop_concept_name = None
# --- Step 2: Try each coding entry against OMOPHub ---
for coding in fhir_codings:
system = coding.get("system", "")
code = coding.get("code", "")
omop_vocab = FHIR_TO_OMOP_VOCAB.get(system)
if not omop_vocab:
print(f" Skipping unrecognized system: {system}")
continue
print(f" Trying {omop_vocab} code '{code}'...")
try:
results = client.search.basic(
code,
vocabulary_ids=[omop_vocab],
page_size=1,
)
candidates = results.get("concepts", []) if results else []
if candidates:
concept = candidates[0]
omop_concept_id = concept["concept_id"]
omop_concept_name = concept.get("concept_name")
print(f" -> Found: {omop_concept_name} (OMOP ID: {omop_concept_id})")
# If non-standard concept, get the standard "Maps to" equivalent
if concept.get("standard_concept") != "S":
print(f" Non-standard. Looking up 'Maps to' mapping...")
std_mappings = client.mappings.get(omop_concept_id, target_vocabulary=omop_vocab)
map_list = (
std_mappings if isinstance(std_mappings, list)
else std_mappings.get("concepts", std_mappings.get("mappings", []))
) if std_mappings else []
if map_list:
std = map_list[0]
omop_concept_id = std["concept_id"]
omop_concept_name = std.get("concept_name")
print(f" -> Standard: {omop_concept_name} (OMOP ID: {omop_concept_id})")
break # Found a match, stop iterating
except omophub.APIError as e:
print(f" -> API error: {e.message}")
# --- Step 3: Fallback to text search ---
if omop_concept_id is None and fhir_text:
print(f" No coded match. Searching by text: '{fhir_text}'")
try:
results = client.search.basic(
fhir_text,
vocabulary_ids=["LOINC"],
domain_ids=["Measurement"],
page_size=3,
)
candidates = results.get("concepts", []) if results else []
if not candidates:
semantic = client.search.semantic(fhir_text, vocabulary_ids=["LOINC"], domain_ids=["Measurement"], page_size=3)
candidates = (semantic.get("results", semantic.get("concepts", [])) if semantic else [])
if candidates:
concept = candidates[0]
omop_concept_id = concept["concept_id"]
omop_concept_name = concept.get("concept_name")
print(f" -> Text match: {omop_concept_name} (OMOP ID: {omop_concept_id})")
except omophub.APIError as e:
print(f" -> API error: {e.message}")
# --- Build the OMOP measurement record ---
if omop_concept_id:
omop_measurement = {
"measurement_concept_id": omop_concept_id,
"measurement_source_value": fhir_text or fhir_codings[0].get("display", ""),
"value_as_number": fhir_obs["valueQuantity"]["value"],
"unit_source_value": fhir_obs["valueQuantity"].get("unit"),
"measurement_date": fhir_obs.get("effectiveDateTime", "")[:10],
}
print(f"\n--- OMOP Measurement Record ---")
print(json.dumps(omop_measurement, indent=2))
else:
print("\n Could not resolve to OMOP concept - needs manual mapping")
```
**The Key Insight:** The LOINC code 2339-0 may already be the standard OMOP concept (LOINC codes are often standard in OMOP for measurements). The `search.basic()` call with `vocabulary_ids=["LOINC"]` finds it directly. No "transformation" needed - just vocabulary resolution. The fallback to text search handles the cases where EHRs send local codes instead of LOINC.
## 4. Use Case B: FHIR MedicationStatement → OMOP Drug Exposure (The Ingredient Bridge)
Medication reconciliation requires rolling specific product codes up to their active ingredient for class-level analysis.
**The 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.
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
FHIR_TO_OMOP_VOCAB = {
"http://hl7.org/fhir/sid/ndc": "NDC",
"http://www.nlm.nih.gov/research/umls/rxnorm": "RxNorm",
}
# FHIR MedicationStatement (simplified)
fhir_med = {
"resourceType": "MedicationStatement",
"id": "med-001",
"medicationCodeableConcept": {
"coding": [
{
"system": "http://hl7.org/fhir/sid/ndc",
"code": "0071-0155-01",
"display": "Lipitor 10 MG Oral Tablet",
}
]
},
}
print("Resolving FHIR MedicationStatement → OMOP Drug + Ingredient\n")
ndc_code = fhir_med["medicationCodeableConcept"]["coding"][0]["code"]
ndc_system = fhir_med["medicationCodeableConcept"]["coding"][0]["system"]
omop_vocab = FHIR_TO_OMOP_VOCAB.get(ndc_system)
if not omop_vocab:
print(f" Unrecognized system: {ndc_system}")
else:
try:
# Step 1: Find the NDC concept in OMOP
print(f" NDC: {ndc_code}")
results = client.search.basic(
ndc_code,
vocabulary_ids=[omop_vocab],
page_size=1,
)
ndc_candidates = results.get("concepts", []) if results else []
if not ndc_candidates:
print(f" -> NDC not found in OMOPHub")
else:
ndc_concept = ndc_candidates[0]
ndc_omop_id = ndc_concept["concept_id"]
print(f" -> NDC concept: {ndc_concept.get('concept_name')} (OMOP: {ndc_omop_id})")
# Step 2: Map NDC → standard RxNorm concept
rxnorm_mappings = client.mappings.get(ndc_omop_id, target_vocabulary="RxNorm")
rxn_list = (
rxnorm_mappings if isinstance(rxnorm_mappings, list)
else rxnorm_mappings.get("concepts", rxnorm_mappings.get("mappings", []))
) if rxnorm_mappings else []
if not rxn_list:
print(f" -> No RxNorm mapping found")
else:
rxnorm_concept = rxn_list[0]
rxnorm_id = rxnorm_concept["concept_id"]
rxnorm_name = rxnorm_concept.get("concept_name", "Unknown")
print(f" -> RxNorm: {rxnorm_name} (OMOP: {rxnorm_id})")
# Step 3: Find the ingredient via concept relationships
# "Has ingredient" is a non-hierarchical relationship, NOT an ancestor traversal
rels = client.concepts.relationships(rxnorm_id)
rel_list = (
rels if isinstance(rels, list)
else rels.get("relationships", [])
) if rels else []
# Look for "RxNorm has ing" or "Has ingredient" relationship
ingredients = [
r for r in rel_list
if "ingredient" in r.get("relationship_id", "").lower()
or r.get("concept_class_id") == "Ingredient"
]
if ingredients:
ing = ingredients[0]
ing_id = ing.get("concept_id") or ing.get("concept_id_2")
ing_name = ing.get("concept_name")
print(f" -> Ingredient: {ing_name} (OMOP: {ing_id})")
# Alternative: traverse "Is a" hierarchy to find Ingredient class
else:
print(f" -> No ingredient relationship found. Trying hierarchy...")
try:
ancestors = client.hierarchy.ancestors(
rxnorm_id,
max_levels=5,
relationship_types=["Is a"],
)
anc_list = (
ancestors if isinstance(ancestors, list)
else ancestors.get("concepts", [])
) if ancestors else []
ing_ancestors = [
a for a in anc_list if a.get("concept_class_id") == "Ingredient"
]
if ing_ancestors:
ing = ing_ancestors[0]
print(f" -> Ingredient (via hierarchy): {ing.get('concept_name')} (OMOP: {ing['concept_id']})")
else:
print(f" -> Could not resolve to ingredient")
except omophub.APIError:
print(f" -> Hierarchy lookup failed")
except omophub.APIError as e:
print(f" -> API error: {e.message}")
```
**The Key Insight:** The NDC → RxNorm → Ingredient pipeline is three OMOPHub calls: `search.basic()` to find the NDC concept, `mappings.get()` to cross to RxNorm, and `concepts.relationships()` to find the ingredient. "Has ingredient" is a *relationship*, not a *hierarchical ancestor* - an important distinction. The OMOP vocabulary encodes drug composition as relationships between concepts, not as parent-child hierarchy levels. Your pharmacy sees "Lipitor 10mg." Your research database needs "Atorvastatin." OMOPHub bridges the gap.
## 5. Handling FHIR Extensions and Profiles
Real-world FHIR uses extensions - extra data fields not in the base spec. The US Core Profile, for example, adds detailed race and ethnicity extensions to `Patient` resources.
**How this maps to OMOP:**
* Smoking status extension → `OBSERVATION` table with a standard SNOMED concept
* Detailed race/ethnicity → `person.race_concept_id` and `person.ethnicity_concept_id`
* Patient-reported outcomes → `OBSERVATION` or `MEASUREMENT` depending on type
**OMOPHub's role:** When extensions contain `CodeableConcept` values, the same resolution pattern applies - extract the code, search OMOPHub, get the standard concept ID. When extensions contain text or categorical values, you may need to search OMOPHub by the display text to find the appropriate OMOP concept.
The parsing of FHIR extensions (identifying them by URL, extracting their values) is application code - OMOPHub handles the vocabulary resolution for whatever coded or text values you extract.
## 6. Conclusion: From Structure to Meaning
FHIR gives you structured data exchange. OMOP gives you standardized vocabularies. OMOPHub bridges the vocabulary gap between them - one `CodeableConcept` at a time.
The pattern is the same for every FHIR resource type: extract the `coding` array → look up each code in OMOPHub → get the standard OMOP concept → fall back to text search if needed. Whether it's an `Observation` becoming a `measurement`, a `MedicationStatement` becoming a `drug_exposure`, or a `Condition` becoming a `condition_occurrence`, the vocabulary resolution step is OMOPHub's job.
Build the FHIR-system-to-OMOP-vocabulary lookup table. Implement the code-first, text-fallback resolution pattern. Let OMOPHub handle the vocabulary mapping while your application handles the FHIR parsing and OMOP loading. That separation of concerns is what makes the pipeline maintainable across EHR installations that all speak slightly different FHIR dialects.
# Clinical Coding Automation
Source: https://docs.omophub.com/guides/use-cases/clinical-coding
Unlocking the power of OMOPHub for automated clinical coding workflows. Rrom raw text to standardized OMOP concepts.
## 1. The "Manual Mapping" Tax
If you've ever spent an entire afternoon staring at a spreadsheet, trying to figure out which SNOMED code matches your hospital's local lab entry for "GLUC\_FAST\_SER" - you know the pain. It's a hidden tax on your time. And it compounds.
Traditional ETL processes for converting raw healthcare data into the OMOP Common Data Model (CDM) are slow, expensive, and riddled with human error. Every local code, every proprietary label, every physician shorthand needs to be manually mapped to a standardized vocabulary. Multiply that across thousands of concepts, and you've got weeks of work that doesn't even feel like real work.
But here's the thing: **most of that mapping is repetitive and predictable.** And predictable work is exactly the kind of work that should be automated.
That's where **OMOPHub** comes in. It's a vocabulary API that gives you instant, programmatic access to the OHDSI ATHENA vocabularies: SNOMED, ICD-10, LOINC, RxNorm, and 100+ others - without needing to set up and maintain a local PostgreSQL database. Combine it with NLP tools or LLMs for the text extraction step, and you've got a powerful end-to-end clinical coding pipeline.
The goal? Shift from being a data cleaner to a clinical researcher. Let the tools handle the 80%, so you can focus your expertise on the 20% that actually matters.
## 2. The Core Concept: From Raw Text to Standardized Concepts
At its heart, clinical coding automation is about bridging the gap between diverse, messy source data and the structured world of the OMOP CDM. Think of it as a two-stage translation process:
1. **Extract:** Use NLP tools (like MedCAT, cTAKES, Amazon Comprehend Medical, or an LLM) to identify clinical entities from unstructured or semi-structured text - conditions, medications, procedures, lab tests.
2. **Map & Validate:** Use OMOPHub's vocabulary API to translate those extracted entities into standardized OMOP concept IDs (SNOMED, ICD-10, LOINC, etc.), and review the results with confidence metrics for human-in-the-loop quality assurance.
**Why OMOPHub for the mapping step?** Traditionally, querying OMOP vocabularies meant downloading multi-gigabyte ATHENA CSV files and loading them into your own database. OMOPHub eliminates that overhead. Install the Python SDK, add your API key, and start searching concepts, traversing hierarchies, and building mappings in minutes. It covers all major OHDSI vocabularies with bi-annual updates, batch operations, and healthcare-grade security.
It's not an NLP engine. It's the vocabulary backbone that makes your NLP pipeline clinically accurate.
## 3. Use Case A: Mapping Entities from Physician Notes (Unstructured Data)
Imagine you're a clinical researcher tasked with analyzing thousands of physician notes to identify patients with specific conditions. Manually reading those notes is impractical. But even after you run NLP extraction, you still need to link extracted terms to standardized OMOP concepts.
**The Scenario:** A researcher uses an NLP tool to extract clinical entities from free-text physician notes, then uses OMOPHub to map those entities to standard SNOMED or ICD-10 concepts.
**The Two-Step Workflow:**
* **Step 1:** An NLP tool (e.g., MedCAT, an LLM, or Amazon Comprehend Medical) processes the note and extracts entities like "acute myocardial infarction," "Type 2 Diabetes Mellitus," and "hypertension."
* **Step 2:** OMOPHub's search API maps each extracted entity to the correct OMOP concept ID.
**Code Snippet: Looking Up Extracted Entities via OMOPHub**
First, install the OMOPHub Python SDK:
```bash theme={null}
pip install omophub
```
Now, let's see how to use it:
```python Python theme={null}
import omophub
# Initialize the OMOPHub client with your API key
# Set OMOPHUB_API_KEY as an environment variable
client = omophub.OMOPHub()
physician_note = """
Patient presented with severe chest pain radiating to the left arm,
shortness of breath, and diaphoresis. Initial ECG showed ST elevation
in leads V2-V4. Suspected acute myocardial infarction.
Past medical history includes Type 2 Diabetes Mellitus and hypertension.
"""
# Step 1: Extract entities using your NLP tool of choice.
# This is a simplified placeholder - in production you'd use
# MedCAT, cTAKES, Amazon Comprehend Medical, SciSpacy, or an LLM.
extracted_conditions = [
"acute myocardial infarction",
"Type 2 Diabetes Mellitus",
"hypertension",
"chest pain",
"shortness of breath",
"diaphoresis",
]
# Step 2: Map each extracted entity to OMOP concepts via OMOPHub
print("\nMapping extracted conditions to OMOP concepts:")
for condition in extracted_conditions:
try:
results = client.search.basic(
query=condition,
vocabulary_ids=["SNOMED", "ICD10CM"],
standard_concept="S", # Only standard concepts
page_size=3,
)
if results and results.get("data"):
top_match = results["data"][0]
concept_name = top_match.get("concept_name", "N/A")
concept_id = top_match.get("concept_id", "N/A")
vocabulary_id = top_match.get("vocabulary_id", "N/A")
print(
f" '{condition}' -> {concept_name} "
f"(ID: {concept_id}, Vocab: {vocabulary_id})"
)
else:
print(f" '{condition}' -> No standard concept found")
except omophub.APIStatusError as e:
print(f" API Error for '{condition}': {e.status_code}")
except Exception as e:
print(f" Unexpected error for '{condition}': {e}")
```
**The Key Insight:** For large-scale research, "perfect" manual extraction is an illusion. The sheer volume of data makes it impractical and introduces its own inconsistencies. An automated NLP + vocabulary lookup pipeline provides a **consistent, scalable baseline**. You get 80% of the way there with a fraction of the effort, and then apply human expertise to the edge cases. That's the leverage.
## 4. Use Case B: Scaling Lab Code Mapping (Semi-Structured Data)
Beyond unstructured notes, healthcare systems are full of local, proprietary codes for lab tests, medications, and procedures. Mapping these to standard vocabularies like LOINC or RxNorm is foundational for data interoperability.
**The Scenario:** A data engineer needs to map local lab test names to their corresponding LOINC concept IDs.
**The Logic:** For semi-structured data where you already have specific local names (not free text), OMOPHub's search and mapping APIs are ideal. Search for each local code by name, find the best standard concept match, then retrieve its cross-vocabulary mappings if needed.
**Code Snippet: Mapping Local Lab Codes to LOINC**
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
local_lab_codes = [
"Fasting glucose",
"Hemoglobin A1c",
"Serum creatinine",
"TSH level",
"Urine sodium",
"NON_EXISTENT_LAB_TEST", # Example of something that might not map
]
print("\nMapping local lab codes to LOINC:")
for lab_code in local_lab_codes:
try:
# Search for the lab concept in LOINC vocabulary
results = client.search.basic(
query=lab_code,
vocabulary_ids=["LOINC"],
standard_concept="S",
page_size=1,
)
if results and results.get("data") and len(results["data"]) > 0:
match = results["data"][0]
concept_name = match.get("concept_name", "N/A")
concept_id = match.get("concept_id", "N/A")
concept_code = match.get("concept_code", "N/A")
print(
f" Local: '{lab_code}' -> LOINC: {concept_name} "
f"(Concept ID: {concept_id}, LOINC Code: {concept_code})"
)
# Optionally: get mappings to other vocabularies
# mappings = client.mappings.get(concept_id, target_vocabulary="SNOMED")
else:
print(f" Local: '{lab_code}' -> No LOINC match found (needs manual review)")
except omophub.APIStatusError as e:
print(f" API Error for '{lab_code}': {e.status_code}")
except Exception as e:
print(f" Unexpected error for '{lab_code}': {e}")
```
**The Key Insight:** This is the "set it and automate it" mindset. By scripting the lookup of common local codes, data engineers can build ETL pipelines that handle the vast majority of transformations automatically. The unmapped codes get flagged for manual review. Over time, as you resolve edge cases, you build a growing lookup table that makes every subsequent ETL run faster. Less technical debt, faster time-to-analysis.
## 5. Validation & Human-in-the-Loop
Automation is powerful, but it's not infallible. Clinical data is complex, and no automated system achieves 100% accuracy without validation. This is where the "human-in-the-loop" step earns its keep.
Here's how to build a practical validation workflow:
* **Flag uncertain mappings:** If a search returns multiple plausible matches or the top result doesn't look right, flag it for manual review by a clinical expert. You can use heuristics like comparing the returned `concept_name` against the original term, or checking whether the concept's `domain_id` matches your expectation.
* **Prioritize review:** Instead of reviewing every mapping, clinical experts focus on the ambiguous or high-impact cases - the codes that affect cohort definitions or safety endpoints.
* **Iterate and improve:** Feedback from reviewers feeds back into your mapping logic. Resolved edge cases become lookup rules. The system gets smarter with each ETL cycle.
**The Practical Workflow:** Export a CSV containing the original local code, the proposed OMOP concept, its concept ID, vocabulary, and a column for "Reviewed (Y/N)" and "Corrected Concept ID." This gives clinical experts a clean, sortable review surface. Simple, effective, scalable.
## 6. Conclusion: Reclaiming Your Time
Clinical coding automation isn't about replacing clinical expertise - it's about deploying it where it matters most. The combination of NLP tools for entity extraction and OMOPHub for vocabulary lookup and mapping gives you a pipeline that handles the repetitive 80% while surfacing the 20% that needs your judgment.
By integrating OMOPHub into your ETL workflow, you go from maintaining local vocabulary databases and doing manual lookups to making simple API calls. That's less infrastructure, faster iterations, and more time spent on actual research.
Try the Python snippets with your own data. Start with a small batch of local codes. See how many map cleanly on the first pass. I think you'll be surprised at how much time you get back.
The "manual mapping tax"? Consider it optional.
# Clinical Trial Eligibility Screening
Source: https://docs.omophub.com/guides/use-cases/clinical-trial-eligibility
Finding the needle in the OMOP haystack. Resolve trial criteria to standardized concept sets and screen patients using hierarchy expansion and set-based matching
## 1. The "Needle in a Haystack" Problem
A Phase III diabetes trial with 200 empty slots and six months to fill them. The patients exist - their data is sitting right there in the OMOP CDM. The problem? The eligibility criteria say things like "Adults with HbA1c > 7.0% despite Metformin therapy" - and translating that into queries across `condition_occurrence`, `measurement`, and `drug_exposure` tables, with the right concept IDs, the right value thresholds, and the right temporal logic, takes weeks of manual work per trial.
This is the bottleneck in clinical trial recruitment. Not a lack of patients - a lack of infrastructure to match patients to trials at scale.
Solving this requires two things working together: (1) an NLP or LLM system to parse the eligibility criteria text into structured components (conditions, drugs, measurements, thresholds, temporal constraints), and (2) a vocabulary API to resolve those components into standardized OMOP concept IDs that you can actually query against.
**OMOPHub** handles the second part. It gives you instant API access to the full OHDSI ATHENA vocabulary - SNOMED for conditions, RxNorm for drugs, LOINC for measurements - so you can resolve "Type 2 Diabetes Mellitus" to concept ID `201826`, "Metformin" to its RxNorm ingredient, and "HbA1c" to its LOINC code, all without maintaining a local vocabulary database.
But OMOPHub's real superpower for trial screening is **concept set expansion**. A trial criterion says "Type 2 Diabetes." But your patients might be coded as "Type 2 DM with renal complications," "Type 2 DM with peripheral angiopathy," or a dozen other specific variants. Simple ID matching would miss them. OMOPHub's hierarchy API lets you expand a single concept into all its descendants - catching every patient who should qualify, regardless of how specifically they were coded.
## 2. The Core Concept: From Criteria Text to OMOP Queries
Automating trial eligibility screening is a multi-step process. Here's how the pieces fit together:
**Step 1 - Parse the criteria (NLP/LLM).** Take the eligibility text from the protocol and extract structured components. A criterion like "patients with Type 2 Diabetes Mellitus and HbA1c > 7.0%, currently on Metformin" decomposes into:
* Condition: "Type 2 Diabetes Mellitus" (inclusion)
* Measurement: "HbA1c" > 7.0% (inclusion)
* Drug: "Metformin" (inclusion, current exposure)
This step requires an NLP tool or LLM (tools like Criteria2Query, or prompting Claude/GPT with the protocol text). OMOPHub does not do this step - it's a vocabulary API, not a text parser.
**Step 2 - Resolve to OMOP concepts (OMOPHub).** Take each extracted entity and look up its standardized OMOP concept ID via OMOPHub's search API:
* "Type 2 Diabetes Mellitus" → SNOMED concept ID `201826`
* "Metformin" → RxNorm ingredient concept ID
* "HbA1c" → LOINC concept ID
**Step 3 - Expand concept sets (OMOPHub).** Use `hierarchy.descendants()` to build complete concept sets. "Type 2 Diabetes Mellitus" has dozens of child concepts in SNOMED. You want to match patients coded with any of them.
**Step 4 - Query the OMOP database.** Apply the resolved, expanded concept IDs against the patient database (`condition_occurrence`, `drug_exposure`, `measurement` tables) with the appropriate value/temporal filters.
OMOPHub owns Steps 2 and 3. That's where it adds the most value - turning clinical terms into queryable concept sets without the overhead of a local vocabulary database.
## 3. Use Case A: Resolving Parsed Criteria to OMOP Concept Sets
Suppose your NLP step has already extracted the key entities from the trial protocol. Now you need OMOP concept IDs - and not just the top-level concept, but the full expanded set of descendants.
**The Scenario:** A trial requires patients with "Type 2 Diabetes Mellitus" and current "Metformin" use. You need concept sets for both.
**Code Snippet: Resolving Criteria Entities and Expanding Concept Sets**
```bash theme={null}
pip install omophub
```
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
# These entities were extracted from the trial protocol by an NLP/LLM step
# (OMOPHub does NOT do the extraction - it resolves terms to concept IDs)
parsed_criteria = {
"inclusion_conditions": ["Type 2 Diabetes Mellitus"],
"inclusion_drugs": ["Metformin"],
"inclusion_measurements": ["Hemoglobin A1c"],
}
resolved_criteria = {
"condition_concept_ids": set(),
"drug_concept_ids": set(),
"measurement_concept_ids": set(),
}
print("Resolving parsed criteria to OMOP concept sets:\n")
# --- Resolve Conditions ---
for condition_name in parsed_criteria["inclusion_conditions"]:
try:
results = client.search.basic(
condition_name,
vocabulary_ids=["SNOMED"],
domain_ids=["Condition"],
page_size=3,
)
candidates = results.get("concepts", []) if results else []
if candidates:
top_match = candidates[0]
top_id = top_match["concept_id"]
print(f" Condition: '{condition_name}'")
print(f" -> {top_match['concept_name']} (ID: {top_id})")
# Expand concept set: get all descendants
descendants = client.hierarchy.descendants(top_id, max_levels=5)
desc_list = descendants if isinstance(descendants, list) else descendants.get("concepts", [])
resolved_criteria["condition_concept_ids"].add(top_id)
for desc in desc_list:
resolved_criteria["condition_concept_ids"].add(desc["concept_id"])
print(f" -> Expanded to {len(resolved_criteria['condition_concept_ids'])} concepts (including descendants)")
else:
print(f" Condition: '{condition_name}' -> No SNOMED match found")
except omophub.APIError as e:
print(f" API error for '{condition_name}': {e.message}")
# --- Resolve Drugs ---
for drug_name in parsed_criteria["inclusion_drugs"]:
try:
results = client.search.basic(
drug_name,
vocabulary_ids=["RxNorm"],
domain_ids=["Drug"],
page_size=3,
)
candidates = results.get("concepts", []) if results else []
if candidates:
# Find the ingredient-level concept
ingredient = None
for c in candidates:
if c.get("concept_class_id") == "Ingredient":
ingredient = c
break
if not ingredient:
ingredient = candidates[0]
drug_id = ingredient["concept_id"]
print(f" Drug: '{drug_name}'")
print(f" -> {ingredient['concept_name']} (ID: {drug_id}, Class: {ingredient.get('concept_class_id', 'N/A')})")
# Expand: get all drug products containing this ingredient
descendants = client.hierarchy.descendants(drug_id, max_levels=3)
desc_list = descendants if isinstance(descendants, list) else descendants.get("concepts", [])
resolved_criteria["drug_concept_ids"].add(drug_id)
for desc in desc_list:
resolved_criteria["drug_concept_ids"].add(desc["concept_id"])
print(f" -> Expanded to {len(resolved_criteria['drug_concept_ids'])} concepts (ingredient + products)")
else:
print(f" Drug: '{drug_name}' -> No RxNorm match found")
except omophub.APIError as e:
print(f" API error for '{drug_name}': {e.message}")
# --- Resolve Measurements ---
for meas_name in parsed_criteria["inclusion_measurements"]:
try:
results = client.search.basic(
meas_name,
vocabulary_ids=["LOINC"],
domain_ids=["Measurement"],
page_size=3,
)
candidates = results.get("concepts", []) if results else []
if candidates:
top_match = candidates[0]
meas_id = top_match["concept_id"]
print(f" Measurement: '{meas_name}'")
print(f" -> {top_match['concept_name']} (ID: {meas_id}, LOINC: {top_match.get('concept_code', 'N/A')})")
resolved_criteria["measurement_concept_ids"].add(meas_id)
else:
print(f" Measurement: '{meas_name}' -> No LOINC match found")
except omophub.APIError as e:
print(f" API error for '{meas_name}': {e.message}")
# --- Summary ---
print(f"\nResolved Criteria Summary:")
print(f" Condition concept set: {len(resolved_criteria['condition_concept_ids'])} concepts")
print(f" Drug concept set: {len(resolved_criteria['drug_concept_ids'])} concepts")
print(f" Measurement concept set: {len(resolved_criteria['measurement_concept_ids'])} concepts")
print(f"\nNote: Numerical thresholds (e.g., HbA1c > 7.0%) and temporal logic")
print(f"(e.g., 'for at least 1 year') must be handled in the database query layer.")
```
**The Key Insight:** The concept set expansion is what makes this practical. Without it, you'd miss every patient coded with a specific subtype of Type 2 Diabetes. With it, a single concept ID becomes a comprehensive set that catches all clinically equivalent patients. This is the difference between finding 50 candidates and finding 500 - and OMOPHub makes it an API call instead of a local database query.
## 4. Use Case B: Patient Pre-screening Against Trial Criteria
Once you have expanded concept sets from Use Case A, screening a patient becomes straightforward set logic: does the patient's OMOP profile overlap with the trial's required concepts?
**The Scenario:** A patient is in the clinic. Their OMOP record has condition, drug, and measurement concept IDs. You need to check if they match the trial criteria.
**Code Snippet: Pre-screening a Patient**
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
# Patient's OMOP profile (from their EHR/OMOP database)
patient_profile = {
"condition_ids": {201826, 4329847, 443727, 40484648},
# 201826 = Type 2 DM, 4329847 = MI, 443727 = Essential HTN, 40484648 = CKD
"drug_ids": {1503297},
# 1503297 = Metformin (example concept ID)
}
# Trial criteria (resolved + expanded from Use Case A)
# In production, these would be the full expanded concept sets
trial_criteria = {
"required_condition_ids": {201826}, # Type 2 DM (would be expanded set in production)
"required_drug_ids": {1503297}, # Metformin
"excluded_condition_ids": {432571}, # e.g., Liver disease
}
print("Patient Pre-screening:\n")
# Check inclusion conditions
conditions_met = trial_criteria["required_condition_ids"].issubset(patient_profile["condition_ids"])
drugs_met = trial_criteria["required_drug_ids"].issubset(patient_profile.get("drug_ids", set()))
# Check exclusion conditions
has_exclusion = bool(trial_criteria["excluded_condition_ids"] & patient_profile["condition_ids"])
print(f" Required conditions met: {conditions_met}")
print(f" Required drugs met: {drugs_met}")
print(f" Has exclusion condition: {has_exclusion}")
if conditions_met and drugs_met and not has_exclusion:
print("\n RESULT: Patient is a POTENTIAL CANDIDATE for this trial.")
print(" (Pending measurement checks - HbA1c threshold, etc.)")
else:
# Look up names of unmet criteria for clear reporting
unmet = []
if not conditions_met:
missing = trial_criteria["required_condition_ids"] - patient_profile["condition_ids"]
unmet.extend(list(missing))
if not drugs_met:
missing = trial_criteria["required_drug_ids"] - patient_profile.get("drug_ids", set())
unmet.extend(list(missing))
if unmet:
try:
details = client.concepts.batch(list(unmet))
detail_list = details if isinstance(details, list) else details.get("concepts", [])
print(f"\n RESULT: Patient does NOT meet criteria.")
print(f" Missing:")
for concept in detail_list:
print(f" - {concept.get('concept_name', 'Unknown')} (ID: {concept['concept_id']})")
except omophub.APIError as e:
print(f"\n RESULT: Patient does NOT meet criteria. Missing IDs: {unmet}")
if has_exclusion:
excluded = trial_criteria["excluded_condition_ids"] & patient_profile["condition_ids"]
try:
details = client.concepts.batch(list(excluded))
detail_list = details if isinstance(details, list) else details.get("concepts", [])
print(f" Exclusion criteria triggered:")
for concept in detail_list:
print(f" - {concept.get('concept_name', 'Unknown')} (ID: {concept['concept_id']})")
except omophub.APIError:
print(f" Exclusion criteria triggered: IDs {excluded}")
```
**The Key Insight:** The core matching is just set operations. OMOPHub's value is in two places: (a) building the concept sets in the first place (Use Case A), and (b) providing human-readable concept names for the screening report - so the clinician sees "Missing: Metformin" instead of "Missing: concept ID 1503297." That translation from IDs to names is small but critical for clinical adoption.
## 5. The "Explainable Matching" Layer
A simple "eligible / not eligible" isn't enough for clinicians. They need to understand *why* - especially for exclusions. Was the patient excluded because of a permanent contraindication, or something that could change (like a medication that could be washed out)?
This is where pairing OMOPHub's structured vocabulary data with an external LLM creates real clinical value.
**Example Workflow:**
1. OMOPHub identifies that the patient has concept ID `4329847` (Myocardial Infarction), which is an exclusion criterion
2. OMOPHub provides the structured metadata: concept name, domain, vocabulary
3. You feed this to an LLM with the trial context to generate a clinician-facing explanation
```python Python theme={null}
# After OMOPHub resolves the exclusion reason:
exclusion_data = {
"concept_name": "Myocardial infarction",
"concept_id": 4329847,
"vocabulary": "SNOMED",
"domain": "Condition",
"criterion_type": "exclusion",
"trial_criterion_text": "Patients with history of myocardial infarction within the past 6 months",
}
# Feed to an external LLM for a clinician-facing explanation:
prompt = f"""
A patient was flagged during clinical trial screening.
Exclusion reason: The patient has a recorded {exclusion_data['concept_name']}
(SNOMED concept {exclusion_data['concept_id']}), which triggers the following
trial exclusion criterion: "{exclusion_data['trial_criterion_text']}"
Provide a 2-sentence clinical explanation for the treating physician,
including whether this exclusion could become eligible with time.
"""
# llm_response = your_llm_client.complete(prompt)
# Expected: "This patient is currently excluded due to a history of
# myocardial infarction, which falls under the trial's cardiovascular
# safety exclusion. If the MI occurred more than 6 months ago, the
# patient may become eligible - verify the event date in the clinical record."
```
**The Key Insight:** OMOPHub provides the structured vocabulary backbone (concept names, IDs, relationships). The LLM provides the clinical reasoning and natural language generation. Together, they transform an opaque "excluded" flag into an actionable explanation that helps the clinician make a decision. This is how you build trust in automated screening systems.
## 6. Conclusion: Accelerating the Path to "First Patient In"
Clinical trial recruitment is a pipeline problem. The patients are there. The data is there. What's missing is the infrastructure to connect them efficiently.
OMOPHub addresses two critical bottlenecks in that pipeline: resolving clinical terms from trial protocols into standardized OMOP concept IDs, and expanding those concepts into comprehensive concept sets that catch patients regardless of coding specificity. The first turns "Metformin" into a queryable ID. The second turns "Type 2 Diabetes" into a set of 50+ concept IDs that catches every relevant patient.
Combined with NLP for criteria parsing and an LLM for explainable matching, you get a screening pipeline that takes days instead of weeks - and produces results that clinicians can understand and act on.
Start with Use Case A: take one inclusion criterion from a trial on ClinicalTrials.gov, extract the clinical terms, and run them through OMOPHub's search and hierarchy APIs. See how many descendant concepts you get. That expanded concept set is the difference between a trial that struggles to recruit and one that finds its patients.
# Collaborative Mapping
Source: https://docs.omophub.com/guides/use-cases/collaborative-mapping
Break the silo tax by using OMOPHub to build shareable source_to_concept_map files that accelerate multi-site OMOP mapping.
## 1. The "Silo Tax"
Every hospital that joins an OMOP network faces the same mapping wall. Their lab system uses "Cr\_Serum" for creatinine. The one across town uses "CREAT\_BLD." The academic medical center uses "Creatinine\_S\_mg." All three mean the same thing: LOINC 2160-0, Creatinine \[Mass/volume] in Serum or Plasma.
But each hospital maps it independently. Three data engineers, three weeks of work, three times the cost - for the same result. Scale this across 500 hospitals and 800 unique lab codes each, and you're looking at hundreds of thousands of hours of duplicated mapping labor per year.
This is the **Silo Tax**: the cost of every institution solving the same vocabulary problem in isolation.
The OHDSI community has partial solutions. **USAGI** is an open-source mapping tool that helps data engineers find standard concept matches for local codes. **Athena** distributes the vocabularies themselves. The OMOP CDM has a **`source_to_concept_map`** table designed specifically to store local-to-standard mappings. But none of these provide a way to *share* completed mappings between institutions.
**OMOPHub** doesn't solve the sharing problem either - it doesn't have a collaborative mapping repository. But it accelerates the *creation* of mapping files by providing fast vocabulary search without requiring a local Athena installation. And because mapping files follow a standard format (`source_to_concept_map`), they can be shared between institutions manually - via GitHub, shared drives, or project-specific data exchanges.
This article shows the practical workflow: use OMOPHub to build mapping files fast, format them as `source_to_concept_map` records, and share them between sites participating in the same research network.
> **Honest scoping:** OMOPHub does not currently have a shared mapping repository, user-contributed mappings, or collaborative mapping API. The workflow described here uses OMOPHub for vocabulary search + standard file formats for sharing. A future collaborative mapping feature would be valuable - but this article works with what exists today.
## 2. The Core Concept: The `source_to_concept_map` as a Sharing Format
The OMOP CDM includes a table purpose-built for local-to-standard mappings:
```
source_to_concept_map:
- source_code (VARCHAR) - the local code, e.g., "Cr_Serum"
- source_concept_id (INTEGER) - 0 if no OMOP concept for the local code
- source_vocabulary_id (VARCHAR) - identifier for the local vocabulary, e.g., "HospitalA_Labs"
- target_concept_id (INTEGER) - the standard OMOP concept ID, e.g., 3016723
- target_vocabulary_id (VARCHAR) - e.g., "LOINC"
- valid_start_date (DATE)
- valid_end_date (DATE)
- invalid_reason (VARCHAR)
```
This table is the natural format for shareable mapping files. If Hospital A builds a `source_to_concept_map` for their lab codes and Hospital B has similar local codes, Hospital B can import Hospital A's mappings as a starting point - reviewing and adjusting as needed.
**OMOPHub's role:** Populating the `target_concept_id` and `target_vocabulary_id` columns. For each local code, search OMOPHub to find the standard concept match, then write the result into the mapping table format.
## 3. Use Case A: Building a Shareable Mapping File with OMOPHub
A multi-site sepsis research project needs all participating hospitals to map their local lab codes for inflammatory markers to standard LOINC concepts. Instead of each hospital starting from scratch, the coordinating center builds an initial mapping file using OMOPHub, then distributes it.
```bash theme={null}
pip install omophub
```
```python Python theme={null}
import omophub
import json
from datetime import date
client = omophub.OMOPHub()
# Local codes from Hospital A's lab system (representative set for sepsis)
local_codes = [
{"source_code": "CRP_HS", "display": "C-Reactive Protein, High Sensitivity", "vocab": "HospitalA_Labs"},
{"source_code": "PCT_Level", "display": "Procalcitonin Level", "vocab": "HospitalA_Labs"},
{"source_code": "Lactate_Art", "display": "Arterial Lactate", "vocab": "HospitalA_Labs"},
{"source_code": "WBC_Auto", "display": "White Blood Cell Count, Automated", "vocab": "HospitalA_Labs"},
{"source_code": "BldCx_Ana", "display": "Blood Culture Anaerobic", "vocab": "HospitalA_Labs"},
{"source_code": "SepsisAlert", "display": "Sepsis Alert Triggered", "vocab": "HospitalA_Events"},
]
print("Building source_to_concept_map for sepsis project...\n")
mapping_records = []
for entry in local_codes:
code = entry["source_code"]
display = entry["display"]
source_vocab = entry["vocab"]
print(f" {code}: '{display}'")
target_id = 0 # Default: unmapped
target_vocab = ""
target_name = ""
match_method = "unmapped"
try:
# Step 1: Search OMOPHub for the best LOINC/SNOMED match
results = client.search.basic(
display,
vocabulary_ids=["LOINC", "SNOMED"],
domain_ids=["Measurement", "Condition", "Observation"],
page_size=3,
)
candidates = results.get("concepts", []) if results else []
# Step 2: Semantic fallback if basic search misses
if not candidates:
semantic = client.search.semantic(display, vocabulary_ids=["LOINC", "SNOMED"], domain_ids=["Measurement", "Condition", "Observation"], page_size=3)
candidates = (semantic.get("results", semantic.get("concepts", [])) if semantic else [])
if candidates:
match_method = "semantic"
if candidates:
best = candidates[0]
target_id = best["concept_id"]
target_vocab = best.get("vocabulary_id", "")
target_name = best.get("concept_name", "")
match_method = match_method if match_method == "fuzzy" else "basic"
print(f" -> {target_name} ({target_vocab}, OMOP: {target_id}) [{match_method}]")
else:
print(f" -> NO MATCH - needs manual review")
except omophub.APIError as e:
print(f" -> API error: {e.message}")
match_method = "error"
# Step 3: Build source_to_concept_map record
mapping_records.append({
"source_code": code,
"source_concept_id": 0,
"source_vocabulary_id": source_vocab,
"source_code_description": display,
"target_concept_id": target_id,
"target_vocabulary_id": target_vocab,
"valid_start_date": str(date.today()),
"valid_end_date": "2099-12-31",
"invalid_reason": None,
# Extra metadata for review (not standard OMOP columns, but useful for sharing)
"_match_method": match_method,
"_target_concept_name": target_name,
"_reviewed": False,
"_reviewer": None,
})
# Summary
mapped = sum(1 for r in mapping_records if r["target_concept_id"] != 0)
unmapped = len(mapping_records) - mapped
print(f"\n--- Mapping File Summary ---")
print(f" Total: {len(mapping_records)} | Mapped: {mapped} | Needs review: {unmapped}")
# In production: save as CSV for sharing
# pd.DataFrame(mapping_records).to_csv("sepsis_source_to_concept_map_hospitalA.csv", index=False)
print(f"\n--- source_to_concept_map Records ---")
for r in mapping_records:
status = f"-> {r['_target_concept_name']} ({r['target_vocabulary_id']}: {r['target_concept_id']})" if r["target_concept_id"] != 0 else "-> UNMAPPED"
print(f" {r['source_code']:15s} {status} [{r['_match_method']}]")
```
**The Key Insight:** This script produces a `source_to_concept_map`-formatted file in minutes - work that would take days of manual USAGI review. The output is a CSV that can be shared with other hospitals in the network. Hospital B imports it, reviews the mappings (adjusting for their local code variants), and has a head start on their own mapping work.
## 4. Use Case B: Importing and Adapting a Shared Mapping File
Hospital B receives Hospital A's mapping file. Their local codes are different ("CRP\_Serum" instead of "CRP\_HS"), but the target OMOP concepts are the same. They adapt the shared file and fill in the gaps.
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
# Hospital A's shared mapping file (loaded from CSV in production)
shared_mappings = {
"C-Reactive Protein": {"target_concept_id": 3010156, "target_vocab": "LOINC"},
"Procalcitonin": {"target_concept_id": 3046279, "target_vocab": "LOINC"},
"Lactate": {"target_concept_id": 3047181, "target_vocab": "LOINC"},
"White Blood Cell Count": {"target_concept_id": 3000905, "target_vocab": "LOINC"},
"Blood Culture": {"target_concept_id": 3016407, "target_vocab": "LOINC"},
}
# Hospital B's local codes
hospital_b_codes = [
{"source_code": "CRP_Serum", "display": "CRP Serum Level"},
{"source_code": "PCT_Quant", "display": "Quantitative Procalcitonin"},
{"source_code": "Lact_Venous", "display": "Venous Lactate"},
{"source_code": "IL6_Level", "display": "Interleukin-6 Level"}, # Not in shared file
]
print("Adapting shared mapping file for Hospital B...\n")
for entry in hospital_b_codes:
display = entry["display"]
code = entry["source_code"]
# Step 1: Check if any shared mapping matches by keyword overlap
matched_shared = None
for shared_key, shared_val in shared_mappings.items():
if shared_key.lower() in display.lower() or any(
word in display.lower() for word in shared_key.lower().split()
):
matched_shared = (shared_key, shared_val)
break
if matched_shared:
key, val = matched_shared
print(f" {code}: Reused shared mapping '{key}' -> OMOP {val['target_concept_id']} ({val['target_vocab']})")
else:
# Step 2: Not in shared file - look up via OMOPHub
print(f" {code}: Not in shared file. Searching OMOPHub...")
try:
results = client.search.basic(
display,
vocabulary_ids=["LOINC"],
domain_ids=["Measurement"],
page_size=1,
)
candidates = results.get("concepts", []) if results else []
if candidates:
best = candidates[0]
print(f" -> NEW: {best.get('concept_name')} (OMOP: {best['concept_id']})")
print(f" -> Add to shared mapping file for other hospitals")
else:
print(f" -> No match - needs manual review")
except omophub.APIError as e:
print(f" -> API error: {e.message}")
```
**The Key Insight:** Hospital B's mapping took minutes instead of days because Hospital A already did the vocabulary search work. The shared mapping file isn't an API feature - it's a CSV. But OMOPHub made it fast to build, and the `source_to_concept_map` format makes it portable. Hospital B's new mappings (like IL-6) get added to the shared file for Hospital C.
## 5. The Sharing Workflow
The practical workflow for a multi-site research network:
**Step 1: Coordinating center builds initial mapping file** (Use Case A)
* Extract all unique local codes from the first participating site
* Look up each via OMOPHub
* Human reviews and approves each mapping
* Save as `source_to_concept_map` CSV
**Step 2: Distribute to network participants**
* Share via GitHub repo, shared drive, or project data package
* Include review metadata (\_match\_method, \_reviewed, \_reviewer)
**Step 3: Each site adapts the shared file** (Use Case B)
* Match their local codes against the shared mappings by display name similarity
* Use OMOPHub to fill gaps (codes not in the shared file)
* Contribute new mappings back to the shared file
**Step 4: Iterate**
* The mapping file grows with each new site
* Later sites have fewer gaps to fill
* The coordinating center merges contributions and resolves conflicts
This isn't a fancy platform feature - it's a disciplined workflow using standard formats and a fast vocabulary API. But it's how the Silo Tax actually gets reduced in practice.
**What a future collaborative mapping feature could add:**
* Centralized, API-accessible mapping repository (search other institutions' mappings)
* Confidence scoring based on number of institutions that agree on a mapping
* Version-controlled mapping provenance
* Automated conflict detection when institutions map the same local code differently
These would be genuinely valuable - but they require infrastructure beyond what OMOPHub currently provides. The workflow above works today.
## 6. Conclusion: Share Mappings, Not Just Vocabularies
The Silo Tax persists because sharing local-to-standard mappings is harder than it should be. Standard vocabularies are shared (via Athena). Phenotype definitions are shared (via OHDSI PhenotypeLibrary). But the translation layer - the `source_to_concept_map` that each site builds laboriously - stays locked in institutional silos.
OMOPHub makes building that mapping file fast: search for a local code display name, get back the standard OMOP concept, write it to the mapping table. The `source_to_concept_map` format makes the file portable: CSV in, CSV out, same schema everywhere.
The missing piece isn't technology - it's workflow discipline. If your research network adopts the pattern of building, sharing, and iterating on mapping files, the Silo Tax drops with every site that joins. The first hospital does 100% of the mapping work. The second does 30%. By the fifth, it's mostly review and edge cases.
Start with one mapping file. Build it with OMOPHub. Share it with your network. Let the next site extend it. That's how collaborative mapping works in practice - not with a magic platform, but with standard formats and a fast vocabulary API.
# Drug Interaction Checking
Source: https://docs.omophub.com/guides/use-cases/drug-interaction-checking
Moving beyond alert fatigue with OMOPHub. Resolve drug names to standardized ingredients via the RxNorm hierarchy for smarter DDI checking.
## 1. The "Alert Fatigue" Problem
A doctor clicks "override" on a drug interaction alert for the 47th time today. Not because the alert is wrong, but because it's not useful. It says "Interaction detected" with no context, no severity, no clinical reasoning. After the 47th time, even the dangerous alerts start blending into the noise.
That's alert fatigue. And it's one of the most well-documented patient safety problems in clinical informatics. Studies consistently show that clinicians override 50–96% of DDI alerts, depending on the system. The problem isn't that interactions don't matter. It's that most DDI checkers treat every interaction the same way: if Drug A and Drug B are present, flag it. No nuance, no dosage context, no ingredient-level reasoning.
Building a smarter DDI checker requires solving three distinct problems: (1) resolving drug names and codes to their active ingredients, (2) looking up actual interaction data from a reliable knowledge base, and (3) presenting the results with enough clinical context to be actionable.
**OMOPHub** solves problem #1, and it solves it well. It's a vocabulary API that gives you programmatic access to the full RxNorm hierarchy, so you can instantly resolve any drug product to its standardized active ingredient without maintaining a local vocabulary database. Pair it with a DDI knowledge base (like DrugBank or FDB) for the interaction lookup, and optionally an LLM for contextual reasoning, and you've got the foundation for a DDI system that clinicians might actually trust.
## 2. The Core Concept: Navigating the RxNorm Hierarchy
Drug interactions happen at the **ingredient level**. It doesn't matter whether the patient is taking Tylenol, Paracetamol 500mg tablets, or Generic Acetaminophen - the interaction risk comes from the Acetaminophen. So before you can check for interactions, you need to resolve every drug on the patient's medication list down to its active ingredient(s).
RxNorm - the standardized drug nomenclature used in the OMOP CDM - is organized hierarchically:
* **Branded Drug** (e.g., "Coumadin 5 MG Oral Tablet")
* **Clinical Drug** (e.g., "Warfarin 5 MG Oral Tablet")
* **Clinical Drug Component** (e.g., "Warfarin 5 MG")
* **Ingredient** (e.g., "Warfarin")
Navigating this hierarchy manually across thousands of drugs would mean writing complex SQL against a multi-gigabyte ATHENA database. OMOPHub eliminates that. Its Python SDK gives you intuitive methods to search for drugs, traverse ancestors/descendants, and retrieve concept relationships, all via simple API calls.
**What OMOPHub handles:** Resolving drug names/codes to standardized RxNorm concept IDs, traversing the hierarchy to find active ingredients, and cross-vocabulary mappings (e.g., RxNorm to ATC class).
**What OMOPHub does NOT handle:** Actual drug-drug interaction data. You'll need an external DDI knowledge base (DrugBank, FDB/First Databank, Medi-Span, or similar) to determine whether Ingredient A interacts with Ingredient B. OMOPHub gives you the ingredients; the DDI database tells you which combinations are dangerous.
## 3. Use Case A: Resolving Drug Names to Ingredients for Interaction Checking
Here's a practical scenario: a patient's medication list mentions "Coumadin" and "Aspirin." Before you can check for interactions, you need to resolve both to their RxNorm ingredients (Warfarin and Acetylsalicylic acid).
**The Workflow:**
1. Search OMOPHub for each drug name to get its standard RxNorm concept ID
2. Use hierarchy traversal to find the ingredient-level concept
3. Pass the resolved ingredients to your DDI knowledge base
**Code Snippet: Resolving Drug Names to Ingredients**
```bash theme={null}
pip install omophub
```
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
# Drug names extracted from a medication list or clinical note
# (In production, you'd use NLP/NER to extract these from free text first)
drug_names = ["Coumadin", "Aspirin"]
print("Resolving drug names to active ingredients via OMOPHub:\n")
resolved_ingredients = {}
for drug_name in drug_names:
try:
# Step 1: Search for the drug in RxNorm
search_results = client.search.basic(
drug_name,
vocabulary_ids=["RxNorm"],
domain_ids=["Drug"],
page_size=3,
)
if not search_results or not search_results.get("concepts"):
print(f" '{drug_name}' -> No RxNorm match found")
continue
drug_concept = search_results["concepts"][0]
drug_id = drug_concept["concept_id"]
print(f" '{drug_name}' -> {drug_concept['concept_name']} (ID: {drug_id})")
# Step 2: Traverse ancestors to find the Ingredient
ancestors = client.hierarchy.ancestors(drug_id, max_levels=5)
# Filter ancestors client-side for concept_class_id == "Ingredient"
ingredient = None
if ancestors:
for ancestor in ancestors if isinstance(ancestors, list) else ancestors.get("concepts", []):
if ancestor.get("concept_class_id") == "Ingredient":
ingredient = ancestor
break
if ingredient:
ingredient_name = ingredient["concept_name"]
ingredient_id = ingredient["concept_id"]
print(f" -> Ingredient: {ingredient_name} (ID: {ingredient_id})")
resolved_ingredients[drug_name] = {
"ingredient_name": ingredient_name,
"ingredient_concept_id": ingredient_id,
}
else:
# Fallback: check concept relationships for "Has ingredient"
relationships = client.concepts.relationships(drug_id)
if relationships:
for rel in relationships if isinstance(relationships, list) else relationships.get("concepts", []):
if rel.get("relationship_id") == "RxNorm has ing":
print(f" -> Ingredient (via relationship): {rel['concept_name']}")
resolved_ingredients[drug_name] = {
"ingredient_name": rel["concept_name"],
"ingredient_concept_id": rel["concept_id"],
}
break
except omophub.NotFoundError:
print(f" '{drug_name}' -> Concept not found")
except omophub.APIError as e:
print(f" API error for '{drug_name}': {e.status_code} - {e.message}")
print(f"\nResolved Ingredients: {resolved_ingredients}")
# Step 3: Pass resolved ingredients to your DDI knowledge base
# This is where you'd call DrugBank, FDB, or your internal DDI database
# Example (pseudocode):
# interactions = ddi_database.check(
# list(resolved_ingredients.values())
# )
```
**The Key Insight:** OMOPHub handles the vocabulary resolution that makes DDI checking *possible* by converting messy drug names into clean, standardized ingredients. This is the step that traditionally required a local ATHENA database. With OMOPHub, it's an API call. The actual interaction lookup happens downstream, in a dedicated DDI knowledge base.
## 4. Use Case B: Batch Screening for Population Health
For population-level safety surveillance, you need to screen thousands of patients' medication lists for high-risk combinations. This means batch-resolving drug concept IDs to ingredients, then checking each patient's ingredient set against known interaction rules.
**The Scenario:** A researcher wants to identify all patients concurrently prescribed an SSRI and an NSAID - a combination known to increase GI bleeding risk.
**Code Snippet: Batch Ingredient Resolution and Class Detection**
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
# Example: patient medication list as RxNorm concept IDs
# (These would come from your OMOP drug_exposure table)
patient_medication_ids = [
19013970, # Sertraline 50 MG Oral Tablet
1118084, # Ibuprofen 200 MG Oral Tablet
19080001, # Metformin 500 MG Oral Tablet
19077987, # Amlodipine 5 MG Oral Tablet
]
print(f"Screening patient medications: {patient_medication_ids}\n")
patient_ingredients = []
try:
# Batch-retrieve concept details
batch_results = client.concepts.batch(patient_medication_ids)
for concept in batch_results if isinstance(batch_results, list) else batch_results.get("concepts", []):
concept_name = concept.get("concept_name", "Unknown")
concept_id = concept["concept_id"]
print(f" Drug: {concept_name} (ID: {concept_id})")
# Resolve to ingredient via hierarchy
ancestors = client.hierarchy.ancestors(concept_id, max_levels=5)
if ancestors:
for anc in ancestors if isinstance(ancestors, list) else ancestors.get("concepts", []):
if anc.get("concept_class_id") == "Ingredient":
print(f" -> Ingredient: {anc['concept_name']}")
patient_ingredients.append({
"drug_name": concept_name,
"ingredient_name": anc["concept_name"],
"ingredient_id": anc["concept_id"],
})
break
except omophub.APIError as e:
print(f" API error: {e.status_code} - {e.message}")
# Now classify ingredients into therapeutic classes
# In production, you'd use ATC hierarchy via OMOPHub mappings
# For this example, we use a reference lookup
SSRI_INGREDIENTS = {"Sertraline", "Fluoxetine", "Paroxetine", "Citalopram", "Escitalopram"}
NSAID_INGREDIENTS = {"Ibuprofen", "Naproxen", "Diclofenac", "Celecoxib", "Indomethacin"}
found_ssri = [i for i in patient_ingredients if i["ingredient_name"] in SSRI_INGREDIENTS]
found_nsaid = [i for i in patient_ingredients if i["ingredient_name"] in NSAID_INGREDIENTS]
print(f"\nSSRIs found: {[i['ingredient_name'] for i in found_ssri]}")
print(f"NSAIDs found: {[i['ingredient_name'] for i in found_nsaid]}")
if found_ssri and found_nsaid:
print("\n*** ALERT: SSRI + NSAID concurrent use detected ***")
print("Risk: Increased gastrointestinal bleeding.")
print(f" SSRI: {found_ssri[0]['drug_name']} ({found_ssri[0]['ingredient_name']})")
print(f" NSAID: {found_nsaid[0]['drug_name']} ({found_nsaid[0]['ingredient_name']})")
print("Recommendation: Review clinical necessity. Consider gastroprotective agent.")
else:
print("\nNo SSRI + NSAID combination detected.")
```
**The Key Insight:** For population health, the power is in the batch workflow. OMOPHub's `concepts.batch()` lets you resolve a whole medication list in one call, and `hierarchy.ancestors()` gets you from product-level to ingredient-level. The therapeutic class check (SSRI vs NSAID) can be done via reference lists or, more robustly, by traversing the ATC classification hierarchy through OMOPHub's cross-vocabulary mappings. The actual interaction rules come from your clinical knowledge base or published guidelines. OMOPHub provides the vocabulary infrastructure that makes those rules applicable at scale.
## 5. Adding Clinical Context with an LLM
Identifying an interaction is step one. Making it *actionable* is step two. This is where pairing OMOPHub's structured vocabulary data with an LLM creates real value.
Instead of a generic "Interaction detected" alert, you can feed the resolved ingredient information into an LLM along with patient context, and generate a clinician-friendly explanation.
**Simplified Example: Generating a Contextualized DDI Alert**
```python Python theme={null}
# After resolving ingredients via OMOPHub (as shown above):
# warfarin_info = {"ingredient": "Warfarin", "concept_id": 1310149}
# aspirin_info = {"ingredient": "Aspirin", "concept_id": 1112807}
# Feed structured data to an LLM for clinical reasoning
# (Using any LLM API - OpenAI, Anthropic, etc.)
prompt = f"""
You are a clinical pharmacist. Based on the following drug information
resolved from OMOP/RxNorm standardized vocabularies, provide a concise
clinical assessment of the interaction risk.
Patient medications (resolved to ingredients):
- Warfarin (Anticoagulant, RxNorm Ingredient concept ID: 1310149)
- Aspirin (Antiplatelet/NSAID, RxNorm Ingredient concept ID: 1112807)
Provide:
1. Interaction severity (Minor / Moderate / Major / Contraindicated)
2. Mechanism of interaction
3. Clinical recommendation (2-3 sentences)
"""
# llm_response = your_llm_client.complete(prompt)
# Expected output would be something like:
# "Major interaction. Both Warfarin and Aspirin affect hemostasis through
# complementary mechanisms - Warfarin inhibits clotting factor synthesis
# while Aspirin inhibits platelet aggregation. Concurrent use significantly
# increases bleeding risk. Monitor INR closely and assess whether dual
# therapy is clinically necessary."
```
**The Key Insight:** The LLM doesn't replace the DDI knowledge base, it enhances how the results are communicated. By grounding the LLM prompt with OMOPHub-resolved structured data (standardized ingredient names, concept IDs, vocabulary context), you avoid hallucination and get clinically relevant explanations. This is the shift from "Computer says no" to "Here's why this matters for your patient."
## 6. Conclusion: A Three-Layer Architecture
Building a DDI checker that clinicians will actually trust requires three layers, each doing what it does best:
1. **Vocabulary Resolution (OMOPHub)**: Resolve drug names, codes, and products to standardized RxNorm ingredients via API. No local database maintenance required.
2. **Interaction Lookup (DDI Knowledge Base)**: Check resolved ingredient pairs against a curated DDI database (DrugBank, FDB, Medi-Span, or clinical rules).
3. **Clinical Context (LLM, optional)**: Generate human-readable explanations that include mechanism, severity, and actionable recommendations.
OMOPHub handles the first layer, the vocabulary plumbing that makes everything else possible. It turns "Coumadin 5mg" into "Warfarin" with a single API call, and it does it at scale. That's less infrastructure, faster development, and more time building the clinical logic that actually reduces alert fatigue.
Start with the ingredient resolution snippets above. Plug in your DDI data source. See how many of your current "generic" alerts you can turn into actionable, context-rich clinical guidance.
Alert fatigue is a systems problem. Fix the system.
# Gap Analysis and OHDSI Contributions
Source: https://docs.omophub.com/guides/use-cases/gap-analysis-contributions
Spotting the missing links in standard vocabularies. Use OMOPHub to automate gap detection and build focused shortlists for OHDSI community contributions.
## 1. The "Contribution Friction"
Every institution converting local EHR data to OMOP hits unmappable codes. Local lab abbreviations with no LOINC equivalent. Institutional procedure codes that don't exist in SNOMED. Novel biomarkers that haven't been added to any standard vocabulary yet.
These unmapped codes are gaps - and they're valuable. Each one is a potential contribution to the OHDSI vocabulary ecosystem. If your hospital has a local code for a clinical concept that SNOMED doesn't cover, and you identify it, the OHDSI Vocabulary Team can add it in the next Athena release. Every institution that converts to OMOP after that benefits from your discovery.
But finding these gaps is tedious. You have 3,000 local codes. Most will map fine. Maybe 50 are genuinely missing from the standard vocabularies. The other 2,950 are just lookup work. How do you find the 50 that matter?
**OMOPHub** makes the gap detection step fast. Search for each local code programmatically. The ones that return no match (or only weak matches) are your gap candidates. That's your shortlist for human review and potential OHDSI contribution.
What OMOPHub does *not* do: submit contributions to OHDSI, add concepts to Athena, or determine whether a gap is "truly missing" vs. "just badly named." Gap detection is automated; gap interpretation requires human clinical expertise. The OHDSI contribution itself goes through the community forums and vocabulary team - not through an API.
## 2. The Core Concept: Search Failures as Signals
The insight is simple: if OMOPHub's search can't find a match for a clinical term, that's information.
**Tiered search strategy:**
1. **Basic search** with vocabulary and domain filters - catches exact and close matches
2. **Semantic search** - catches misspellings, abbreviations, word-order variations
3. **If both fail** - flag as a gap candidate
The tier matters because the *type* of failure tells you something:
* **Basic fails, semantic succeeds** → probably a naming/abbreviation issue, not a real gap
* **Both fail** → likely a genuine gap or very institution-specific term
* **Both return results, but wrong domain** → mapping ambiguity, needs human review
This tiered approach reduces false positives (codes flagged as gaps that actually have mappings) and produces a cleaner shortlist for expert review.
## 3. Use Case A: Automated Gap Detection for Local Codes
A hospital joining an OMOP research network has 200 unique local diagnosis codes related to sepsis. The data engineer needs to identify which ones have no standard OMOP equivalent - those are the gaps to review manually and potentially contribute to OHDSI.
```bash theme={null}
pip install omophub
```
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
# Local codes to analyze (in production: extracted from your source system)
local_codes = [
{"code": "Sepsis_OD", "display": "Sepsis with Organ Dysfunction"},
{"code": "Bacteremia_NOS", "display": "Bacteremia, unspecified"},
{"code": "Hypotension_Sep", "display": "Hypotension due to Sepsis"},
{"code": "BiomarkerXYZ", "display": "Novel Sepsis Biomarker XYZ"},
{"code": "ICD_A41.9", "display": "Sepsis, unspecified organism"},
{"code": "AtypSepsis", "display": "Atypical Sepsis Presentation"},
{"code": "qSOFA_Pos", "display": "Positive qSOFA Score"},
]
print("Automated Gap Analysis\n")
results = []
for entry in local_codes:
code = entry["code"]
display = entry["display"]
match_found = False
match_tier = None
match_concept = None
# --- Tier 1: Basic search ---
try:
basic = client.search.basic(
display,
vocabulary_ids=["SNOMED", "ICD10CM"],
domain_ids=["Condition"],
page_size=3,
)
candidates = basic.get("concepts", []) if basic else []
# Step 1: Filter to standard concepts only
standard = [c for c in candidates if c.get("standard_concept") == "S"]
if standard:
match_found = True
match_tier = "basic"
match_concept = standard[0]
except omophub.APIError:
pass
# --- Tier 2: Semantic search (if basic failed) ---
if not match_found:
try:
semantic = client.search.semantic(display, domain_ids=["Condition"], standard_concept="S", page_size=3)
candidates = (semantic.get("results", semantic.get("concepts", [])) if semantic else [])
if candidates:
match_found = True
match_tier = "semantic"
match_concept = candidates[0]
except omophub.APIError:
pass
# --- Record result ---
if match_found:
name = match_concept.get("concept_name", "Unknown")
cid = match_concept["concept_id"]
vocab = match_concept.get("vocabulary_id", "N/A")
print(f" MAPPED [{match_tier}]: '{display}' -> {name} ({vocab}: {cid})")
results.append({
"local_code": code,
"local_display": display,
"status": "mapped",
"match_tier": match_tier,
"omop_concept_id": cid,
"omop_concept_name": name,
"omop_vocabulary": vocab,
})
else:
print(f" GAP CANDIDATE: '{display}' - no standard match found")
results.append({
"local_code": code,
"local_display": display,
"status": "gap_candidate",
"match_tier": None,
"omop_concept_id": None,
"omop_concept_name": None,
"omop_vocabulary": None,
})
# --- Summary ---
mapped = [r for r in results if r["status"] == "mapped"]
gaps = [r for r in results if r["status"] == "gap_candidate"]
print(f"\n--- Gap Analysis Summary ---")
print(f" Total codes: {len(results)}")
print(f" Mapped: {len(mapped)} ({len([m for m in mapped if m['match_tier'] == 'basic'])} basic, {len([m for m in mapped if m['match_tier'] == 'semantic'])} semantic)")
print(f" Gap candidates: {len(gaps)}")
if gaps:
print(f"\n--- Gap Candidates for Human Review ---")
for g in gaps:
print(f" Code: {g['local_code']:20s} Display: {g['local_display']}")
```
**The Key Insight:** "Novel Sepsis Biomarker XYZ" and "Positive qSOFA Score" will likely fail both search tiers - the first because it's genuinely novel, the second because clinical scoring concepts are sparsely represented in some vocabularies. "Sepsis, unspecified organism" will map (it's essentially ICD-10 A41.9). "Bacteremia, unspecified" will likely map to a SNOMED concept. The gap candidates are the shortlist that needs human expert review.
## 4. Use Case B: Categorizing Gaps for Targeted Action
Not all gaps are the same. A gap candidate could be:
* **A genuinely missing concept** - the clinical idea doesn't exist in any standard vocabulary (e.g., a brand-new biomarker)
* **A missing mapping** - the concept exists in SNOMED but not in the vocabulary you searched (e.g., exists as a Procedure, not a Condition)
* **A local abbreviation** - the term is too institution-specific for search to match, but the concept exists under a different name
* **A composite concept** - the local code combines multiple clinical ideas that are separate concepts in OMOP
Categorizing gaps helps prioritize action: missing concepts should be proposed to OHDSI; missing mappings might just need a broader search; abbreviations need local mapping work; composites need decomposition.
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
def categorize_gap(display_term):
"""
Attempt to categorize a gap candidate by searching more broadly.
Returns a category and any near-miss candidates found.
"""
near_misses = []
# Step 1: Broader search - drop domain filter, search all vocabularies
try:
broad = client.search.basic(display_term, page_size=5)
candidates = broad.get("concepts", []) if broad else []
if candidates:
# Found something, but not in our original domain/vocab filter
near_misses = [
{
"concept_name": c.get("concept_name"),
"concept_id": c["concept_id"],
"domain": c.get("domain_id"),
"vocabulary": c.get("vocabulary_id"),
"standard": c.get("standard_concept"),
}
for c in candidates[:3]
]
# Check if matches are in a different domain
domains = set(c.get("domain_id") for c in candidates)
if domains and "Condition" not in domains:
return "wrong_domain", near_misses
# Check if matches are non-standard (mapping exists but not standard)
standards = [c.get("standard_concept") for c in candidates]
if all(s != "S" for s in standards):
return "non_standard_only", near_misses
return "partial_match", near_misses
except omophub.APIError:
pass
# Step 2: Try semantic search as last resort
try:
semantic = client.search.semantic(display_term)
sem_list = (
semantic if isinstance(semantic, list)
else semantic.get("concepts", [])
) if semantic else []
if sem_list:
near_misses = [
{
"concept_name": c.get("concept_name"),
"concept_id": c["concept_id"],
"domain": c.get("domain_id"),
"vocabulary": c.get("vocabulary_id"),
}
for c in sem_list[:3]
]
return "semantic_near_miss", near_misses
except omophub.APIError:
pass
# Nothing found anywhere
return "genuinely_missing", []
# Categorize the gap candidates from Use Case A
gap_candidates = [
"Novel Sepsis Biomarker XYZ",
"Positive qSOFA Score",
"Atypical Sepsis Presentation",
]
print("Categorizing gap candidates...\n")
for term in gap_candidates:
category, near_misses = categorize_gap(term)
print(f" '{term}'")
print(f" Category: {category}")
if near_misses:
print(f" Near misses:")
for nm in near_misses:
print(f" - {nm['concept_name']} ({nm['domain']}/{nm['vocabulary']})")
# Recommended action based on category
actions = {
"genuinely_missing": "-> Propose new concept to OHDSI Vocabulary Team via forums.ohdsi.org",
"wrong_domain": "-> Concept exists but in different domain. Review if domain mapping is correct.",
"non_standard_only": "-> Non-standard concept exists. Check if standard equivalent was missed.",
"partial_match": "-> Partial matches found. Human review needed to select best match.",
"semantic_near_miss": "-> Semantic match only. May be a naming/abbreviation issue.",
}
print(f" Action: {actions.get(category, 'Unknown')}")
print()
```
**The Key Insight:** "Novel Sepsis Biomarker XYZ" will likely categorize as `genuinely_missing` - no matches anywhere. That's your OHDSI contribution candidate. "Positive qSOFA Score" might find `semantic_near_miss` results (qSOFA-related SNOMED concepts exist but may not match the exact phrasing). "Atypical Sepsis Presentation" might get `partial_match` - "Sepsis" matches, but "Atypical" is too vague for a specific concept. Each category drives a different action.
## 5. The OHDSI Contribution Pathway
Once you've identified genuinely missing concepts, the contribution process is community-driven, not API-driven:
1. **Prepare a gap report** from your analysis (the output of Use Cases A + B)
2. **Post to OHDSI Forums** (forums.ohdsi.org → Vocabulary category) describing:
* The missing concept and its clinical definition
* The source vocabulary where it originates (if applicable)
* How many records in your data use this code (impact/frequency)
* Any near-miss concepts from your OMOPHub search (shows you did due diligence)
3. **OHDSI Vocabulary Team reviews** the proposal and decides whether to add it
4. **If accepted**, it appears in the next Athena vocabulary release
5. **Update your local vocabularies** from Athena to get the new concept
OMOPHub helps with step 1 (automated gap detection and categorization). Steps 2-5 are community process. This is by design - vocabulary changes affect the entire OHDSI network and need human review.
**Tools that complement this workflow:**
* **USAGI** - OHDSI's mapping tool for the manual review step (reviewing near-misses, approving mappings)
* **Athena** - Where accepted contributions land (vocabulary downloads)
* **OMOPHub** - Fast vocabulary search for gap detection (no local vocab DB needed)
## 6. Conclusion: Finding the Gaps That Matter
Gap analysis isn't about finding every unmapped code - most unmapped codes are just local abbreviations that need manual mapping work. It's about finding the codes that represent genuinely missing clinical concepts in the standard vocabularies. Those are the high-value contributions to OHDSI.
The tiered search approach (basic → semantic → flag) combined with gap categorization (genuinely missing vs. wrong domain vs. naming issue) produces a focused shortlist that expert reviewers can act on efficiently. Instead of reviewing 3,000 local codes, they review 50 gap candidates, 10 of which might be genuine OHDSI contributions.
OMOPHub makes the detection fast. The categorization helps prioritize. The OHDSI community process handles the rest.
Start with your most problematic source system - the one with the most unmapped codes. Run the gap analysis. Categorize the results. Post the genuinely missing concepts to the OHDSI forums. Your contribution makes the next institution's mapping work a little easier.
# Insurance Claims Processing
Source: https://docs.omophub.com/guides/use-cases/insurance-claims-processing
Bridging the billing vs. clinical divide. Standardize CPT, ICD-10, and HCPCS billing codes to OMOP clinical concepts for medical necessity review, fraud detection, and risk adjustment
## 1. The "Paperwork Tax"
Every insurance claim tells two stories. The billing story: a list of CPT, ICD-10, and HCPCS codes, optimized for reimbursement. The clinical story: what actually happened to the patient and why. The gap between these two stories is where billions of dollars in administrative waste, payment delays, and fraud live.
A CPT code for an "Office Visit" doesn't explain why the patient was there. An ICD-10 code for "M05.79" means "Rheumatoid arthritis with rheumatoid factor, multiple sites" - but only if you know ICD-10. And a HCPCS J-code for a biologic drug tells you the molecule, not whether it was clinically appropriate for the patient's condition.
Bridging this divide starts with a basic but powerful step: **standardizing the codes.** OMOP's vocabulary structure maps billing codes (ICD-10-CM, CPT-4, HCPCS) to clinically rich standard concepts (SNOMED for conditions/procedures, RxNorm for drugs) via "Maps to" relationships. Once standardized, you can start asking clinical questions about administrative data.
**OMOPHub** makes this standardization step fast and programmatic. It's a REST API for the OHDSI ATHENA vocabularies - so instead of maintaining a local vocabulary database, you look up a billing code, get its standard OMOP concept, and immediately have access to the clinical hierarchy, relationships, and cross-vocabulary mappings. The pattern: billing code → OMOPHub search → standard concept ID → clinical context.
What OMOPHub does *not* do: it doesn't determine medical necessity, detect fraud, or adjudicate claims. Those require clinical rules engines, indication databases (like FDB or DrugBank), and CMS edit tables (like CCI/NCCI). OMOPHub handles the vocabulary layer - translating billing codes into a standardized clinical language that those downstream systems can work with.
## 2. The Core Concept: Reconstructing the Clinical Event
An insurance claim is a bundle of billing codes representing a clinical encounter. OMOPHub helps you unpack that bundle into standardized clinical components:
* **Procedure performed** → CPT-4 code → maps to SNOMED Procedure concept
* **Diagnosis justifying the procedure** → ICD-10-CM code → maps to SNOMED Condition concept
* **Drug or device administered** → HCPCS J-code or NDC → maps to RxNorm Drug concept
The "Maps to" relationships in the OMOP vocabulary are the bridge. Multiple billing codes can map to the same standard concept (many-to-one), and a single billing code can occasionally map to multiple standard concepts (one-to-many). OMOPHub's search and mappings APIs let you traverse these relationships programmatically.
Once you have standard concept IDs, you can:
* Look up concept hierarchies (is this drug an anti-TNF agent?)
* Check concept relationships (what ingredient does this drug contain?)
* Compare across claims (different billing codes, same clinical event)
This standardization is the foundation. Everything else - necessity review, fraud detection, risk adjustment - builds on top of it.
## 3. Use Case A: Standardizing a Claim for Medical Necessity Review
Medical necessity review asks: is this billed drug clinically appropriate for this diagnosed condition? The first step is standardizing both the drug and the diagnosis.
**The Scenario:** A claim arrives with HCPCS code J0135 (Adalimumab/Humira) and ICD-10-CM code M05.79 (Rheumatoid arthritis with rheumatoid factor, multiple sites). You need to resolve both to standard OMOP concepts.
**Code Snippet: Standardizing Billing Codes via OMOPHub**
```bash theme={null}
pip install omophub
```
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
# Billing codes from the claim
claim_drug_code = "J0135" # HCPCS: Adalimumab injection
claim_diag_code = "M05.79" # ICD-10-CM: RA with rheumatoid factor, multiple sites
print("Standardizing claim billing codes...\n")
# --- Step 1: Resolve the drug (HCPCS → RxNorm) ---
print(f"Drug: HCPCS {claim_drug_code}")
try:
# Search for the HCPCS code in OMOPHub
drug_search = client.search.basic(
claim_drug_code,
vocabulary_ids=["HCPCS"],
page_size=1,
)
drug_candidates = drug_search.get("concepts", []) if drug_search else []
drug_standard_id = None
if drug_candidates:
hcpcs_concept = drug_candidates[0]
hcpcs_id = hcpcs_concept["concept_id"]
print(f" Found: {hcpcs_concept.get('concept_name')} (OMOP ID: {hcpcs_id})")
# Get the "Maps to" standard concept
drug_mappings = client.mappings.get(hcpcs_id, target_vocabulary="RxNorm")
drug_map_list = (
drug_mappings if isinstance(drug_mappings, list)
else drug_mappings.get("concepts", drug_mappings.get("mappings", []))
) if drug_mappings else []
if drug_map_list:
standard_drug = drug_map_list[0]
drug_standard_id = standard_drug["concept_id"]
print(f" -> Standard RxNorm: {standard_drug.get('concept_name')} (ID: {drug_standard_id})")
else:
print(f" -> No RxNorm mapping found. Checking if already standard...")
# HCPCS drug codes sometimes are their own standard concept
if hcpcs_concept.get("standard_concept") == "S":
drug_standard_id = hcpcs_id
print(f" -> Using HCPCS concept as standard (ID: {drug_standard_id})")
else:
print(f" Not found in OMOPHub")
except omophub.APIError as e:
print(f" API error: {e.message}")
# --- Step 2: Resolve the diagnosis (ICD-10-CM → SNOMED) ---
print(f"\nDiagnosis: ICD-10-CM {claim_diag_code}")
try:
diag_search = client.search.basic(
claim_diag_code,
vocabulary_ids=["ICD10CM"],
page_size=1,
)
diag_candidates = diag_search.get("concepts", []) if diag_search else []
diag_standard_id = None
if diag_candidates:
icd_concept = diag_candidates[0]
icd_id = icd_concept["concept_id"]
print(f" Found: {icd_concept.get('concept_name')} (OMOP ID: {icd_id})")
# Get the "Maps to" standard SNOMED concept
diag_mappings = client.mappings.get(icd_id, target_vocabulary="SNOMED")
diag_map_list = (
diag_mappings if isinstance(diag_mappings, list)
else diag_mappings.get("concepts", diag_mappings.get("mappings", []))
) if diag_mappings else []
if diag_map_list:
standard_diag = diag_map_list[0]
diag_standard_id = standard_diag["concept_id"]
print(f" -> Standard SNOMED: {standard_diag.get('concept_name')} (ID: {diag_standard_id})")
else:
print(f" -> No SNOMED mapping found")
else:
print(f" Not found in OMOPHub")
except omophub.APIError as e:
print(f" API error: {e.message}")
# --- Step 3: Explore relationships (for context, NOT for necessity determination) ---
if drug_standard_id:
print(f"\nDrug concept relationships:")
try:
drug_rels = client.concepts.relationships(drug_standard_id)
rel_list = (
drug_rels if isinstance(drug_rels, list)
else drug_rels.get("relationships", [])
) if drug_rels else []
for rel in rel_list[:5]:
print(f" {rel.get('relationship_id', 'N/A')}: {rel.get('concept_name', 'N/A')}")
if len(rel_list) > 5:
print(f" ... and {len(rel_list) - 5} more relationships")
except omophub.APIError:
pass
# --- Important limitation ---
print("\n--- Medical Necessity Determination ---")
print("OMOPHub standardizes billing codes to clinical concepts.")
print("To determine medical necessity (is Adalimumab indicated for RA?),")
print("you need an external drug indications database:")
print(" - FDB First Databank")
print(" - DrugBank")
print(" - DailyMed / FDA label data")
print(" - Medi-Span")
print("OMOP vocabularies do NOT contain 'treats' or 'indicated for' relationships.")
```
**The Key Insight:** OMOPHub gets you from billing codes to standard clinical concepts in two API calls per code. That's the essential first step - without it, you're comparing HCPCS strings to ICD-10 strings, which tells you nothing clinically. With standardized concepts, you can feed them into an indications database to check medical necessity, or into a risk adjustment model for accurate scoring. OMOPHub handles the translation layer; the clinical decision logic lives elsewhere.
## 4. Use Case B: Code Standardization for Fraud Detection
Fraud, Waste, and Abuse (FWA) detection - particularly "unbundling" (billing component procedures separately instead of using a comprehensive code) - requires comparing what was billed against what should have been billed.
**The Scenario:** A claim includes CPT 10060 (I\&D abscess, simple) and CPT 10061 (I\&D abscess, complicated) for the same patient visit. Are these legitimately separate procedures, or is this unbundling?
**The Reality:** This question can't be answered by vocabulary hierarchies alone. The authoritative source for billing bundling rules is the **CMS Correct Coding Initiative (CCI/NCCI) edit tables**, which specify exactly which code pairs can and cannot be billed together. OMOP vocabularies don't contain these rules.
**Where OMOPHub helps:** Standardizing the CPT codes so they can be looked up against CCI edit tables and clinical context.
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
# CPT codes from a suspicious claim
claim_cpt_codes = ["10060", "10061"]
print("Standardizing CPT codes for FWA analysis...\n")
standardized = []
for cpt_code in claim_cpt_codes:
try:
results = client.search.basic(
cpt_code,
vocabulary_ids=["CPT4"],
page_size=1,
)
candidates = results.get("concepts", []) if results else []
if candidates:
concept = candidates[0]
omop_id = concept["concept_id"]
name = concept.get("concept_name", "Unknown")
print(f" CPT {cpt_code}: {name} (OMOP ID: {omop_id})")
standardized.append({
"cpt_code": cpt_code,
"omop_id": omop_id,
"concept_name": name,
})
# Get the standard concept mapping (CPT4 → SNOMED Procedure)
mappings = client.mappings.get(omop_id, target_vocabulary="SNOMED")
map_list = (
mappings if isinstance(mappings, list)
else mappings.get("concepts", mappings.get("mappings", []))
) if mappings else []
if map_list:
std = map_list[0]
print(f" -> SNOMED: {std.get('concept_name')} (ID: {std['concept_id']})")
standardized[-1]["snomed_id"] = std["concept_id"]
standardized[-1]["snomed_name"] = std.get("concept_name")
else:
print(f" CPT {cpt_code}: Not found")
except omophub.APIError as e:
print(f" CPT {cpt_code}: API error - {e.message}")
# --- FWA analysis requires external data ---
print("\n--- Unbundling Analysis ---")
print("To determine if these CPT codes represent unbundling:")
print(" 1. Look up the code pair in CMS CCI/NCCI edit tables")
print(" 2. Check modifier indicators (some pairs are allowed with modifiers)")
print(" 3. Review anatomical site documentation")
print()
print("OMOPHub's role: standardize codes so they can be matched against CCI edits.")
print("CCI edit tables must be maintained separately (available from CMS.gov).")
if len(standardized) >= 2:
print(f"\nCode pair for CCI lookup:")
print(f" Column 1: CPT {standardized[0]['cpt_code']} ({standardized[0]['concept_name']})")
print(f" Column 2: CPT {standardized[1]['cpt_code']} ({standardized[1]['concept_name']})")
print(f" -> Check NCCI PTP edits for this pair")
```
**The Key Insight:** OMOPHub standardizes billing codes - that's its job. Unbundling detection requires CCI edit tables, which are billing policy rules, not vocabulary relationships. The workflow is: OMOPHub resolves codes to standard concepts → CCI edits determine if the code pair is allowed → clinical context (from SNOMED hierarchy) provides supporting evidence. Each tool does what it's built for.
## 5. The "Clinical Context" Enrichment Layer
Once billing codes are standardized, OMOPHub's vocabulary features can add clinical context that pure billing data lacks:
**Hierarchy for severity inference:** A diagnosis of "Type 2 diabetes mellitus with diabetic chronic kidney disease" (a specific SNOMED descendant) tells you more about severity than just "Type 2 diabetes." OMOPHub's hierarchy API reveals where a concept sits in the clinical tree - deeper = more specific = often more severe.
**Relationships for care expectations:** Using `concepts.relationships()`, you can discover what measurements, procedures, or findings are associated with a condition in the OMOP vocabulary. A diabetes diagnosis has relationships to HbA1c measurements, foot exam procedures, and eye exam referrals - all quality metrics that payers track.
**Cross-vocabulary mapping for completeness:** A claim might use ICD-10-CM codes, but quality measures are often defined in SNOMED. OMOPHub's mappings ensure you can bridge between what was billed and what's being measured.
These enrichments don't replace clinical judgment or rules engines, but they provide the standardized vocabulary substrate that those systems need to function. The more context you attach to a claim at the vocabulary level, the smarter your downstream analytics become.
## 6. Conclusion: From Billing Codes to Clinical Understanding
The billing-clinical divide isn't going away - ICD-10, CPT, and HCPCS serve a different purpose than SNOMED and RxNorm. But the translation between them doesn't have to be manual.
OMOPHub handles the vocabulary translation layer: billing code → search → standard concept ID → hierarchy, relationships, and mappings. This standardization is the prerequisite for every downstream application - medical necessity review (with external indications data), fraud detection (with CCI edit tables), risk adjustment (with clinical severity hierarchies), and quality measurement (with standard measure definitions).
The pattern is the same across all these applications: standardize first, analyze second. OMOPHub makes the "standardize first" part fast and programmatic. The "analyze second" part requires domain-specific tools and rules - but they all work better when the vocabulary layer is clean.
Start with a real claim. Map its CPT, ICD-10, and HCPCS codes through OMOPHub. See what standard concepts come back, what hierarchies they belong to, and what relationships they have. That clinical context, invisible in the raw billing data, is where the value lives.
# Laboratory Result Mapping
Source: https://docs.omophub.com/guides/use-cases/laboratory-result-mapping
Bridging the gap from local lab-speak to LOINC. Map messy lab names to standardized codes using OMOPHub vocabulary search
## 1. The "Tower of Babel" in the Lab
You open the spreadsheet and your heart sinks. Two thousand rows of local lab test names: "S-Gluc," "Glucose, Serum," "Blood Sugar Fasting," "GLC\_RANDOM." All referring to essentially the same thing, yet each a unique string that defies simple categorization.
This is the Tower of Babel problem in healthcare data. Every lab system speaks its own dialect. And it doesn't stop at naming - a Glucose result of `90` is meaningless without its unit. Is it `90 mg/dL` (normal fasting) or `90 mmol/L` (you'd be dead)? Mismatched units aren't just a data quality issue; they're a patient safety hazard hiding in your ETL pipeline.
Traditional string matching falls apart here. "CRP\_QUANT" doesn't look like "C reactive protein \[Mass/volume] in Serum or Plasma" - but they're the same test. What you need is a way to map messy local lab names to **LOINC** (Logical Observation Identifiers Names and Codes), the international standard for lab tests, quickly and at scale.
**OMOPHub** makes the vocabulary lookup part of this problem dramatically easier. It's a REST API that gives you instant access to the full LOINC vocabulary (along with SNOMED, RxNorm, and 100+ others) via the OHDSI ATHENA standardized vocabularies. Instead of downloading multi-gigabyte vocabulary files and maintaining a local database, you search for LOINC concepts with a single API call - including fuzzy and semantic search that handles abbreviations and misspellings.
The mapping process then becomes: clean up your local names, search OMOPHub for LOINC candidates, triage by match quality, and have a human review the uncertain ones. That's it. Let's build it.
## 2. The Core Concept: The 6-Axis LOINC Model
Every lab test has six dimensions that define exactly what it measures. Get even one wrong, and you're comparing apples to oranges. LOINC captures all six:
1. **Component** - What's being measured? (Glucose, Sodium, Hemoglobin A1c)
2. **Property** - What characteristic? (Mass concentration, Substance concentration)
3. **Time** - When? (Point in time, 24-hour collection, 1-hour post-glucose challenge)
4. **System** - What specimen? (Serum, Plasma, Urine, Whole Blood)
5. **Scale** - What type of result? (Quantitative, Ordinal, Nominal)
6. **Method** - How was it measured? (Colorimetric, Immunoassay, Automated)
So "Glucose \[Mass/volume] in Serum or Plasma" is a fundamentally different LOINC concept from "Glucose \[Moles/volume] in Urine" - even though a local system might call both of them "GLUCOSE."
The challenge: local lab systems rarely provide all six axes explicitly. They give you a cryptic abbreviation like "GLUC\_FAST" and expect you to figure out the rest.
**How OMOPHub helps:** It gives you programmatic search across the full LOINC vocabulary. You pass in the messy local string, and OMOPHub returns ranked candidate LOINC concepts. Its fuzzy search handles typos and abbreviations. Its basic search supports filtering by vocabulary (`LOINC`) and domain (`Measurement`). You don't need a local vocabulary database - just an API key.
What OMOPHub does *not* do: it doesn't infer the six axes from context, perform NLP on clinical notes, or run an LLM. It's a vocabulary lookup engine. The intelligence in choosing the right LOINC code from the candidates still comes from your mapping logic and, for edge cases, your human reviewers.
## 3. Use Case A: Automated Mapping of Local Lab Catalogs
The most common headache: a new data source arrives with 2,000 local lab test names, and you need LOINC mappings for all of them.
**The Workflow:**
1. For each local lab string, search OMOPHub's LOINC vocabulary
2. Use `search.basic()` for clean names, `search.semantic()` for abbreviated/misspelled ones
3. Collect the top candidates for each local string
4. Auto-accept high-confidence matches, flag the rest for review
**Code Snippet: Mapping Local Lab Strings to LOINC Candidates**
```bash theme={null}
pip install omophub
```
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
local_lab_strings = [
"GLUCOSE_FASTING",
"CRP_QUANT",
"URINE_PROT_24HR",
"TSH",
"HEMOGLOBIN A1C",
"LIVER_PANEL", # Broader term - may match multiple LOINC codes
"NON_EXISTENT_LAB_XYZ", # Unmappable example
]
print("Automated Mapping of Local Lab Strings to LOINC:\n")
mapping_results = []
for local_string in local_lab_strings:
# Clean up underscores for better search results
search_term = local_string.replace("_", " ")
print(f" Searching for: '{local_string}' (query: '{search_term}')")
try:
# Try basic search first (best for clean, descriptive names)
results = client.search.basic(
search_term,
vocabulary_ids=["LOINC"],
domain_ids=["Measurement"],
page_size=3,
)
candidates = results.get("concepts", []) if results else []
# If basic search returns nothing, try semantic search for abbreviations
if not candidates:
semantic = client.search.semantic(search_term, vocabulary_ids=["LOINC"], domain_ids=["Measurement"], page_size=3)
candidates = (semantic.get("results", semantic.get("concepts", [])) if semantic else [])
if candidates:
for i, concept in enumerate(candidates):
c_name = concept.get("concept_name", "N/A")
c_id = concept.get("concept_id", "N/A")
c_code = concept.get("concept_code", "N/A")
rank_label = "** BEST MATCH" if i == 0 else f" candidate {i + 1}"
print(f" {rank_label}: {c_name} (ID: {c_id}, LOINC: {c_code})")
mapping_results.append({
"local_string": local_string,
"top_match": candidates[0].get("concept_name"),
"top_match_id": candidates[0].get("concept_id"),
"num_candidates": len(candidates),
"status": "auto_mapped" if len(candidates) == 1 else "needs_review",
})
else:
print(" No LOINC matches found - flagged for manual mapping")
mapping_results.append({
"local_string": local_string,
"top_match": None,
"status": "manual_mapping_required",
})
except omophub.APIError as e:
print(f" API error: {e.status_code} - {e.message}")
print()
# Summary
auto_mapped = sum(1 for r in mapping_results if r["status"] == "auto_mapped")
needs_review = sum(1 for r in mapping_results if r["status"] == "needs_review")
manual = sum(1 for r in mapping_results if r["status"] == "manual_mapping_required")
print(f"Summary: {auto_mapped} auto-mapped, {needs_review} need review, {manual} need manual mapping")
```
**The Key Insight:** This approach converts weeks of manual lookup into hours of automated search plus targeted review. The `search.basic()` call handles clean descriptive names. The `search.semantic()` fallback catches abbreviations and misspellings that basic search would miss. The result: a prioritized list of LOINC candidates for each local string, ready for human triage.
## 4. Use Case B: Unit Normalization and Value Range Validation
Once your lab tests are mapped to LOINC, the next critical question is: are the actual numeric results interpretable? A Glucose result of `90` means very different things depending on the unit.
**The Scenario:** Your data contains Glucose results in both `mg/dL` and `mmol/L`. You need to normalize everything to a single unit and flag physiologically implausible values.
**The Logic:** OMOPHub helps you confirm the LOINC concept and retrieve its metadata. The unit conversion itself is custom logic - OMOPHub is a vocabulary API, not a calculator - but knowing the exact LOINC concept tells you what units to expect.
**Code Snippet: LOINC Concept Lookup + Unit Normalization**
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
# OMOP Concept ID for "Glucose [Mass/volume] in Serum or Plasma" (LOINC: 2345-7)
loinc_glucose_id = 3016723
# Lab results to validate
lab_results = [
{"value": 90, "unit": "mg/dL"},
{"value": 5.0, "unit": "mmol/L"},
{"value": 90, "unit": "mmol/L"}, # Physiologically impossible
{"value": 0.1, "unit": "mg/dL"}, # Physiologically impossible
]
# Known conversion factors (maintained as reference data, NOT from OMOPHub)
GLUCOSE_MMOL_TO_MGDL = 18.0182 # 1 mmol/L glucose = 18.0182 mg/dL
try:
# Step 1: Confirm the LOINC concept via OMOPHub
glucose_concept = client.concepts.get(loinc_glucose_id)
concept_name = glucose_concept.get("concept_name", "Unknown")
concept_code = glucose_concept.get("concept_code", "N/A")
print(f"LOINC Concept: {concept_name} (Code: {concept_code})")
# Step 2: Optionally check relationships for unit info
relationships = client.concepts.relationships(loinc_glucose_id)
print(f" Relationships found: {len(relationships) if isinstance(relationships, list) else 'see details'}")
except omophub.APIError as e:
print(f"Could not retrieve concept: {e.message}")
# Step 3: Normalize units and validate ranges (custom logic)
standard_unit = "mg/dL"
print(f"\nNormalizing results to: {standard_unit}\n")
for result in lab_results:
value = result["value"]
unit = result["unit"]
# Convert to standard unit if needed
if unit == "mmol/L":
normalized = value * GLUCOSE_MMOL_TO_MGDL
print(f" {value} {unit} -> {normalized:.1f} {standard_unit} (converted)")
else:
normalized = value
print(f" {value} {unit} (already in standard unit)")
# Plausibility check
if normalized < 20 or normalized > 1000:
print(f" ALERT: Physiologically implausible ({normalized:.1f} {standard_unit})")
elif normalized < 70 or normalized > 200:
print(f" Warning: Outside typical normal range ({normalized:.1f} {standard_unit})")
else:
print(f" Within plausible range ({normalized:.1f} {standard_unit})")
```
**The Key Insight:** OMOPHub's role here is confirming the LOINC concept identity - making sure you're looking at the right test before you apply unit logic. The conversion factors and plausibility ranges are domain knowledge that you maintain separately (or source from clinical guidelines). The power is in the combination: OMOPHub gives you vocabulary certainty, your custom logic handles the math, and together they catch data quality issues that could otherwise corrupt downstream analyses.
## 5. The "Human-in-the-Loop" Review Workflow
Even with good search, lab mapping is never 100% automated. "CBC with Diff" might map to different LOINC codes depending on whether it's a manual or automated differential. "Liver Panel" is a composite that maps to multiple individual LOINC tests. These require human judgment.
Here's a practical tiered review workflow based on search result quality:
**Tier 1 - Auto-Accept:** Search returns exactly one strong match (the local string is essentially the LOINC name). Accept automatically. These are your easy wins.
**Tier 2 - Flag for Review:** Search returns multiple plausible candidates, or the top match is a slightly different test variant. Queue these for review by a clinical data expert. Present them with the local string, the top 3 LOINC candidates, and any context from the source system.
**Tier 3 - Manual Mapping:** Search returns no results or only irrelevant matches. These need hands-on expert mapping, often requiring institutional knowledge about what the local code actually means.
**The Review Interface:** Build a simple web app or even a spreadsheet with columns for:
* Original local lab string
* Top N LOINC candidates from OMOPHub (with concept names and codes)
* Tier assignment (auto / review / manual)
* Reviewer's selected LOINC code
* Comments/rationale
Over time, reviewed mappings become lookup rules that feed back into your ETL, reducing the manual workload with each new data source. The system gets smarter.
## 6. Conclusion: From Raw Signals to Research Insights
Lab results are the bedrock of clinical research, but they're locked behind a wall of local naming chaos and unit inconsistency. The mapping problem has always been solvable - it's just been slow.
OMOPHub makes the vocabulary lookup part fast. Instead of maintaining a local ATHENA database, you search LOINC concepts via API - with fuzzy and semantic search that handles the abbreviations and misspellings that make lab data so painful. Pair that with systematic unit normalization and a tiered human review workflow, and you've got a pipeline that turns 2,000 messy local lab codes into standardized, analysis-ready LOINC mappings.
For data engineers, that's faster ETL builds and fewer manual hours. For researchers, it's lab data you can actually trust for cohort identification, predictive modeling, and cross-institutional comparisons.
Run your messiest local lab string through the search snippet above. See what comes back. I think you'll be surprised at how far a good vocabulary API gets you.
# Lean ETL Mapping Cache
Source: https://docs.omophub.com/guides/use-cases/lean-etl-mapping-cache
Build validated mapping caches with OMOPHub during development and apply them locally at production speed - no full vocabulary load required.
## 1. The Mapping Bottleneck
Every OMOP ETL has a mapping step: take the source system's local codes and translate them to standard OMOP concept IDs. This step has two costs that people conflate but shouldn't:
**Cost 1: The one-time setup.** Figuring out what each local code maps to. "Cr\_Serum" → LOINC 2160-0. "WBC\_Count" → LOINC 6690-2. "Local\_Sepsis\_Dx" → SNOMED 91302008. This is the hard intellectual work - searching vocabularies, validating matches, handling ambiguity.
**Cost 2: The runtime application.** Applying those mappings to millions of records every night. This is a SQL JOIN or pandas merge - fast, mechanical, and local.
The mistake is conflating these two costs and loading a full Athena vocabulary database to handle both. You don't need 4GB of vocabulary tables to map 500 unique local codes. But you also can't make HTTP API calls for every row in a million-record batch.
**OMOPHub** is the right tool for Cost 1: the mapping discovery and validation phase. Search for your local codes, verify the matches, build a mapping cache. Then use that cache - locally, with no API calls - for Cost 2: the nightly production run.
This is "Lean ETL": use OMOPHub to build smart, validated mapping files during development. Apply those mappings via local lookups during production. No full vocabulary load needed for the ETL. No API latency during batch processing. Best of both worlds.
## 2. The Core Concept: The Mapping Cache Pattern
The workflow has three phases:
**Phase 1: Extract unique source codes.**
Your source data has millions of records but a much smaller set of unique codes. A hospital lab system might produce 500,000 lab results per month, but only use 800 unique local lab codes. Extract those 800 codes - that's your mapping workload.
**Phase 2: Look up each unique code via OMOPHub.**
Send each of the 800 codes to OMOPHub (search by display name, code, or fuzzy match). Get back the standard OMOP concept ID, name, vocabulary, and domain. This takes a few minutes for 800 codes. Save the results as a mapping file (CSV, JSON, or database table).
**Phase 3: Apply the mapping cache in production.**
Your nightly ETL reads the mapping file, joins it to the source data via local lookup (pandas merge, SQL JOIN, dictionary lookup), and writes the enriched records to OMOP CDM tables. Zero API calls. Full local speed.
**When to re-run Phase 2:** When new source codes appear that aren't in your mapping cache. Your ETL detects unmapped codes, queues them for OMOPHub lookup, and a human (or automated process) reviews and approves the new mappings before they enter the cache.
## 3. Use Case A: Building a Mapping Cache for a Sepsis Study
A multi-site sepsis research project receives data from four hospitals. Each hospital uses different local codes for lactate, WBC, blood cultures, and sepsis diagnoses. The ETL needs a consistent mapping from all local codes to standard OMOP concepts.
```bash theme={null}
pip install omophub
```
```python Python theme={null}
import omophub
import json
client = omophub.OMOPHub()
# --- Phase 1: Unique source codes from all hospitals ---
# In production, extract these from: SELECT DISTINCT source_code, source_display FROM source_data
unique_source_codes = [
{"source_code": "Lactate_Serum", "display": "Serum Lactate", "domain_hint": "Measurement"},
{"source_code": "WBC_Count", "display": "White Blood Cell Count", "domain_hint": "Measurement"},
{"source_code": "BldCx_Aero", "display": "Blood Culture Aerobic", "domain_hint": "Measurement"},
{"source_code": "Local_Sepsis_Dx", "display": "Sepsis Diagnosis", "domain_hint": "Condition"},
{"source_code": "PROC_CVC", "display": "Central Venous Catheter Insertion", "domain_hint": "Procedure"},
{"source_code": "INVALID_999", "display": "Unknown Local Test", "domain_hint": "Measurement"},
]
print(f"Phase 1: {len(unique_source_codes)} unique source codes to map\n")
# Domain → vocabulary mapping for targeted search
DOMAIN_VOCABS = {
"Measurement": ["LOINC"],
"Condition": ["SNOMED"],
"Procedure": ["SNOMED", "CPT4"],
"Drug": ["RxNorm"],
}
# --- Phase 2: Look up each code via OMOPHub ---
mapping_cache = []
for entry in unique_source_codes:
code = entry["source_code"]
display = entry["display"]
domain = entry["domain_hint"]
vocabs = DOMAIN_VOCABS.get(domain, [])
print(f" Looking up: '{display}' ({code})")
omop_id = None
omop_name = None
omop_vocab = None
omop_code = None
match_method = None
try:
# Step 1: Try basic search with vocabulary filter
results = client.search.basic(
display,
vocabulary_ids=vocabs,
domain_ids=[domain] if domain else [],
page_size=3,
)
candidates = results.get("concepts", []) if results else []
# Step 2: If basic search misses, try semantic
if not candidates:
semantic = client.search.semantic(display, vocabulary_ids=vocabs, domain_ids=[domain] if domain else [], page_size=3)
candidates = (semantic.get("results", semantic.get("concepts", [])) if semantic else [])
if candidates:
match_method = "semantic"
if candidates:
best = candidates[0]
omop_id = best["concept_id"]
omop_name = best.get("concept_name")
omop_vocab = best.get("vocabulary_id")
omop_code = best.get("concept_code")
match_method = match_method or "basic"
# Step 3: If non-standard, follow "Maps to" to get standard concept
if best.get("standard_concept") != "S":
std_map = client.mappings.get(omop_id, target_vocabulary=vocabs[0] if vocabs else None)
map_list = (
std_map if isinstance(std_map, list)
else std_map.get("concepts", std_map.get("mappings", []))
) if std_map else []
if map_list:
std = map_list[0]
omop_id = std["concept_id"]
omop_name = std.get("concept_name")
omop_vocab = std.get("vocabulary_id")
omop_code = std.get("concept_code")
match_method = "mapped_to_standard"
print(f" -> {omop_name} ({omop_vocab}: {omop_code}, OMOP: {omop_id}) [{match_method}]")
else:
print(f" -> NO MATCH - needs manual mapping")
match_method = "unmapped"
except omophub.APIError as e:
print(f" -> API error: {e.message}")
match_method = "error"
mapping_cache.append({
"source_code": code,
"source_display": display,
"domain_hint": domain,
"omop_concept_id": omop_id,
"omop_concept_name": omop_name,
"omop_vocabulary_id": omop_vocab,
"omop_concept_code": omop_code,
"match_method": match_method,
"reviewed": False, # Flag for human review
})
# --- Save the mapping cache ---
print(f"\n--- Mapping Cache ({len(mapping_cache)} entries) ---")
mapped = sum(1 for m in mapping_cache if m["omop_concept_id"])
unmapped = len(mapping_cache) - mapped
print(f" Mapped: {mapped} | Unmapped: {unmapped}")
# In production, save to CSV or database:
# pd.DataFrame(mapping_cache).to_csv("sepsis_mapping_cache.csv", index=False)
# Print for review
for m in mapping_cache:
status = f"OMOP {m['omop_concept_id']}" if m["omop_concept_id"] else "UNMAPPED"
print(f" {m['source_code']:20s} -> {status:>12s} [{m['match_method']}]")
```
**The Key Insight:** This script runs once during ETL development - not every night. It produces a mapping file that the production ETL uses as a local lookup. If tomorrow's data batch contains a new source code not in the cache, the ETL logs it as unmapped and queues it for a new OMOPHub lookup + human review. The cache grows over time until it covers all source codes. OMOPHub calls drop to near-zero in steady state.
## 4. Use Case B: Applying the Mapping Cache in Production
Once you have the mapping cache, the nightly ETL is pure local processing - no API calls.
```python Python theme={null}
import pandas as pd
# --- Production ETL: Apply the mapping cache ---
# Step 1: Load the mapping cache (built via OMOPHub in Phase 2)
mapping_cache = pd.DataFrame([
{"source_code": "Lactate_Serum", "omop_concept_id": 3047181, "omop_concept_name": "Lactate [Moles/volume] in Blood"},
{"source_code": "WBC_Count", "omop_concept_id": 3000905, "omop_concept_name": "Leukocytes [#/volume] in Blood"},
{"source_code": "BldCx_Aero", "omop_concept_id": 3016407, "omop_concept_name": "Blood culture"},
{"source_code": "Local_Sepsis_Dx", "omop_concept_id": 132797, "omop_concept_name": "Sepsis"},
{"source_code": "PROC_CVC", "omop_concept_id": 4180032, "omop_concept_name": "Insertion of central venous catheter"},
# "INVALID_999" intentionally absent - unmapped
])
# Step 2: Load today's source data batch (in production: millions of rows from EHR extract)
source_batch = pd.DataFrame([
{"patient_id": "P001", "source_code": "Lactate_Serum", "value": 2.5, "datetime": "2025-02-01 09:00"},
{"patient_id": "P001", "source_code": "WBC_Count", "value": 15.2, "datetime": "2025-02-01 09:00"},
{"patient_id": "P002", "source_code": "Lactate_Serum", "value": 4.1, "datetime": "2025-02-01 10:30"},
{"patient_id": "P002", "source_code": "Local_Sepsis_Dx", "value": None, "datetime": "2025-02-01 10:30"},
{"patient_id": "P003", "source_code": "INVALID_999", "value": 99, "datetime": "2025-02-01 11:00"},
])
print(f"Production ETL: {len(source_batch)} records to process\n")
# Step 3: Merge source data with mapping cache - pure local operation, zero API calls
enriched = source_batch.merge(
mapping_cache[["source_code", "omop_concept_id", "omop_concept_name"]],
on="source_code",
how="left",
)
# Step 4: Split into mapped and unmapped
mapped_records = enriched[enriched["omop_concept_id"].notna()]
unmapped_records = enriched[enriched["omop_concept_id"].isna()]
print(f" Mapped: {len(mapped_records)} records -> ready for OMOP CDM load")
print(f" Unmapped: {len(unmapped_records)} records -> queued for OMOPHub lookup\n")
if not unmapped_records.empty:
new_codes = unmapped_records["source_code"].unique()
print(f" New unmapped codes to look up via OMOPHub: {list(new_codes)}")
# The mapped records are ready for OMOP CDM insertion
# (measurement table for labs, condition_occurrence for diagnoses, etc.)
print(f"\n--- Sample Output ---")
print(mapped_records[["patient_id", "source_code", "omop_concept_id", "value"]].to_string(index=False))
```
**The Key Insight:** The production ETL is a pandas merge - milliseconds for millions of rows. No HTTP calls, no API latency, no rate limits, no network dependency. OMOPHub was used once (during development) to build the mapping cache. The cache is the artifact that persists. This is the actual "Lean ETL" - lean on runtime resources because the vocabulary work was done upfront.
## 5. When New Codes Appear
The mapping cache isn't static. As hospitals add new tests or change local codes, unmapped codes appear. The workflow:
1. **ETL detects unmapped codes** (the `unmapped_records` in Use Case B)
2. **Queue the new codes** for OMOPHub lookup
3. **Run the Phase 2 script** (Use Case A) on just the new codes
4. **Human reviews** the suggested mappings (critical for data quality)
5. **Append approved mappings** to the cache
6. **Re-run the ETL** for the previously unmapped records
This keeps the cache growing and OMOPHub calls shrinking. In a mature ETL, you might go months without needing a new OMOPHub lookup.
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
def lookup_new_codes(new_codes, mapping_cache_path="sepsis_mapping_cache.csv"):
"""Look up unmapped codes via OMOPHub and suggest mappings for review."""
print(f"\nLooking up {len(new_codes)} new unmapped codes...\n")
suggestions = []
for code_info in new_codes:
code = code_info["source_code"]
display = code_info.get("display", code)
try:
# Step 1: Search OMOPHub for candidates
results = client.search.basic(display, page_size=3)
candidates = results.get("concepts", []) if results else []
if candidates:
for i, c in enumerate(candidates[:3]):
suggestions.append({
"source_code": code,
"source_display": display,
"suggestion_rank": i + 1,
"omop_concept_id": c["concept_id"],
"omop_concept_name": c.get("concept_name"),
"omop_vocabulary_id": c.get("vocabulary_id"),
"approved": False, # Human must approve
})
print(f" {code}: Suggestion {i+1}: {c.get('concept_name')} ({c.get('vocabulary_id')})")
else:
print(f" {code}: No suggestions found - needs manual mapping")
suggestions.append({
"source_code": code,
"source_display": display,
"suggestion_rank": None,
"omop_concept_id": None,
"omop_concept_name": "MANUAL MAPPING REQUIRED",
"approved": False,
})
except omophub.APIError as e:
print(f" {code}: API error - {e.message}")
return suggestions
# Example: codes that failed mapping in today's ETL run
new_unmapped = [
{"source_code": "INVALID_999", "display": "Unknown Local Test"},
{"source_code": "BNP_Plasma", "display": "Brain Natriuretic Peptide"},
]
suggestions = lookup_new_codes(new_unmapped)
```
## 6. Conclusion: OMOPHub for Discovery, Local Cache for Production
The "Compute Tax" isn't loading vocabulary tables - it's doing vocabulary lookups at runtime instead of build time. The lean approach:
* **Build time:** Use OMOPHub to discover and validate mappings for your unique source codes. Takes minutes. Run it once per new site or when new codes appear.
* **Runtime:** Apply the mapping cache via local pandas merge or SQL JOIN. Takes milliseconds per million records. No API calls. No network dependency.
OMOPHub's value isn't replacing your local vocabulary - it's making the mapping discovery phase fast and accessible without requiring a full Athena installation. Once the mappings are built, they live locally. The ETL stays lean because the vocabulary intelligence was front-loaded.
Start with your messiest source system. Extract the unique codes. Run them through OMOPHub. Build the mapping cache. Plug it into your ETL. Measure the difference.
# Phenotype Development & Concept Set Expansion
Source: https://docs.omophub.com/guides/use-cases/phenotype-development
Uncovering the hidden codes of clinical research. Build comprehensive concept sets using OMOPHub hierarchy, mapping, and semantic search APIs
## 1. The "Hidden Code" Problem
You'd think defining "Type 2 Diabetes" in a database would be simple. Look for ICD-10 code E11. Done, right?
Not even close. In OMOP CDM data, Type 2 Diabetes is a sprawling web of concepts: the top-level SNOMED code, dozens of specific subtypes ("Type 2 DM with renal complications," "Type 2 DM with peripheral angiopathy"), equivalent ICD-10 codes, related lab measurements (HbA1c in LOINC), medications (Metformin, insulin glargine in RxNorm), and procedures. Miss even one obscure code and your research cohort is undercounted. One major study found their diabetes phenotype captured 23% fewer patients when they used a narrow concept set versus a properly expanded one.
This is the omission bias problem - and it's endemic in observational research. The codes are there in the vocabulary. You just can't find them all manually.
**Concept set expansion** - the process of systematically identifying every relevant concept ID for your phenotype - is the solution. It works in two dimensions:
* **Vertical (hierarchical):** Follow the parent-child tree. "Type 2 Diabetes Mellitus" has dozens of SNOMED descendants. You want all of them.
* **Horizontal (cross-vocabulary):** Map your SNOMED concepts to their ICD10CM, LOINC, and RxNorm equivalents, so your phenotype catches patients regardless of which coding system their data uses.
**OMOPHub** is a vocabulary API that makes both of these fast. Its hierarchy API traverses ancestor/descendant relationships in a single call. Its mappings API resolves concepts across vocabularies. And its search API (including fuzzy and semantic search) helps you discover concepts you didn't know to look for. No local vocabulary database required - just API calls against the full OHDSI ATHENA vocabulary.
## 2. The Core Concept: Hierarchical vs. Semantic Expansion
When building a concept set, you're doing two kinds of work:
**Hierarchical expansion** follows the vocabulary's built-in structure. In SNOMED, "Atrial Fibrillation" has child concepts like "Paroxysmal atrial fibrillation," "Persistent atrial fibrillation," and "Chronic atrial fibrillation." OMOPHub's `hierarchy.descendants()` walks this tree for you - specify a concept ID, set the depth, and get back every descendant. This is the "vertical" dimension: going deeper into specificity within a single vocabulary.
**Cross-vocabulary mapping** is the "horizontal" dimension. Your phenotype might be defined in SNOMED, but your billing data uses ICD-10-CM. OMOPHub's `mappings.get()` finds the "Maps to" relationships between vocabularies, ensuring your phenotype works regardless of how the data was originally coded.
**Semantic search** adds a discovery layer. If you search OMOPHub for "atrial fibrillation," the semantic and fuzzy search methods can surface related concepts you might not have thought to include - terms worded differently, abbreviations, or clinically adjacent concepts that share a vocabulary neighborhood. This isn't AI "understanding" the clinical meaning; it's smart text matching against a comprehensive vocabulary index. But it's remarkably effective at catching concepts that pure hierarchy traversal would miss.
OMOPHub handles the vocabulary lookup. The clinical judgment - deciding which concepts belong in your phenotype and which don't - still belongs to you.
## 3. Use Case A: Rapid Concept Set Bootstrapping (From a Single Seed)
A researcher starts with a single concept and needs a comprehensive concept set. Here's how to bootstrap it.
**The Scenario:** You're building an Atrial Fibrillation phenotype. You know the SNOMED concept name but need to find (a) all its SNOMED descendants and (b) related concepts you might be missing.
**Code Snippet: Expanding a Concept Set from a Seed**
```bash theme={null}
pip install omophub
```
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
# Step 1: Find the seed concept's OMOP concept ID
# IMPORTANT: OMOPHub uses OMOP concept IDs, not SNOMED codes directly.
# SNOMED code 49436004 = "Atrial fibrillation", but the OMOP concept ID is different.
# Always look it up first.
seed_search = client.search.basic(
"Atrial fibrillation",
vocabulary_ids=["SNOMED"],
domain_ids=["Condition"],
page_size=3,
)
seed_candidates = seed_search.get("concepts", []) if seed_search else []
if not seed_candidates:
print("Could not find seed concept. Check spelling or vocabulary.")
else:
seed = seed_candidates[0]
seed_id = seed["concept_id"]
seed_name = seed.get("concept_name", "Unknown")
seed_code = seed.get("concept_code", "N/A")
print(f"Seed: {seed_name} (OMOP ID: {seed_id}, SNOMED: {seed_code})\n")
# Collect all expanded concepts
expanded = {seed_id: seed_name}
# Step 2: Hierarchical expansion - get all descendants
print("--- Hierarchical Expansion (Descendants) ---")
try:
descendants = client.hierarchy.descendants(
seed_id,
max_levels=5,
relationship_types=["Is a"],
)
desc_list = (
descendants if isinstance(descendants, list)
else descendants.get("concepts", [])
)
for desc in desc_list:
d_id = desc["concept_id"]
d_name = desc.get("concept_name", "Unknown")
expanded[d_id] = d_name
print(f" - {d_name} (ID: {d_id})")
if not desc_list:
print(" No descendants found.")
except omophub.APIError as e:
print(f" Hierarchy API error: {e.message}")
# Step 3: Semantic search - discover related concepts
# This catches concepts that aren't hierarchical children but are
# clinically adjacent (e.g., "Atrial flutter" near "Atrial fibrillation")
print("\n--- Semantic Search (Discovery) ---")
try:
semantic_results = client.search.semantic("atrial fibrillation related arrhythmia")
sem_list = (
semantic_results if isinstance(semantic_results, list)
else semantic_results.get("concepts", [])
) if semantic_results else []
# Filter to Condition domain, exclude concepts already found
new_finds = 0
for concept in sem_list:
c_id = concept.get("concept_id")
if c_id and c_id not in expanded and concept.get("domain_id") == "Condition":
c_name = concept.get("concept_name", "Unknown")
c_vocab = concept.get("vocabulary_id", "N/A")
expanded[c_id] = c_name
new_finds += 1
print(f" - {c_name} (ID: {c_id}, Vocab: {c_vocab})")
if new_finds == 0:
print(" No additional concepts found via semantic search.")
except omophub.APIError as e:
print(f" Semantic search error: {e.message}")
# Summary
print(f"\n--- Expanded Concept Set ---")
print(f"Total concepts: {len(expanded)} (1 seed + {len(expanded) - 1} expanded)")
print(f"First 10: {list(expanded.values())[:10]}")
```
**The Key Insight:** The hierarchy traversal is the workhorse - it systematically captures every subtype. The semantic search is the scout - it surfaces concepts in adjacent branches or different vocabularies that hierarchy alone would miss. Together, they bootstrap a concept set in seconds that would take hours of manual ATHENA browsing.
## 4. Use Case B: Cross-Vocabulary Mapping for Phenotype Validation
Your SNOMED-based phenotype needs to work against ICD-10-CM billing data. If there's no mapping, you're leaking patients.
**The Scenario:** You have four SNOMED concepts defining a cardiovascular phenotype. You need their ICD-10-CM equivalents to validate against billing claims.
**Code Snippet: Cross-Vocabulary Mapping**
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
# Core phenotype concepts (OMOP concept IDs - verify these via search)
# In production, look these up rather than hardcoding.
phenotype_concepts = [
{"name": "Atrial fibrillation", "search_term": "Atrial fibrillation"},
{"name": "Myocardial infarction", "search_term": "Myocardial infarction"},
{"name": "Hypertensive disorder", "search_term": "Hypertensive disorder"},
{"name": "Chronic kidney disease", "search_term": "Chronic kidney disease"},
]
print("Cross-Vocabulary Mapping: SNOMED -> ICD-10-CM\n")
all_icd10_codes = set()
for entry in phenotype_concepts:
# Step 1: Resolve to OMOP concept ID via search
try:
results = client.search.basic(
entry["search_term"],
vocabulary_ids=["SNOMED"],
domain_ids=["Condition"],
page_size=1,
)
candidates = results.get("concepts", []) if results else []
if not candidates:
print(f" {entry['name']}: No SNOMED match found. Skipping.")
continue
concept = candidates[0]
omop_id = concept["concept_id"]
snomed_code = concept.get("concept_code", "N/A")
print(f" {concept.get('concept_name', entry['name'])} (OMOP: {omop_id}, SNOMED: {snomed_code})")
# Step 2: Get ICD-10-CM mappings
mappings = client.mappings.get(omop_id, target_vocabulary="ICD10CM")
mapping_list = (
mappings if isinstance(mappings, list)
else mappings.get("concepts", mappings.get("mappings", []))
) if mappings else []
if mapping_list:
for m in mapping_list:
icd_name = m.get("concept_name", "N/A")
icd_code = m.get("concept_code", "N/A")
all_icd10_codes.add(icd_code)
print(f" -> ICD-10-CM: {icd_name} ({icd_code})")
else:
print(f" -> No direct ICD-10-CM mapping found")
except omophub.APIError as e:
print(f" {entry['name']}: API error - {e.message}")
print()
print(f"Total unique ICD-10-CM codes for phenotype: {len(all_icd10_codes)}")
print(f"Codes: {sorted(all_icd10_codes)}")
```
**The Key Insight:** This is the "leak check" for your phenotype. If your study database uses ICD-10-CM for billing data and your phenotype is defined only in SNOMED, you'll miss every patient who was coded only in ICD-10. By mapping each SNOMED concept to its ICD-10-CM equivalents, you ensure the phenotype captures patients regardless of the coding system - and you can spot concepts with no cross-vocabulary mapping that might need manual attention.
## 5. Exploring Concept Relationships for Phenotype Refinement
Beyond hierarchy and cross-vocabulary mapping, OMOP concepts have rich relationships to each other: "Has associated finding," "Has causative agent," "Occurs after," and more. Traversing these relationships helps you discover concepts that are clinically relevant but outside the hierarchical tree.
**The Idea:** For a seed concept, explore its relationships to find clinically related concepts in other domains. A condition might have associated measurements (LOINC), treatments (RxNorm), or complications (SNOMED) linked through OMOP relationships.
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
# Find the seed concept (Type 2 Diabetes Mellitus)
seed_results = client.search.basic(
"Type 2 diabetes mellitus",
vocabulary_ids=["SNOMED"],
domain_ids=["Condition"],
page_size=1,
)
if seed_results and seed_results.get("concepts"):
seed = seed_results["concepts"][0]
seed_id = seed["concept_id"]
print(f"Seed: {seed.get('concept_name')} (ID: {seed_id})\n")
# Get all relationships for this concept
try:
relationships = client.concepts.relationships(seed_id)
rel_list = (
relationships if isinstance(relationships, list)
else relationships.get("relationships", [])
) if relationships else []
print(f"Found {len(rel_list)} relationships:\n")
# Group by relationship type for clarity
by_type = {}
for rel in rel_list:
rel_type = rel.get("relationship_id", "Unknown")
if rel_type not in by_type:
by_type[rel_type] = []
by_type[rel_type].append(rel)
for rel_type, rels in sorted(by_type.items()):
print(f" {rel_type} ({len(rels)} concepts):")
for rel in rels[:5]: # Show up to 5 per type
r_name = rel.get("concept_name", "Unknown")
r_id = rel.get("concept_id", "N/A")
r_vocab = rel.get("vocabulary_id", "N/A")
print(f" - {r_name} (ID: {r_id}, Vocab: {r_vocab})")
if len(rels) > 5:
print(f" ... and {len(rels) - 5} more")
print()
except omophub.APIError as e:
print(f"Relationship lookup error: {e.message}")
else:
print("Could not find seed concept.")
```
**The Key Insight:** OMOP's relationship graph is an underused goldmine for phenotype development. A single concept can have dozens of relationships to associated findings, complications, and treatments. Traversing these relationships - rather than relying solely on hierarchy - helps build multi-domain phenotypes that capture the full clinical picture. For example, a diabetes phenotype should include not just condition codes, but also HbA1c measurements (LOINC) and antidiabetic medications (RxNorm). OMOPHub's concept relationship API makes this exploration programmatic.
**A note on Phoebe:** The OHDSI community has developed the Phoebe algorithm, which recommends concepts based on co-occurrence patterns in real-world OMOP data. If OMOPHub exposes Phoebe functionality (check their latest documentation), it would complement the relationship-based exploration shown above with data-driven recommendations. Phoebe is particularly valuable for identifying concepts that are empirically associated with your phenotype but not linked through formal vocabulary relationships.
## 6. Conclusion: Building Better Cohorts, Faster
Phenotype quality determines research quality. An incomplete concept set means an incomplete cohort, which means biased results. The traditional approach - manually browsing ATHENA, relying on expert intuition, hoping you haven't missed a code - doesn't scale.
OMOPHub makes the vocabulary mechanics fast: hierarchy traversal to capture all subtypes, cross-vocabulary mapping to ensure portability, semantic search to discover concepts you didn't know to look for, and relationship exploration to build multi-domain phenotypes. What used to take days of manual vocabulary work becomes a set of API calls.
The clinical judgment - deciding which concepts belong in your phenotype and which don't - still requires human expertise. But OMOPHub gives that expertise a comprehensive starting set to work with, rather than a handful of codes pulled from memory.
Start with your favorite condition. Search for it, expand its descendants, map it to ICD-10-CM, and explore its relationships. You'll almost certainly discover concepts you would have missed manually. That's the difference between a phenotype that works and one that leaks.
# Population Health Analytics
Source: https://docs.omophub.com/guides/use-cases/population-health-analytics
From data points to public health action. Analyze population health trends using OMOPHub hierarchy expansion, cross-vocabulary mapping, and OMOP CDM SQL queries
## 1. From Data Points to Public Health
12,000 patients with a diabetes diagnosis in your health system. How many of them had an HbA1c test in the last 12 months? If you can't answer that question in under a minute, you have a population health problem.
The challenge isn't data - it's aggregation. A "Diabetes" diagnosis in one clinic is coded as a specific ICD-10 subtype in billing, a SNOMED concept in the EHR, and possibly a local code in a legacy system. Counting "all diabetics" means resolving hundreds of concept codes across vocabularies into a single, comprehensive set. Do the same for HbA1c measurements. Then query across millions of records. Manually, this takes weeks. By the time you have your answer, the care gap has widened.
**OMOPHub** makes the vocabulary resolution fast. It's a REST API for the OHDSI ATHENA vocabularies - SNOMED, ICD-10, LOINC, RxNorm, and 100+ others. Its hierarchy API lets you take a broad concept like "Type 2 Diabetes Mellitus" and retrieve every descendant code in a single call - all the specific subtypes, complications, and variants. Its search API handles fuzzy and semantic matching for disparate local names. Its mappings API resolves across vocabularies.
The pattern is simple: define your clinical concept at a high level, expand it into a comprehensive concept set via OMOPHub, and plug that set into your OMOP CDM SQL queries. That turns population health questions into answerable queries - and care gaps into actionable lists.
## 2. The Core Concept: Rolling Up the Hierarchy
Population health analytics lives and dies on aggregation. You don't want to count 500 individual respiratory infection codes separately - you want to count "Respiratory Infections" as a category, knowing it captures everything underneath.
The OMOP vocabulary hierarchy is built for this. Every specific concept has parent concepts linked by "Is a" relationships, forming a tree. "Streptococcal pneumonia" **Is a** "Bacterial pneumonia" **Is a** "Pneumonia" **Is a** "Respiratory tract infection." If you query for the parent, and include all its descendants, you catch everything.
OMOPHub's `hierarchy.descendants()` does this traversal via API. Give it a concept ID and a depth, and it returns every child, grandchild, and great-grandchild in the hierarchy. This is the "roll-up" - and it's the foundation of every use case in this article.
It works at any level of granularity:
* **Broad:** "Cardiovascular disease" → hundreds of descendant conditions
* **Medium:** "Type 2 Diabetes Mellitus" → dozens of specific subtypes
* **Narrow:** "Atrial fibrillation" → a handful of specific variants
This multi-level capability means you can build dashboards that start at the disease class level, then drill down to specific conditions, then to individual patients - all driven by the same hierarchy API.
## 3. Use Case A: Identifying Gaps in Care (The "Diabetes Screening" Gap)
The most actionable application of population health: finding patients who should be getting care but aren't.
**The Scenario:** Your health system wants to identify all patients with a Diabetes diagnosis who haven't had an HbA1c test in the last 12 months. These patients are your outreach targets.
**The Workflow:** Use OMOPHub to build two concept sets (all Diabetes condition codes, all HbA1c measurement codes), then plug them into an OMOP CDM SQL query.
```bash theme={null}
pip install omophub
```
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
# OMOP concept IDs for our parent concepts
# 201826 = Type 2 diabetes mellitus (SNOMED, standard concept)
# 3004410 = Hemoglobin A1c (LOINC measurement - verify via OMOPHub search)
dia_parent_id = 201826
hba1c_parent_id = 3004410
print("Building concept sets for care gap analysis...\n")
# --- Build Diabetes concept set ---
diabetes_ids = {dia_parent_id}
try:
dia_desc = client.hierarchy.descendants(dia_parent_id, max_levels=5, relationship_types=["Is a"])
desc_list = dia_desc if isinstance(dia_desc, list) else dia_desc.get("concepts", [])
for d in desc_list:
diabetes_ids.add(d["concept_id"])
print(f" Diabetes concept set: {len(diabetes_ids)} concepts (parent + descendants)")
except omophub.APIError as e:
print(f" Error expanding diabetes concepts: {e.message}")
# --- Build HbA1c concept set ---
hba1c_ids = {hba1c_parent_id}
try:
hba1c_desc = client.hierarchy.descendants(hba1c_parent_id, max_levels=3, relationship_types=["Is a"])
desc_list = hba1c_desc if isinstance(hba1c_desc, list) else hba1c_desc.get("concepts", [])
for d in desc_list:
hba1c_ids.add(d["concept_id"])
print(f" HbA1c concept set: {len(hba1c_ids)} concepts (parent + descendants)")
except omophub.APIError as e:
print(f" Error expanding HbA1c concepts: {e.message}")
# --- Generate the OMOP CDM SQL ---
dia_ids_str = ", ".join(str(i) for i in diabetes_ids)
hba1c_ids_str = ", ".join(str(i) for i in hba1c_ids)
print("\n--- OMOP CDM SQL: Patients with Diabetes but No Recent HbA1c ---\n")
print(f"""SELECT p.person_id, p.gender_concept_id, p.year_of_birth
FROM person p
JOIN condition_occurrence co ON p.person_id = co.person_id
WHERE co.condition_concept_id IN ({dia_ids_str})
AND NOT EXISTS (
SELECT 1
FROM measurement m
WHERE m.person_id = p.person_id
AND m.measurement_concept_id IN ({hba1c_ids_str})
AND m.measurement_date >= DATEADD(month, -12, GETDATE())
);""")
print("\n-- Note: Date syntax varies by database engine.")
print("-- SQL Server: DATEADD(month, -12, GETDATE())")
print("-- PostgreSQL: CURRENT_DATE - INTERVAL '12 months'")
print("-- MySQL: DATE_SUB(CURRENT_DATE(), INTERVAL 12 MONTH)")
```
**The Key Insight:** This is OMOPHub doing exactly what it's built for. The hierarchy expansion ensures your Diabetes concept set catches patients coded as "Type 2 DM with renal complications," "Type 2 DM with peripheral angiopathy," or any other specific subtype - not just the top-level code. Without this expansion, you'd systematically undercount your care gap population. The concept set feeds directly into standard OMOP CDM SQL, making the entire workflow reproducible and auditable.
## 4. Use Case B: Disease Surveillance Across Heterogeneous Sources
Public health surveillance requires aggregating data from dozens of hospitals, each with different coding practices. The goal: detect trends in broad disease categories regardless of how the source data was coded.
**The Scenario:** You're monitoring "Flu-like Illness" across 50 hospitals. Some report in ICD-10-CM, some in SNOMED, and some use local codes. You need a single surveillance count.
**The Workflow:**
1. Build a surveillance concept set for "Respiratory tract infection" by expanding the hierarchy
2. For incoming standard codes (ICD-10, SNOMED), look them up in OMOPHub and check if they're in the set
3. For local/proprietary codes, flag for manual mapping (OMOPHub can't resolve these)
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
# Incoming diagnosis events from various hospitals
incoming_events = [
{"code": "J11.1", "vocab": "ICD10CM", "hospital": "Hospital A"},
{"code": "J06.9", "vocab": "ICD10CM", "hospital": "Hospital B"},
{"code": "FLU_SYMPT", "vocab": "LOCAL", "hospital": "Hospital C"},
{"code": "233604007", "vocab": "SNOMED", "hospital": "Hospital D"},
]
# Step 1: Build surveillance concept set via hierarchy
# First, find the OMOP concept ID for "Respiratory tract infection"
print("Building surveillance concept set...\n")
search_result = client.search.basic(
"Infection of respiratory tract",
vocabulary_ids=["SNOMED"],
domain_ids=["Condition"],
page_size=1,
)
candidates = search_result.get("concepts", []) if search_result else []
if not candidates:
print("Could not find parent concept for respiratory tract infection.")
else:
parent = candidates[0]
parent_id = parent["concept_id"]
print(f" Parent: {parent.get('concept_name')} (OMOP ID: {parent_id})")
# Expand to all descendants
surveillance_ids = {parent_id}
try:
descendants = client.hierarchy.descendants(parent_id, max_levels=5, relationship_types=["Is a"])
desc_list = descendants if isinstance(descendants, list) else descendants.get("concepts", [])
for d in desc_list:
surveillance_ids.add(d["concept_id"])
print(f" Surveillance set: {len(surveillance_ids)} concepts\n")
except omophub.APIError as e:
print(f" Error expanding hierarchy: {e.message}\n")
# Step 2: Classify incoming events
print("Classifying incoming diagnosis events:\n")
flu_count = 0
unresolved = 0
for event in incoming_events:
code = event["code"]
vocab = event["vocab"]
hospital = event["hospital"]
if vocab == "LOCAL":
# OMOPHub cannot resolve proprietary local codes
print(f" {hospital}: '{code}' ({vocab}) -> UNRESOLVED (local code, needs manual mapping)")
unresolved += 1
continue
# Look up the standard code in OMOPHub
try:
results = client.search.basic(
code,
vocabulary_ids=[vocab],
domain_ids=["Condition"],
page_size=1,
)
matches = results.get("concepts", []) if results else []
if matches:
matched = matches[0]
omop_id = matched["concept_id"]
c_name = matched.get("concept_name", "Unknown")
if omop_id in surveillance_ids:
print(f" {hospital}: '{code}' ({vocab}) -> {c_name} -> FLAGGED as flu-like illness")
flu_count += 1
else:
print(f" {hospital}: '{code}' ({vocab}) -> {c_name} -> Not in surveillance set")
else:
print(f" {hospital}: '{code}' ({vocab}) -> No match found")
unresolved += 1
except omophub.APIError as e:
print(f" {hospital}: '{code}' ({vocab}) -> API error: {e.message}")
unresolved += 1
print(f"\nSurveillance Summary:")
print(f" Flu-like illness events: {flu_count}")
print(f" Unresolved events: {unresolved} (need manual review)")
```
**The Key Insight:** The hierarchy-based surveillance set is the foundation - it defines exactly which concepts count as "flu-like illness." Incoming standard codes get resolved to OMOP concept IDs and checked against this set. Local codes can't be resolved automatically - that's an honest limitation. In production, you'd maintain a local-to-standard mapping table built over time with human review. The result: a single surveillance count across heterogeneous sources, updated as fast as data arrives.
## 5. Risk Stratification: Beyond Simple Counts
Two patients both have "Type 2 Diabetes." One is well-controlled with no complications. The other has diabetic retinopathy, chronic kidney disease, and a prior MI. They need very different levels of care - but a simple count treats them identically.
Risk stratification means identifying which patients carry comorbidities and complications that increase their risk. OMOPHub helps by building complication concept sets programmatically.
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
# A patient's condition concept IDs (from their OMOP record)
patient_conditions = {201826, 40484648, 4329847, 443727, 40480853}
# Note: verify all IDs via OMOPHub search before production use
# Define complication categories to check
# Instead of hardcoding IDs, search for the parent concept and expand
complication_categories = [
"Diabetic retinopathy",
"Diabetic nephropathy",
"Chronic kidney disease",
"Myocardial infarction",
]
print("Risk Stratification: Checking for diabetes complications\n")
risk_score = 0
identified_complications = []
for comp_name in complication_categories:
try:
results = client.search.basic(
comp_name,
vocabulary_ids=["SNOMED"],
domain_ids=["Condition"],
page_size=1,
)
candidates = results.get("concepts", []) if results else []
if not candidates:
print(f" '{comp_name}': Not found in SNOMED")
continue
comp_concept = candidates[0]
comp_id = comp_concept["concept_id"]
# Expand to include subtypes
comp_set = {comp_id}
try:
desc = client.hierarchy.descendants(comp_id, max_levels=3, relationship_types=["Is a"])
desc_list = desc if isinstance(desc, list) else desc.get("concepts", [])
for d in desc_list:
comp_set.add(d["concept_id"])
except omophub.APIError:
pass # Use just the parent if hierarchy fails
# Check if patient has any concept in this complication set
overlap = patient_conditions & comp_set
if overlap:
risk_score += 1
identified_complications.append(comp_concept.get("concept_name", comp_name))
print(f" PRESENT: {comp_concept.get('concept_name', comp_name)} ({len(comp_set)} concepts in set, {len(overlap)} matched)")
else:
print(f" Absent: {comp_concept.get('concept_name', comp_name)} ({len(comp_set)} concepts checked)")
except omophub.APIError as e:
print(f" '{comp_name}': API error - {e.message}")
print(f"\nRisk Summary:")
print(f" Complications found: {len(identified_complications)} of {len(complication_categories)}")
print(f" Complications: {', '.join(identified_complications) if identified_complications else 'None'}")
print(f" Risk tier: {'HIGH' if risk_score >= 2 else 'MODERATE' if risk_score == 1 else 'LOW'}")
```
**The Key Insight:** The hierarchy expansion makes risk stratification robust. Instead of checking for a single concept ID (which would miss patients coded with a specific subtype), you check against an expanded set. A patient coded as "Stage 3 chronic kidney disease" gets caught by the CKD complication check because it's a descendant of the parent CKD concept. This is the same hierarchy roll-up from Use Case A, applied to a different question - and it's the pattern that makes OMOPHub genuinely useful for population health.
## 6. Conclusion: From Reactive to Proactive
Population health analytics comes down to answering simple questions at scale: Who has diabetes but isn't being monitored? Is flu season hitting harder this year than last? Which patients are at highest risk for complications?
The vocabulary complexity is what makes these questions hard. OMOPHub handles the vocabulary layer - expanding concept hierarchies, resolving across vocabularies, looking up codes - so you can focus on the clinical logic and the actionable insights.
The pattern across all three use cases is the same: define a clinical concept, expand it into a comprehensive concept set via OMOPHub's hierarchy API, and use that set to query your OMOP CDM database. Whether you're finding care gaps, running surveillance, or stratifying risk, the vocabulary machinery is identical. That's the power of standardization.
Start with Use Case A. Pick a quality measure that matters to your organization - HbA1c for diabetics, colonoscopy for age-eligible patients, statin therapy for cardiovascular risk. Build the concept sets, write the SQL, and see what falls out. The care gaps are there. OMOPHub helps you find them.
# Vocabulary Lifecycle Management
Source: https://docs.omophub.com/guides/use-cases/vocabulary-lifecycle-management
Automate detection of stale vocabularies and deprecated concepts with OMOPHub, so you update from Athena only when it matters.
## 1. The "Maintenance Tax"
Every OMOP implementation has a dirty secret: the vocabulary tables. They're the backbone of everything - every concept ID, every mapping, every hierarchy traversal depends on them. And they go stale.
SNOMED releases twice a year. RxNorm updates monthly. ICD-10-CM gets annual revisions. LOINC adds new lab codes quarterly. Each release can add concepts, deprecate others, change mappings, or restructure hierarchies. If your local vocabulary tables are from six months ago, your ETL is mapping against an outdated reality. A new drug approved in March doesn't exist in your January vocabulary. A SNOMED concept that was merged in the April release is still two separate concepts in your system.
This is **Version Drift** - and the manual process of fixing it is the **Maintenance Tax**: download multi-gigabyte Athena files, load them into your database (hours to days), re-run ETL validation, hope nothing broke. It's a lost weekend every quarter, and most teams put it off because the operational cost is so high.
**OMOPHub** doesn't eliminate the need for local vocabulary tables - your OMOP CDM requires them for performant SQL joins. But it provides a **fast, always-current API** for vocabulary lookups that complements your local installation in specific, high-value ways:
* **Version checking:** Is your local SNOMED current, or has a new release dropped?
* **Concept validation:** Is this concept ID still active, or was it deprecated?
* **Ad-hoc resolution:** During development, look up a concept without querying your local database
* **Gap detection:** Find concepts in the latest vocabulary that don't exist in your local installation
Think of OMOPHub as the "weather check" for your vocabulary - you still need a roof (local tables), but you want to know when a storm is coming (vocabulary updates that could affect your data).
## 2. The Core Concept: When to Use OMOPHub vs. Local Vocabularies
This distinction matters. Getting it wrong leads to either operational fragility (over-reliance on API) or stale data (ignoring updates).
**Use local vocabulary tables when:**
* Running production ETL on millions of records (local SQL joins are orders of magnitude faster than API calls)
* Executing OMOP CDM queries that join against `concept`, `concept_relationship`, or `concept_ancestor` tables
* Reproducing research results (you need a fixed vocabulary version, not a live API that might change)
* Any workflow where performance and reliability are critical
**Use OMOPHub API when:**
* Checking if your local vocabularies are current
* Validating individual concepts during development or debugging
* Resolving codes in low-volume, real-time applications (CDS alerts, FHIR integration)
* Detecting deprecated or changed concepts in your existing OMOP data
* Prototyping before you have a full local Athena installation
**The combination:** OMOPHub tells you *when* to update. Athena provides the *files* to update with. Your local database is *where* you update to. This three-part workflow is vocabulary lifecycle management done right.
## 3. Use Case A: Detecting When Your Local Vocabularies Are Stale
Before updating anything, you need to know *if* an update is needed. OMOPHub can help by letting you check the current state of vocabulary concepts against what you have locally.
**The Scenario:** Your ETL runs nightly. Once a week, before the ETL starts, a pre-check script queries OMOPHub to determine if any key vocabularies have changed since your last Athena download.
**The Approach:** Since the OMOPHub SDK doesn't expose a dedicated vocabulary version endpoint, we use a practical proxy: check a set of well-known "sentinel" concepts from each vocabulary and compare their metadata (validity dates, standard status) against your local records. If a sentinel concept has changed, a vocabulary update is likely.
```bash theme={null}
pip install omophub
```
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
# Sentinel concepts - well-known, stable concepts from each vocabulary
# If these change, the vocabulary has been updated
SENTINELS = {
"SNOMED": {
"concept_name": "Type 2 diabetes mellitus",
"search_term": "Type 2 diabetes mellitus",
"expected_domain": "Condition",
},
"RxNorm": {
"concept_name": "Metformin",
"search_term": "Metformin",
"expected_domain": "Drug",
},
"LOINC": {
"concept_name": "Glucose [Mass/volume] in Blood",
"search_term": "2339-0",
"expected_domain": "Measurement",
},
"ICD10CM": {
"concept_name": "Type 2 diabetes mellitus without complications",
"search_term": "E11.9",
"expected_domain": "Condition",
},
}
# Your local vocabulary state (in production, read from your local OMOP database)
local_state = {
"SNOMED": {"last_concept_id": 201826, "last_checked": "2025-01-15"},
"RxNorm": {"last_concept_id": 1503297, "last_checked": "2025-01-15"},
"LOINC": {"last_concept_id": 3004501, "last_checked": "2025-01-15"},
"ICD10CM": {"last_concept_id": 45576876, "last_checked": "2025-01-15"},
}
def check_vocabulary_freshness():
"""Check if local vocabularies might be stale by querying OMOPHub sentinels."""
print("Vocabulary Freshness Check\n")
alerts = []
for vocab_id, sentinel in SENTINELS.items():
print(f" Checking {vocab_id}...")
try:
# Step 1: Search for the sentinel concept
results = client.search.basic(
sentinel["search_term"],
vocabulary_ids=[vocab_id],
page_size=1,
)
candidates = results.get("concepts", []) if results else []
if not candidates:
print(f" -> Sentinel not found - possible vocabulary restructuring")
alerts.append(f"{vocab_id}: sentinel concept not found")
continue
remote = candidates[0]
remote_id = remote["concept_id"]
remote_name = remote.get("concept_name", "Unknown")
remote_valid_end = remote.get("valid_end_date", "N/A")
remote_standard = remote.get("standard_concept", "N/A")
local = local_state.get(vocab_id, {})
local_id = local.get("last_concept_id")
print(f" Remote: {remote_name} (ID: {remote_id}, valid_end: {remote_valid_end}, standard: {remote_standard})")
# Step 2: Check if the concept ID changed (rare but significant)
if local_id and remote_id != local_id:
print(f" ALERT: Concept ID changed from {local_id} to {remote_id}")
alerts.append(f"{vocab_id}: sentinel concept ID changed")
# Step 3: Check if the concept has been deprecated
if remote_standard != "S":
print(f" ALERT: Sentinel is no longer standard (standard_concept='{remote_standard}')")
alerts.append(f"{vocab_id}: sentinel concept deprecated")
# Step 4: Verify concept is still valid
if remote_valid_end and remote_valid_end != "2099-12-31":
print(f" WARNING: Concept has a non-default valid_end_date: {remote_valid_end}")
if not any(vocab_id in a for a in alerts):
print(f" OK: Sentinel matches expectations")
except omophub.APIError as e:
print(f" API error: {e.message}")
alerts.append(f"{vocab_id}: API error during check")
print(f"\n--- Summary ---")
if alerts:
print(f" {len(alerts)} alert(s) detected - consider updating from Athena:")
for alert in alerts:
print(f" - {alert}")
print(f"\n To update: https://athena.ohdsi.org/vocabulary/list")
else:
print(f" All vocabularies appear current. Next check recommended in 2 weeks.")
return alerts
# Run the check
alerts = check_vocabulary_freshness()
```
**The Key Insight:** This doesn't replace Athena downloads. It tells you *when* to do them. Instead of updating on a fixed schedule (quarterly, whether needed or not) or never (because it's too painful), you check programmatically and update only when something has changed. The sentinel approach is a lightweight proxy: for full version detection, you'd check multiple concepts per vocabulary or compare concept counts.
## 4. Use Case B: Detecting Deprecated Concepts in Your Existing OMOP Data
This is where OMOPHub provides unique value that local vocabulary tables don't: checking your *existing* data against the *latest* vocabulary to find concepts that have been deprecated, merged, or replaced since you last updated.
**The Scenario:** Your OMOP CDM has 50,000 unique condition concept IDs in the `condition_occurrence` table. Some of these may reference concepts that have been deprecated in the latest SNOMED release. You need to find them and map them to their current replacements.
```python Python theme={null}
import omophub
client = omophub.OMOPHub()
def check_concepts_for_deprecation(concept_ids, batch_label=""):
"""
Check a list of OMOP concept IDs against OMOPHub to find deprecated ones.
Returns list of deprecated concepts with replacement suggestions.
"""
print(f"\nChecking {len(concept_ids)} concepts for deprecation{f' ({batch_label})' if batch_label else ''}...\n")
deprecated = []
valid = 0
errors = 0
for cid in concept_ids:
try:
# Step 1: Fetch the concept from OMOPHub
concept = client.concepts.get(cid)
if not concept:
print(f" ID {cid}: NOT FOUND - may have been removed entirely")
deprecated.append({"concept_id": cid, "status": "not_found", "replacement": None})
continue
standard = concept.get("standard_concept")
valid_end = concept.get("valid_end_date", "2099-12-31")
name = concept.get("concept_name", "Unknown")
# Step 2: Check if deprecated (non-standard or expired)
if standard != "S":
print(f" ID {cid}: DEPRECATED - '{name}' (standard_concept='{standard}')")
# Step 3: Try to find the replacement via "Maps to" relationship
replacement = None
try:
rels = client.concepts.relationships(cid)
rel_list = (
rels if isinstance(rels, list)
else rels.get("relationships", [])
) if rels else []
maps_to = [
r for r in rel_list
if r.get("relationship_id") == "Maps to"
and r.get("concept_id") != cid # Exclude self-maps
]
if maps_to:
replacement = maps_to[0]
rep_name = replacement.get("concept_name", "Unknown")
rep_id = replacement.get("concept_id")
print(f" -> Replacement: '{rep_name}' (ID: {rep_id})")
except omophub.APIError:
pass
deprecated.append({
"concept_id": cid,
"concept_name": name,
"status": "deprecated",
"replacement_id": replacement.get("concept_id") if replacement else None,
"replacement_name": replacement.get("concept_name") if replacement else None,
})
else:
valid += 1
except omophub.APIError:
errors += 1
print(f"\n--- Results ---")
print(f" Valid (standard): {valid}")
print(f" Deprecated/removed: {len(deprecated)}")
print(f" Errors: {errors}")
if deprecated:
print(f"\n Deprecated concepts requiring remapping:")
for d in deprecated:
rep = f" -> {d['replacement_name']} (ID: {d['replacement_id']})" if d.get("replacement_id") else " -> no replacement found"
print(f" ID {d['concept_id']}: {d.get('concept_name', 'N/A')}{rep}")
return deprecated
# Example: check a sample of concept IDs from your condition_occurrence table
# In production, query: SELECT DISTINCT condition_concept_id FROM condition_occurrence
sample_condition_ids = [201826, 4329847, 316139, 999999999] # Last one is fake
deprecated_concepts = check_concepts_for_deprecation(sample_condition_ids, batch_label="condition_occurrence")
```
**The Key Insight:** `client.concepts.get()` is a real SDK method. This pattern - iterate through your existing concept IDs, check each against OMOPHub, find the deprecated ones, look up replacements via relationships - is a genuine, high-value use of the API. You can't do this easily with local vocabulary tables if those tables are themselves stale. OMOPHub, with its always-current data, becomes a validation layer *on top of* your local installation.
**For production use:** Batch this across your OMOP tables: `condition_occurrence.condition_concept_id`, `drug_exposure.drug_concept_id`, `measurement.measurement_concept_id`, etc. Run it monthly. Generate a deprecation report. Use the replacement concept IDs to plan your next vocabulary update and ETL re-mapping.
## 5. The Vocabulary Lifecycle Workflow
Putting it all together:
**Weekly: Freshness check** (Use Case A)
* Run sentinel checks against OMOPHub
* If alerts fire, schedule an Athena download
**Monthly: Deprecation scan** (Use Case B)
* Extract distinct concept IDs from your OMOP CDM tables
* Check each against OMOPHub for deprecation
* Generate a report of deprecated concepts and their replacements
**Quarterly (or when alerts fire): Full vocabulary update**
* Download latest vocabularies from Athena (athena.ohdsi.org)
* Load into your local OMOP vocabulary tables
* Re-run ETL validation against the updated vocabularies
* Update your local state records for the next freshness check
OMOPHub's role: steps 1 and 2 (detection). Athena's role: step 3 (the actual vocabulary files). Your database: step 3 (where the files get loaded). Each tool does what it's built for.
## 6. Conclusion: From Reactive to Proactive
The Maintenance Tax isn't the vocabulary update itself - it's not knowing *when* you need one. Teams either update too often (wasting operational time on unchanged vocabularies) or too rarely (accumulating version drift that compounds with every ETL run).
OMOPHub makes vocabulary lifecycle management proactive. Check freshness programmatically. Detect deprecated concepts before they corrupt your analyses. Know exactly when to pull the trigger on an Athena download, and know exactly which concepts need remapping when you do.
Your local vocabulary tables remain the source of truth for your OMOP CDM. OMOPHub is the early warning system that keeps them honest.
Start with the deprecation scan. Pull the distinct concept IDs from your biggest OMOP table, run them through `client.concepts.get()`, and see what comes back deprecated. That report alone is worth the integration.
# Introduction
Source: https://docs.omophub.com/introduction
OMOPHub provides a healthcare vocabulary API with OHDSI and OMOP-compliant medical terminology access for developers.
## Quickstart
Learn how to get OMOPHub API set up in your project.
Get up and running with the OMOPHub API in minutes
Explore our comprehensive API documentation
Use our SDKs for Python, JavaScript, and R
Learn from real-world healthcare implementations
Connect AI agents to medical vocabularies via MCP Server
## Why OMOPHub API?
### Healthcare-Grade Infrastructure
Built specifically for healthcare applications with HIPAA compliance, audit logging, and enterprise security.
### Complete OHDSI Coverage
Full implementation of OMOP vocabulary standards with bi-annual updates and version management.
### Advanced Search
Full-text search, faceted filtering, similarity matching, and healthcare-specific ranking algorithms.
### Comprehensive Relationships
Navigate complex medical concept hierarchies with powerful relationship traversal APIs.
### Multi-Vocabulary Support
Access SNOMED CT, ICD-10, RxNorm, LOINC and 100+ other healthcare vocabularies.
## Key Features
Manage vocabulary versions with confidence:
* Bi-annual OHDSI vocabulary updates
* Version-specific API routing
* Seamless migration between versions
* Version comparison and diff tools
Find the right concepts quickly:
* Multi-field full-text search
* Faceted filtering and aggregations
* Fuzzy matching and typo tolerance
* Healthcare-specific ranking
Map between vocabulary systems:
* Direct and equivalent mappings
* Cross-vocabulary navigation
* Mapping quality metrics
* Batch mapping operations
## Getting Started
Sign up for a OMOPHub on [https://dashboard.omophub.com](https://dashboard.omophub.com)
Generate an API key from your dashboard
Use our SDKs or make direct API calls
Discover vocabularies, search concepts, and traverse relationships
## Quick Example
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/search/concepts?query=diabetes%20mellitus%20type%202&vocabulary_ids=SNOMED&page_size=10" \
-H "Authorization: Bearer your_api_key"
```
```python Python theme={null}
import requests
url = "https://api.omophub.com/v1/search/concepts"
headers = {
"Authorization": "Bearer your_api_key",
"Content-Type": "application/json"
}
params = {
"query": "diabetes mellitus type 2",
"vocabulary_ids": "SNOMED",
"page_size": 10
}
response = requests.get(url, headers=headers, params=params)
data = response.json()
for concept in data["data"]["concepts"]:
print(f"{concept['concept_id']}: {concept['concept_name']}")
```
```javascript JavaScript theme={null}
const url = 'https://api.omophub.com/v1/search/concepts';
const params = new URLSearchParams({
query: 'diabetes mellitus type 2',
vocabulary_ids: 'SNOMED',
page_size: 10
});
const response = await fetch(`${url}?${params}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer your_api_key',
'Content-Type': 'application/json'
}
});
const data = await response.json();
data.data.concepts.forEach(concept => {
console.log(`${concept.concept_id}: ${concept.concept_name}`);
});
```
```r R theme={null}
library(httr)
library(jsonlite)
url <- "https://api.omophub.com/v1/search/concepts"
headers <- add_headers(
"Authorization" = "Bearer your_api_key",
"Content-Type" = "application/json"
)
response <- GET(url, headers, query = list(
query = "diabetes mellitus type 2",
vocabulary_ids = "SNOMED",
page_size = 10
))
data <- fromJSON(content(response, "text"))
for (concept in data$data$concepts) {
cat(paste(concept$concept_id, concept$concept_name, sep = ": "), "\n")
}
```
## Need Help?
Comprehensive guides and API reference
Check API status and uptime
# Quick Start
Source: https://docs.omophub.com/quickstart
Get up and running with the OMOPHub API in 5 minutes
## Overview
This guide will help you make your first API call to OMOPHub in just a few minutes. By the end, you'll be able to search medical concepts and explore healthcare vocabularies.
**Prerequisites**
You'll need:
* A OMOPHub account ([sign up here](https://dashboard.omophub.com/register))
* An API key ([generate one here](https://dashboard.omophub.com/api-keys))
* A programming environment (Python, JavaScript/Node.js, R, or cURL)
## Step 1: Install the SDK
Choose your preferred programming language and install the OMOPHub SDK:
```python Python theme={null}
# Python - Install the OMOPHub SDK
pip install omophub
```
```javascript JavaScript theme={null}
# JavaScript/Node.js - No SDK available yet
# Use fetch() which is built-in for Node.js 18+ and all modern browsers
node --version
```
```r R theme={null}
# R - Install the OMOPHub SDK
install.packages("omophub")
```
```bash cURL theme={null}
# cURL - No installation needed, pre-installed on most systems
curl --version
```
## Step 2: Set Up Authentication
Initialize the SDK client with your API key:
```python Python theme={null}
# Python
from omophub import OMOPHub
# Option 1: Use environment variable (recommended)
# export OMOPHUB_API_KEY="oh_xxxxxxxxx"
client = OMOPHub()
# Option 2: Pass API key directly
client = OMOPHub(api_key="oh_xxxxxxxxx")
```
```javascript JavaScript theme={null}
// JavaScript/TypeScript - Manual setup required
const API_KEY = 'your_api_key_here';
const BASE_URL = 'https://api.omophub.com/v1';
const headers = {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
};
```
```r R theme={null}
# R
library(omophub)
# Option 1: Use environment variable (recommended)
# Sys.setenv(OMOPHUB_API_KEY = "oh_xxxxxxxxx")
client <- OMOPHubClient$new()
# Option 2: Pass API key directly
client <- OMOPHubClient$new(api_key = "oh_xxxxxxxxx")
```
```bash cURL theme={null}
# cURL - Set your API key as an environment variable
export OMOPHUB_API_KEY="your_api_key_here"
export OMOPHUB_BASE_URL="https://api.omophub.com/v1"
```
Never hardcode your API key in your source code. Use environment variables or secure key management systems in production.
## Step 3: Make Your First Request
Let's search for concepts related to "hypertension":
```python Python theme={null}
# Python - Search for hypertension concepts
results = client.search.basic("hypertension", page_size=5)
# Display the results
for concept in results.data:
print(f"ID: {concept.concept_id}")
print(f"Name: {concept.concept_name}")
print(f"Vocabulary: {concept.vocabulary_id}")
print(f"Domain: {concept.domain_id}")
print("---")
```
```javascript JavaScript theme={null}
// JavaScript - Search for hypertension concepts
const params = new URLSearchParams({
query: 'hypertension',
page_size: 5
});
const response = await fetch(`${BASE_URL}/search/concepts?${params}`, {
headers: headers
});
const data = await response.json();
// Display the results
data.data.concepts.forEach(concept => {
console.log(`ID: ${concept.concept_id}`);
console.log(`Name: ${concept.concept_name}`);
console.log(`Vocabulary: ${concept.vocabulary_id}`);
console.log(`Domain: ${concept.domain_id}`);
console.log('---');
});
```
```r R theme={null}
# R - Search for hypertension concepts
results <- client$search$basic("hypertension", page_size = 5)
# Display the results
for (i in seq_along(results$data)) {
concept <- results$data[[i]]
cat(paste("ID:", concept$concept_id, "\n"))
cat(paste("Name:", concept$concept_name, "\n"))
cat(paste("Vocabulary:", concept$vocabulary_id, "\n"))
cat(paste("Domain:", concept$domain_id, "\n"))
cat("---\n")
}
```
```bash cURL theme={null}
# cURL - Search for hypertension concepts
curl -X GET "$OMOPHUB_BASE_URL/search/concepts?query=hypertension&page_size=5" \
-H "Authorization: Bearer $OMOPHUB_API_KEY" \
-H "Content-Type: application/json" | jq '.'
```
## Step 4: Explore Concept Details
Once you have a concept ID, you can get detailed information:
```python Python theme={null}
# Python - Get detailed information about a specific concept
concept_id = 320128 # Hypertension concept ID
# Get concept details
concept = client.concepts.get(concept_id)
print(f"Concept: {concept.concept_name}")
print(f"Code: {concept.concept_code}")
print(f"Class: {concept.concept_class_id}")
print(f"Valid dates: {concept.valid_start_date} to {concept.valid_end_date}")
# Get concept relationships
relationships = client.concepts.relationships(concept_id)
print(f"\nFound {len(relationships.data)} relationships")
```
```javascript JavaScript theme={null}
// JavaScript - Get detailed information about a specific concept
const conceptId = 320128; // Hypertension concept ID
// Get concept details
const conceptResponse = await fetch(`${BASE_URL}/concepts/${conceptId}`, {
headers: headers
});
const conceptData = await conceptResponse.json();
const concept = conceptData.data;
console.log(`Concept: ${concept.concept_name}`);
console.log(`Code: ${concept.concept_code}`);
console.log(`Class: ${concept.concept_class_id}`);
console.log(`Valid dates: ${concept.valid_start_date} to ${concept.valid_end_date}`);
// Get concept relationships
const relResponse = await fetch(`${BASE_URL}/concepts/${conceptId}/relationships`, {
headers: headers
});
const relData = await relResponse.json();
console.log(`\nFound ${relData.data.length} relationships`);
```
```r R theme={null}
# R - Get detailed information about a specific concept
concept_id <- 320128 # Hypertension concept ID
# Get concept details
concept <- client$concepts$get(concept_id)
cat(paste("Concept:", concept$concept_name, "\n"))
cat(paste("Code:", concept$concept_code, "\n"))
cat(paste("Class:", concept$concept_class_id, "\n"))
cat(paste("Valid dates:", concept$valid_start_date, "to", concept$valid_end_date, "\n"))
# Get concept relationships
relationships <- client$concepts$relationships(concept_id)
cat(paste("\nFound", length(relationships$data), "relationships\n"))
```
```bash cURL theme={null}
# cURL - Get concept details
curl -X GET "$OMOPHUB_BASE_URL/concepts/320128" \
-H "Authorization: Bearer $OMOPHUB_API_KEY" | jq '.'
# Get concept relationships
curl -X GET "$OMOPHUB_BASE_URL/concepts/320128/relationships" \
-H "Authorization: Bearer $OMOPHUB_API_KEY" | jq '.'
```
## Step 5: Navigate Concept Hierarchies
Explore parent and child concepts:
```python Python theme={null}
# Python - Get parent concepts (ancestors)
ancestors = client.hierarchy.ancestors(320128, max_levels=2)
print("Parent concepts:")
for ancestor in ancestors.concepts:
indent = " " * ancestor.min_levels_of_separation
print(f"{indent}{ancestor.concept_name}")
# Get child concepts (descendants)
descendants = client.hierarchy.descendants(320128, max_levels=2)
print("\nChild concepts:")
for descendant in descendants.concepts:
indent = " " * descendant.min_levels_of_separation
print(f"{indent}{descendant.concept_name}")
```
```javascript JavaScript theme={null}
// JavaScript - Get parent concepts (ancestors)
const ancestorsParams = new URLSearchParams({ max_levels: 2 });
const ancestorsResponse = await fetch(
`${BASE_URL}/concepts/320128/ancestors?${ancestorsParams}`,
{ headers: headers }
);
const ancestorsData = await ancestorsResponse.json();
console.log('Parent concepts:');
ancestorsData.data.forEach(ancestor => {
const indent = ' '.repeat(ancestor.min_levels_of_separation);
console.log(`${indent}${ancestor.concept_name}`);
});
// Get child concepts (descendants)
const descendantsParams = new URLSearchParams({ max_levels: 2 });
const descendantsResponse = await fetch(
`${BASE_URL}/concepts/320128/descendants?${descendantsParams}`,
{ headers: headers }
);
const descendantsData = await descendantsResponse.json();
console.log('\nChild concepts:');
descendantsData.data.forEach(descendant => {
const indent = ' '.repeat(descendant.min_levels_of_separation);
console.log(`${indent}${descendant.concept_name}`);
});
```
```r R theme={null}
# R - Get parent concepts (ancestors)
ancestors <- client$hierarchy$ancestors(320128, max_levels = 2)
cat("Parent concepts:\n")
for (i in seq_along(ancestors$concepts)) {
ancestor <- ancestors$concepts[[i]]
indent <- strrep(" ", ancestor$min_levels_of_separation)
cat(paste0(indent, ancestor$concept_name, "\n"))
}
# Get child concepts (descendants)
descendants <- client$hierarchy$descendants(320128, max_levels = 2)
cat("\nChild concepts:\n")
for (i in seq_along(descendants$concepts)) {
descendant <- descendants$concepts[[i]]
indent <- strrep(" ", descendant$min_levels_of_separation)
cat(paste0(indent, descendant$concept_name, "\n"))
}
```
```bash cURL theme={null}
# cURL - Get ancestors
curl -X GET "$OMOPHUB_BASE_URL/concepts/320128/ancestors?max_levels=2" \
-H "Authorization: Bearer $OMOPHUB_API_KEY" | jq '.'
# Get descendants
curl -X GET "$OMOPHUB_BASE_URL/concepts/320128/descendants?max_levels=2" \
-H "Authorization: Bearer $OMOPHUB_API_KEY" | jq '.'
```
## Next Steps
Congratulations! You've successfully made your first API calls to OMOPHub. Here's what you can explore next:
Learn about OAuth setup and API key management
Master complex search queries and filters
Map concepts between different vocabularies
See real-world healthcare implementations
## Common Issues
If you receive a 401 Unauthorized error:
* Check that your API key is correct
* Ensure you're using the correct authorization header format
* Verify your API key hasn't expired
If you receive a 429 Too Many Requests error:
* Check your current rate limit in the response headers
* Implement exponential backoff for retries
* Consider upgrading your plan for higher limits
If your search returns no results:
* Try broader search terms
* Check spelling and remove special characters
* Use wildcards or partial matching
* Verify the vocabulary ID is correct
# Concepts
Source: https://docs.omophub.com/sdks/python/concepts
Working with medical concepts in the Python SDK
## Get a Concept
Retrieve a concept by its OMOP concept ID:
```python theme={null}
concept = client.concepts.get(201826)
print(concept["concept_name"]) # "Type 2 diabetes mellitus"
print(concept["vocabulary_id"]) # "SNOMED"
print(concept["domain_id"]) # "Condition"
```
## Get by Vocabulary Code
Look up a concept using a vocabulary-specific code:
```python theme={null}
# Get SNOMED concept by its code
concept = client.concepts.get_by_code("SNOMED", "44054006")
print(concept["concept_name"]) # "Type 2 diabetes mellitus"
# Get ICD-10-CM concept
concept = client.concepts.get_by_code("ICD10CM", "E11")
# Include synonyms and relationships
concept = client.concepts.get_by_code(
"SNOMED",
"44054006",
include_synonyms=True,
include_relationships=True,
)
print(concept["synonyms"]) # List of synonym names
print(concept["relationships"]) # {"parents": [...], "children": [...]}
# Specify vocabulary release version
concept = client.concepts.get_by_code(
"SNOMED",
"44054006",
vocab_release="2025.2",
)
```
## Batch Get Concepts
Retrieve multiple concepts in a single request (max 100):
```python theme={null}
# Basic batch request
result = client.concepts.batch([201826, 4329847, 1112807])
for concept in result["data"]["concepts"]:
print(f"{concept['concept_id']}: {concept['concept_name']}")
# With optional parameters
result = client.concepts.batch(
[201826, 4329847, 1112807],
include_relationships=True,
include_synonyms=True,
include_mappings=True,
vocabulary_filter=["SNOMED"],
standard_only=False, # default
)
# Check summary
print(f"Retrieved: {result['data']['summary']['successful_retrievals']}")
print(f"Failed: {result['data']['summary']['failed_retrievals']}")
```
## Autocomplete Suggestions
Get concept suggestions for autocomplete functionality:
```python theme={null}
# Basic suggestions with pagination
result = client.concepts.suggest("diab", page_size=10)
for concept in result["data"]:
print(concept["concept_name"])
# Check pagination metadata
print(result["meta"]["pagination"]["total_items"])
print(result["meta"]["pagination"]["has_next"])
# Filter by vocabulary and domain
result = client.concepts.suggest(
"diab",
vocabulary_ids=["SNOMED"],
domain_ids=["Condition"],
page_size=20,
)
# Navigate pages
result = client.concepts.suggest("diab", page=2, page_size=10)
```
## Get Relationships
Get relationships for a concept:
```python theme={null}
# Get all relationships with pagination
result = client.relationships.get(201826)
for rel in result["data"]["relationships"]:
print(f"{rel['relationship_id']}: {rel['concept_2']['concept_name']}")
# Check pagination
print(f"Page {result['meta']['pagination']['page']} of {result['meta']['pagination']['total_pages']}")
print(f"Total relationships: {result['meta']['pagination']['total_items']}")
# Filter by relationship type and vocabulary
result = client.relationships.get(
201826,
relationship_ids=["Is a", "Subsumes"],
vocabulary_ids=["SNOMED"],
standard_only=True,
page=1,
page_size=50
)
# Include reverse relationships
result = client.relationships.get(
201826,
include_reverse=True,
domain_ids=["Condition"]
)
```
## Get Related Concepts
Find concepts related to a given concept:
```python theme={null}
# Basic related concepts
result = client.concepts.related(201826, page_size=10)
for related in result["data"]:
print(f"{related['concept_name']} ({related['relationship_id']}): {related['relationship_score']:.2f}")
# Filter by relationship type and minimum score
result = client.concepts.related(
201826,
relationship_types=["Is a", "Maps to"],
min_score=0.5,
page_size=20
)
# Specify vocabulary release
result = client.concepts.related(
201826,
vocab_release="2025.1"
)
```
## Get Recommended Concepts
Get curated concept recommendations using the OHDSI Phoebe algorithm:
```python theme={null}
# Basic recommendations for diabetes-related concepts
result = client.concepts.recommended([201826, 4329847])
for source_id, recommendations in result["data"].items():
print(f"\nRecommendations for concept {source_id}:")
for rec in recommendations[:5]:
print(f" - {rec['concept_name']} ({rec['vocabulary_id']}) via {rec['relationship_id']}")
# With filters
result = client.concepts.recommended(
[201826],
relationship_types=["Has finding", "Associated finding"],
vocabulary_ids=["SNOMED", "LOINC"],
domain_ids=["Condition", "Measurement"],
standard_only=True,
page_size=50
)
# Access pagination metadata
print(f"Page {result['meta']['pagination']['page']} of {result['meta']['pagination']['total_pages']}")
print(f"Total recommendations: {result['meta']['pagination']['total_items']}")
```
## Navigate Hierarchy
### Get Complete Hierarchy
Get both ancestors and descendants in a single request:
```python theme={null}
# Flat format (default) - separate arrays for ancestors and descendants
result = client.hierarchy.get(
201826,
max_levels=3,
vocabulary_ids=["SNOMED"],
)
print(f"Total ancestors: {result['data']['total_ancestors']}")
print(f"Total descendants: {result['data']['total_descendants']}")
# Graph format for visualization (D3.js, Cytoscape, etc.)
graph = client.hierarchy.get(
201826,
format="graph",
max_levels=3,
)
nodes = graph['data']['nodes'] # Concept nodes
edges = graph['data']['edges'] # Relationship edges
print(f"Nodes: {len(nodes)}, Edges: {len(edges)}")
# Advanced filtering options
result = client.hierarchy.get(
201826,
domain_ids=["Condition"],
relationship_types=["Is a"],
max_results=100,
include_invalid=False,
)
```
#### Hierarchy Get Parameters
| Parameter | Type | Default | Description |
| -------------------- | ---------- | -------- | ----------------------------------- |
| `concept_id` | int | required | The concept ID |
| `format` | string | "flat" | Response format ("flat" or "graph") |
| `vocabulary_ids` | list\[str] | None | Filter to specific vocabularies |
| `domain_ids` | list\[str] | None | Filter to specific domains |
| `max_levels` | int | 10 | Maximum hierarchy levels (max 20) |
| `max_results` | int | None | Maximum results per direction |
| `relationship_types` | list\[str] | None | Relationship types to follow |
| `include_invalid` | bool | True | Include deprecated/invalid concepts |
### Get Ancestors
Find parent concepts in the hierarchy:
```python theme={null}
result = client.hierarchy.ancestors(
201826,
max_levels=3,
vocabulary_ids=["SNOMED"], # Filter to specific vocabularies
relationship_types=["Is a"], # Relationship types to follow
include_distance=True, # Include hierarchy_level field
include_paths=False, # Include path information
include_invalid=False, # Exclude deprecated concepts
page=1,
page_size=100,
)
# Access source concept info
print(f"Ancestors of: {result['concept_name']}")
# Access ancestors
for ancestor in result["ancestors"]:
level = ancestor.get("hierarchy_level", 0)
print(f"{' ' * level}{ancestor['concept_name']}")
# Access hierarchy summary
print(f"Max depth: {result['hierarchy_summary']['max_hierarchy_depth']}")
# Access pagination
print(f"Page {result['meta']['pagination']['page']} of {result['meta']['pagination']['total_pages']}")
```
#### Ancestors Parameters
| Parameter | Type | Default | Description |
| -------------------- | ---------- | -------- | ---------------------------------------------- |
| `concept_id` | int | required | The concept ID |
| `vocabulary_ids` | list\[str] | None | Filter to specific vocabularies |
| `max_levels` | int | None | Maximum hierarchy levels to traverse |
| `relationship_types` | list\[str] | None | Relationship types to follow (default: "Is a") |
| `include_paths` | bool | False | Include path information |
| `include_distance` | bool | True | Include hierarchy\_level field |
| `include_invalid` | bool | True | Include deprecated/invalid concepts |
| `page` | int | 1 | Page number |
| `page_size` | int | 100 | Results per page |
### Get Descendants
Find child concepts in the hierarchy:
```python theme={null}
result = client.hierarchy.descendants(
201826,
max_levels=2,
vocabulary_ids=["SNOMED"], # Filter to specific vocabularies
relationship_types=["Is a"], # Relationship types to follow
include_distance=True, # Include hierarchy_level field
include_paths=False, # Include path information
include_invalid=False, # Exclude deprecated concepts
domain_ids=["Condition"], # Filter by domain
page=1,
page_size=100,
)
# Access descendants
for descendant in result["descendants"]:
print(descendant["concept_name"])
# Access pagination
print(f"Page {result['meta']['pagination']['page']} of {result['meta']['pagination']['total_pages']}")
```
#### Descendants Parameters
| Parameter | Type | Default | Description |
| -------------------- | ---------- | -------- | ---------------------------------------------- |
| `concept_id` | int | required | The concept ID |
| `vocabulary_ids` | list\[str] | None | Filter to specific vocabularies |
| `max_levels` | int | 10 | Maximum hierarchy levels (max 20) |
| `relationship_types` | list\[str] | None | Relationship types to follow (default: "Is a") |
| `include_distance` | bool | True | Include hierarchy\_level field |
| `include_paths` | bool | False | Include path information |
| `include_invalid` | bool | True | Include deprecated/invalid concepts |
| `domain_ids` | list\[str] | None | Filter by domains |
| `page` | int | 1 | Page number |
| `page_size` | int | 100 | Results per page |
## Get Relationship Types
Get available relationship types from the OMOP CDM:
```python theme={null}
# Get relationship types with pagination
result = client.relationships.types(page=1, page_size=100)
for rel_type in result["data"]:
print(f"{rel_type['relationship_id']}: {rel_type['relationship_name']}")
# Access pagination
print(f"Total types: {result['meta']['pagination']['total_items']}")
```
## With Options
Include synonyms, relationships, hierarchy, and specify vocabulary release:
```python theme={null}
# Get concept with synonyms
concept = client.concepts.get(
201826,
include_synonyms=True,
)
# Get concept with synonyms and relationships
concept = client.concepts.get(
201826,
include_synonyms=True,
include_relationships=True,
)
print(concept["relationships"]) # {"parents": [...], "children": [...]}
# Get concept with hierarchy information
concept = client.concepts.get(
201826,
include_hierarchy=True,
)
print(concept["hierarchy"]) # Ancestor/descendant summary
# Specify vocabulary release version
concept = client.concepts.get(
201826,
vocab_release="2025.2",
)
```
### Parameters
| Parameter | Type | Default | Description |
| ----------------------- | ------ | -------- | -------------------------------------------- |
| `concept_id` | int | required | The OMOP concept ID |
| `include_relationships` | bool | False | Include related concepts (parents/children) |
| `include_synonyms` | bool | False | Include concept synonyms |
| `include_hierarchy` | bool | False | Include hierarchy information |
| `vocab_release` | string | None | Specific vocabulary release (e.g., "2025.2") |
## Get Concept Relationships
Get detailed relationships for a concept with filtering options:
```python theme={null}
# Get all relationships with pagination
result = client.concepts.relationships(201826)
for rel in result["data"]["relationships"]:
print(f"{rel['relationship_id']}: {rel['concept_2']['concept_name']}")
# Filter by relationship type and vocabulary
result = client.concepts.relationships(
201826,
relationship_ids=["Is a", "Subsumes"],
vocabulary_ids=["SNOMED"],
standard_only=True,
)
# Include reverse relationships
result = client.concepts.relationships(
201826,
include_reverse=True,
domain_ids=["Condition"],
)
```
### Relationships Parameters
| Parameter | Type | Default | Description |
| ------------------ | ---------- | -------- | ----------------------------------------------- |
| `concept_id` | int | required | The concept ID |
| `relationship_ids` | list\[str] | None | Filter by relationship type IDs |
| `vocabulary_ids` | list\[str] | None | Filter by target vocabulary IDs |
| `domain_ids` | list\[str] | None | Filter by target domain IDs |
| `include_invalid` | bool | True | Include relationships to invalid concepts |
| `standard_only` | bool | False | Only include relationships to standard concepts |
| `include_reverse` | bool | False | Include reverse relationships |
| `vocab_release` | string | None | Specific vocabulary release version |
# Domains
Source: https://docs.omophub.com/sdks/python/domains
Working with OMOP domains in the Python SDK
## List All Domains
Get a list of all available OMOP domains:
```python theme={null}
# Basic list
result = client.domains.list()
for domain in result["data"]["domains"]:
print(domain["domain_id"])
# Include concept counts and statistics
result = client.domains.list(include_stats=True)
for domain in result["data"]["domains"]:
print(f"{domain['domain_id']}: {domain['concept_count']} concepts")
```
## Get Domain Concepts
Retrieve all concepts within a specific domain:
```python theme={null}
# Basic usage
result = client.domains.concepts("Condition", page_size=100)
for concept in result["data"]["concepts"]:
print(f"{concept['concept_name']} ({concept['concept_id']})")
# Filter by vocabulary
result = client.domains.concepts(
"Condition",
vocabulary_ids=["SNOMED", "ICD10CM"],
page_size=50
)
# Standard concepts only
result = client.domains.concepts(
"Drug",
vocabulary_ids=["RxNorm"],
standard_only=True,
page_size=100
)
# Include invalid/deprecated concepts
result = client.domains.concepts(
"Procedure",
include_invalid=True,
page=1,
page_size=50
)
# Pagination
print(f"Page {result['meta']['pagination']['page']} of {result['meta']['pagination']['total_pages']}")
print(f"Total: {result['meta']['pagination']['total_items']} concepts")
```
### Parameters
| Parameter | Type | Default | Description |
| ----------------- | ---------- | -------- | --------------------------------------------- |
| `domain_id` | str | required | Domain identifier (e.g., "Condition", "Drug") |
| `vocabulary_ids` | list\[str] | None | Filter to specific vocabularies |
| `standard_only` | bool | False | Return only standard concepts |
| `include_invalid` | bool | True | Include deprecated concepts |
| `page` | int | 1 | Page number |
| `page_size` | int | 50 | Results per page (max 1000) |
# Error Handling
Source: https://docs.omophub.com/sdks/python/error-handling
Handle API errors gracefully
## Exception Types
The SDK provides specific exception types for different error scenarios:
```python theme={null}
import omophub
client = omophub.OMOPHub(api_key="oh_xxx")
try:
concept = client.concepts.get(999999999)
except omophub.NotFoundError as e:
print(f"Not found: {e.message}")
except omophub.AuthenticationError as e:
print(f"Auth failed: {e.message}")
except omophub.RateLimitError as e:
print(f"Rate limited. Retry after {e.retry_after}s")
except omophub.ValidationError as e:
print(f"Invalid request: {e.message}")
except omophub.APIError as e:
print(f"API error {e.status_code}: {e.message}")
```
## Exception Hierarchy
```
OMOPHubError # Base exception for all SDK errors
├── APIError # API returned an error response
│ ├── AuthenticationError # 401 - Invalid or missing API key
│ ├── NotFoundError # 404 - Resource not found
│ ├── ValidationError # 400 - Invalid request parameters
│ ├── RateLimitError # 429 - Too many requests
│ └── ServerError # 500+ - Server-side error
└── ConnectionError # Network connectivity issues
```
## Error Properties
All exceptions include helpful properties:
```python theme={null}
try:
concept = client.concepts.get(999999999)
except omophub.APIError as e:
print(e.message) # Human-readable error message
print(e.status_code) # HTTP status code
print(e.request_id) # Request ID for debugging
```
## Rate Limiting
Handle rate limits with automatic retry:
```python theme={null}
try:
results = client.search.basic("diabetes")
except omophub.RateLimitError as e:
print(f"Rate limited. Retry after {e.retry_after} seconds")
import time
time.sleep(e.retry_after)
# Retry the request
results = client.search.basic("diabetes")
```
The SDK automatically retries failed requests up to `max_retries` times (default: 3) with exponential backoff.
## Connection Errors
Handle network issues:
```python theme={null}
try:
concept = client.concepts.get(201826)
except omophub.ConnectionError as e:
print(f"Network error: {e.message}")
# Implement your retry logic or fallback
```
## Best Practices
### Catch Specific Exceptions First
```python theme={null}
try:
concept = client.concepts.get(concept_id)
except omophub.NotFoundError:
# Handle missing concept
concept = None
except omophub.RateLimitError as e:
# Wait and retry
time.sleep(e.retry_after)
except omophub.APIError as e:
# Log and handle other API errors
logger.error(f"API error: {e.message}")
raise
```
### Use Context Manager for Async
```python theme={null}
async with omophub.AsyncOMOPHub(api_key="oh_xxx") as client:
try:
concept = await client.concepts.get(201826)
except omophub.APIError as e:
print(f"Error: {e.message}")
```
# Mappings
Source: https://docs.omophub.com/sdks/python/mappings
Map concepts between vocabularies
## Get Mappings for a Concept
Find how a concept maps to other vocabularies:
```python theme={null}
result = client.mappings.get(201826) # Type 2 diabetes mellitus
for mapping in result["data"]["mappings"]:
print(f"{mapping['relationship_id']}: {mapping['target_concept_name']}")
```
## Filter by Target Vocabulary
Get mappings to a specific vocabulary only:
```python theme={null}
result = client.mappings.get(
201826,
target_vocabulary="ICD10CM",
)
for mapping in result["data"]["mappings"]:
print(f"{mapping['target_concept_id']}: {mapping['target_concept_name']}")
```
## Include Invalid Mappings
Include deprecated or invalid mappings in results:
```python theme={null}
result = client.mappings.get(
201826,
target_vocabulary="ICD10CM",
include_invalid=True,
)
```
## Use Specific Vocabulary Version
Query mappings from a specific vocabulary release:
```python theme={null}
result = client.mappings.get(
201826,
target_vocabulary="ICD10CM",
vocab_release="2025.1",
)
```
## Map Multiple Concepts
Map a batch of concept IDs to a target vocabulary:
```python theme={null}
result = client.mappings.map(
target_vocabulary="ICD10CM",
source_concepts=[201826, 4329847], # Diabetes, MI
)
for mapping in result["data"]["mappings"]:
source = mapping["source_concept_id"]
target = mapping["target_concept_id"]
print(f"{source} -> {target}")
```
## Map Using Vocabulary Codes
Map concepts directly using vocabulary codes instead of OMOP concept IDs:
```python theme={null}
result = client.mappings.map(
target_vocabulary="RxNorm",
source_codes=[
{"vocabulary_id": "SNOMED", "concept_code": "387517004"}, # Acetaminophen
{"vocabulary_id": "SNOMED", "concept_code": "108774000"}, # Anastrozole
],
)
for mapping in result["data"]["mappings"]:
print(f"{mapping['source_concept_name']} -> {mapping['target_concept_name']}")
```
Use `source_codes` when you have vocabulary-specific codes (e.g., SNOMED codes from your source system).
Use `source_concepts` when you already have OMOP concept IDs.
You cannot use both parameters in the same request.
## Map with Specific Mapping Type
Filter mappings by type:
```python theme={null}
result = client.mappings.map(
target_vocabulary="ICD10CM",
source_concepts=[201826, 192671],
mapping_type="direct", # "direct", "equivalent", "broader", "narrower"
)
```
## Common Use Case: SNOMED to ICD-10
**Option 1: Direct mapping using vocabulary codes (recommended)**
```python theme={null}
result = client.mappings.map(
target_vocabulary="ICD10CM",
source_codes=[
{"vocabulary_id": "SNOMED", "concept_code": "44054006"}, # Type 2 diabetes
],
)
for m in result["data"]["mappings"]:
print(f" -> {m['target_concept_name']} ({m['target_concept_code']})")
```
**Option 2: Using OMOP concept IDs**
```python theme={null}
# First get the OMOP concept ID
concept = client.concepts.get_by_code("SNOMED", "44054006")
# Then map using the concept ID
mappings = client.mappings.get(
concept["concept_id"],
target_vocabulary="ICD10CM",
)
print(f"Mapping {concept['concept_name']}:")
for m in mappings["data"]["mappings"]:
print(f" -> {m['target_concept_name']} (confidence: {m['confidence']})")
```
# Introduction
Source: https://docs.omophub.com/sdks/python/overview
Get started with the OMOPHub Python SDK
## Prerequisites
* Python 3.10+
* [OMOPHub API key](https://dashboard.omophub.com/api-keys)
## Installation
```bash pip theme={null}
pip install omophub
```
```bash poetry theme={null}
poetry add omophub
```
## Quick Start
```python theme={null}
import omophub
client = omophub.OMOPHub(api_key="oh_xxxxxxxxx")
# Get a concept
concept = client.concepts.get(201826)
print(concept["concept_name"]) # "Type 2 diabetes mellitus"
# Search concepts
results = client.search.basic("diabetes", page_size=5)
for concept in results["concepts"]:
print(f"{concept['concept_id']}: {concept['concept_name']}")
```
## Async Usage
```python theme={null}
import omophub
import asyncio
async def main():
async with omophub.AsyncOMOPHub(api_key="oh_xxx") as client:
concept = await client.concepts.get(201826)
print(concept["concept_name"])
asyncio.run(main())
```
## Configuration
```python theme={null}
client = omophub.OMOPHub(
api_key="oh_xxx",
timeout=30.0,
max_retries=3,
vocab_version="2025.1",
)
```
You can also set your API key via environment variable:
```bash theme={null}
export OMOPHUB_API_KEY=oh_xxxxxxxxx
```
```python theme={null}
# API key is read from environment
client = omophub.OMOPHub()
```
## Next Steps
Get, batch, and explore concepts
Search and autocomplete
Map between vocabularies
Handle API errors gracefully
# Search
Source: https://docs.omophub.com/sdks/python/search
Search for medical concepts
## Basic Search
Search for concepts by text:
```python theme={null}
results = client.search.basic("diabetes mellitus", page_size=20)
for concept in results["concepts"]:
print(f"{concept['concept_id']}: {concept['concept_name']}")
```
### Parameters
| Parameter | Type | Default | Description |
| ------------------- | ---------- | -------- | ---------------------------------------------- |
| `query` | string | required | Search query string |
| `vocabulary_ids` | list\[str] | None | Filter by vocabulary IDs |
| `domain_ids` | list\[str] | None | Filter by domain IDs |
| `concept_class_ids` | list\[str] | None | Filter by concept class IDs |
| `standard_concept` | string | None | Filter by standard concept ("S", "C", or None) |
| `include_synonyms` | bool | False | Search in synonyms |
| `include_invalid` | bool | True | Include invalid concepts |
| `min_score` | float | None | Minimum relevance score (0.0-1.0) |
| `exact_match` | bool | False | Require exact match |
| `page` | int | 1 | Page number (1-based) |
| `page_size` | int | 20 | Results per page |
| `sort_by` | string | None | Sort field |
| `sort_order` | string | None | Sort order ("asc" or "desc") |
## Filter by Vocabulary
Restrict search to specific vocabularies:
```python theme={null}
results = client.search.basic(
"heart attack",
vocabulary_ids=["SNOMED", "ICD10CM"],
)
```
## Filter by Domain
Search within specific domains:
```python theme={null}
results = client.search.basic(
"aspirin",
domain_ids=["Drug"],
page_size=10,
)
```
## Filter by Concept Class
Search for specific concept classes:
```python theme={null}
results = client.search.basic(
"aspirin",
concept_class_ids=["Clinical Drug", "Ingredient"],
page_size=10,
)
```
## Standard Concepts Only
Filter to standard concepts:
```python theme={null}
results = client.search.basic(
"diabetes",
standard_concept="S", # "S" = Standard, "C" = Classification
vocabulary_ids=["SNOMED"],
)
```
## Search with Synonyms
Include concept synonyms in search:
```python theme={null}
results = client.search.basic(
"heart attack",
include_synonyms=True,
vocabulary_ids=["SNOMED"],
)
```
## Combined Filters
```python theme={null}
results = client.search.basic(
"myocardial infarction",
vocabulary_ids=["SNOMED"],
domain_ids=["Condition"],
standard_concept="S",
include_synonyms=True,
min_score=0.5,
page_size=20,
)
```
## Autocomplete
Get suggestions as the user types:
```python theme={null}
result = client.search.autocomplete("diab", page_size=10)
for suggestion in result["suggestions"]:
print(suggestion["suggestion"])
```
## Pagination
### Manual Pagination
```python theme={null}
# Get first page
results = client.search.basic("diabetes", page=1, page_size=50)
# Access pagination metadata
pagination = results["meta"]["pagination"]
print(f"Total: {pagination['total_items']}")
print(f"Pages: {pagination['total_pages']}")
print(f"Has next: {pagination['has_next']}")
# Get next page
if pagination["has_next"]:
results = client.search.basic("diabetes", page=2, page_size=50)
```
### Auto-Pagination Iterator
Iterate through all results automatically:
```python theme={null}
count = 0
for concept in client.search.basic_iter("diabetes", page_size=100):
print(concept["concept_name"])
count += 1
if count >= 500: # Limit for demo
break
```
## Advanced Search
Use advanced search with additional filtering options and facets:
```python theme={null}
results = client.search.advanced(
"diabetes",
vocabulary_ids=["SNOMED", "ICD10CM"],
domain_ids=["Condition"],
standard_concepts_only=True,
page_size=50,
)
# Access results
for concept in results["data"]:
print(f"{concept['concept_id']}: {concept['concept_name']}")
# Access pagination
print(f"Total: {results['meta']['pagination']['total_items']}")
```
### Advanced Search Parameters
| Parameter | Type | Default | Description |
| ------------------------ | ----------- | -------- | ----------------------------- |
| `query` | string | required | Search query string |
| `vocabulary_ids` | list\[str] | None | Filter by vocabulary IDs |
| `domain_ids` | list\[str] | None | Filter by domain IDs |
| `concept_class_ids` | list\[str] | None | Filter by concept class IDs |
| `standard_concepts_only` | bool | False | Only return standard concepts |
| `include_invalid` | bool | True | Include invalid concepts |
| `relationship_filters` | list\[dict] | None | Relationship-based filters |
| `page` | int | 1 | Page number (1-based) |
| `page_size` | int | 20 | Results per page |
### Relationship Filters
Apply relationship-based filtering:
```python theme={null}
results = client.search.advanced(
"diabetes",
relationship_filters=[
{"relationship_id": "Is a", "concept_id": 4116142} # Has parent concept
],
standard_concepts_only=True,
)
```
## Semantic Search
Search for concepts using natural language with neural embeddings. Semantic search understands meaning, not just keywords.
```python theme={null}
# Basic semantic search
results = client.search.semantic("heart attack", page_size=10)
# With filters
results = client.search.semantic(
"diabetes mellitus",
vocabulary_ids=["SNOMED"],
domain_ids=["Condition"],
threshold=0.5, # Higher = more similar
page_size=20,
)
# Access results with similarity scores
for concept in results["data"]["results"]:
print(f"{concept['concept_name']}: {concept['similarity_score']:.2f}")
```
### Semantic Search Parameters
| Parameter | Type | Default | Description |
| ------------------ | ---------- | -------- | ------------------------------- |
| `query` | string | required | Natural language search query |
| `vocabulary_ids` | list\[str] | None | Filter by vocabulary IDs |
| `domain_ids` | list\[str] | None | Filter by domain IDs |
| `standard_concept` | "S" \| "C" | None | Filter by standard concept flag |
| `concept_class_id` | string | None | Filter by concept class |
| `threshold` | float | 0.5 | Minimum similarity (0.0-1.0) |
| `page` | int | 1 | Page number |
| `page_size` | int | 20 | Results per page (max 100) |
### Semantic Search Iterator
Iterate through all semantic search results with automatic pagination:
```python theme={null}
# Iterate through all results
for concept in client.search.semantic_iter("diabetes", page_size=50):
print(concept["concept_name"])
# Collect first N results
import itertools
top_100 = list(itertools.islice(
client.search.semantic_iter("diabetes"), 100
))
# With filters
for concept in client.search.semantic_iter(
"heart failure",
vocabulary_ids=["SNOMED"],
threshold=0.4,
):
print(f"{concept['concept_id']}: {concept['concept_name']}")
```
### Async Semantic Search
```python theme={null}
# Async semantic search
results = await client.search.semantic("heart attack", page_size=10)
# Async iterator
async for concept in client.search.semantic_iter("diabetes"):
print(concept["concept_name"])
```
## Find Similar Concepts
Find concepts similar to a reference concept or query. Provide exactly one of: `concept_id`, `concept_name`, or `query`.
```python theme={null}
# By concept ID
similar = client.search.similar(concept_id=4329847) # MI
# By concept name
similar = client.search.similar(concept_name="Type 2 diabetes mellitus")
# By natural language query
similar = client.search.similar(query="elevated blood sugar")
# With all options
similar = client.search.similar(
concept_id=4329847,
algorithm="semantic", # or "lexical", "hybrid"
similarity_threshold=0.7,
page_size=50,
vocabulary_ids=["SNOMED"],
domain_ids=["Condition"],
include_scores=True,
include_explanations=True,
)
# Access results
for concept in similar["similar_concepts"]:
print(f"{concept['concept_name']}: {concept['similarity_score']:.2f}")
```
### Similar Concepts Parameters
| Parameter | Type | Default | Description |
| ---------------------- | ---------- | -------- | ----------------------------------- |
| `concept_id` | int | None | Source concept ID |
| `concept_name` | string | None | Source concept name |
| `query` | string | None | Natural language query |
| `algorithm` | string | "hybrid" | "semantic", "lexical", or "hybrid" |
| `similarity_threshold` | float | 0.7 | Minimum similarity (0.0-1.0) |
| `page_size` | int | 20 | Max results (max 1000) |
| `vocabulary_ids` | list\[str] | None | Filter by vocabulary |
| `domain_ids` | list\[str] | None | Filter by domain |
| `standard_concept` | string | None | "S", "C", or "N" |
| `include_invalid` | bool | None | Include invalid/deprecated concepts |
| `include_scores` | bool | None | Include detailed scores |
| `include_explanations` | bool | None | Include similarity explanations |
### Algorithm Comparison
| Algorithm | Best For | Speed |
| ---------- | ------------------------ | ------ |
| `semantic` | Meaning-based similarity | Slower |
| `lexical` | Text/string similarity | Faster |
| `hybrid` | Balanced approach | Medium |
### Async Similar Concepts
```python theme={null}
# Async version
similar = await client.search.similar(concept_id=4329847)
```
# Vocabularies
Source: https://docs.omophub.com/sdks/python/vocabularies
Working with OMOP vocabularies in the Python SDK
## List All Vocabularies
Get a paginated list of all available medical vocabularies:
```python theme={null}
# Basic list
result = client.vocabularies.list()
for vocab in result["data"]["vocabularies"]:
print(f"{vocab['vocabulary_id']}: {vocab['vocabulary_name']}")
# Include statistics
result = client.vocabularies.list(include_stats=True)
for vocab in result["data"]["vocabularies"]:
print(f"{vocab['vocabulary_id']}: {vocab.get('concept_count', 'N/A')} concepts")
# Custom sorting and pagination
result = client.vocabularies.list(
sort_by="name",
sort_order="asc",
page=1,
page_size=50
)
```
### Parameters
| Parameter | Type | Default | Description |
| ------------------ | ---- | ------- | ------------------------------------------ |
| `include_stats` | bool | False | Include vocabulary statistics |
| `include_inactive` | bool | False | Include inactive vocabularies |
| `sort_by` | str | "name" | Sort field ("name", "priority", "updated") |
| `sort_order` | str | "asc" | Sort order ("asc", "desc") |
| `page` | int | 1 | Page number |
| `page_size` | int | 20 | Results per page (max 1000) |
## Get Vocabulary Details
Get detailed information about a specific vocabulary:
```python theme={null}
vocab = client.vocabularies.get("SNOMED")
print(f"Name: {vocab['data']['vocabulary_name']}")
print(f"Version: {vocab['data']['vocabulary_version']}")
print(f"Reference: {vocab['data']['vocabulary_reference']}")
```
### Parameters
| Parameter | Type | Default | Description |
| --------------- | ---- | -------- | ------------------------------------------------- |
| `vocabulary_id` | str | required | Vocabulary identifier (e.g., "SNOMED", "ICD10CM") |
### Response Fields
Returns `vocabulary_id`, `vocabulary_name`, `vocabulary_reference`, `vocabulary_version`, `vocabulary_concept_id`.
For detailed statistics, use `client.vocabularies.stats(vocabulary_id)`.
## Get Vocabulary Statistics
Get statistical information about a vocabulary:
```python theme={null}
stats = client.vocabularies.stats("SNOMED")
print(f"Total concepts: {stats['total_concepts']}")
print(f"Standard concepts: {stats['standard_concepts']}")
```
## Get Domain Statistics
Get statistics for a specific domain within a vocabulary:
```python theme={null}
result = client.vocabularies.domain_stats("SNOMED", "Condition")
print(f"Total concepts: {result['data']['total_concepts']}")
print(f"Standard concepts: {result['data']['standard_concepts']}")
# View concept class breakdown
for cls in result["data"]["concept_classes"]:
print(f"{cls['concept_class_id']}: {cls['concept_count']} concepts")
```
### Parameters
| Parameter | Type | Default | Description |
| --------------- | ---- | -------- | ---------------------------------------------------------- |
| `vocabulary_id` | str | required | Vocabulary identifier (e.g., "SNOMED", "ICD10CM") |
| `domain_id` | str | required | Domain identifier (e.g., "Condition", "Drug", "Procedure") |
## Get Vocabulary Domains
Get all standard OHDSI domains:
```python theme={null}
result = client.vocabularies.domains()
for domain in result["data"]["domains"]:
print(f"{domain['domain_id']}: {domain['domain_name']}")
```
Returns all available domains with `domain_id`, `domain_name`, and `description`.
## Get Concept Classes
Get all available concept classes:
```python theme={null}
result = client.vocabularies.concept_classes()
for cls in result["data"]:
print(f"{cls['concept_class_id']}: {cls['concept_class_name']}")
```
Returns concept classes with `concept_class_id`, `concept_class_name`, and `concept_class_concept_id`.
## Get Vocabulary Concepts
Retrieve concepts within a specific vocabulary with filtering and pagination:
```python theme={null}
# Basic usage
result = client.vocabularies.concepts("SNOMED", page_size=100)
for concept in result["data"]["concepts"]:
print(f"{concept['concept_name']} ({concept['concept_id']})")
# Search within vocabulary
result = client.vocabularies.concepts(
"SNOMED",
search="diabetes",
standard_concept="S",
page_size=50
)
# Include additional data
result = client.vocabularies.concepts(
"SNOMED",
search="hypertension",
include_relationships=True,
include_synonyms=True
)
# With sorting
result = client.vocabularies.concepts(
"RxNorm",
sort_by="concept_id",
sort_order="desc",
page_size=100
)
# Include invalid/deprecated concepts
result = client.vocabularies.concepts(
"ICD10CM",
include_invalid=True,
page=1,
page_size=50
)
# Pagination
print(f"Page {result['meta']['pagination']['page']} of {result['meta']['pagination']['total_pages']}")
print(f"Total: {result['meta']['pagination']['total_items']} concepts")
```
### Parameters
| Parameter | Type | Default | Description |
| ----------------------- | ---- | -------- | --------------------------------------------------- |
| `vocabulary_id` | str | required | Vocabulary identifier (e.g., "SNOMED", "ICD10CM") |
| `search` | str | None | Search term to filter concepts by name or code |
| `standard_concept` | str | "all" | Filter by standard concept status ("S", "C", "all") |
| `include_invalid` | bool | True | Include invalid or deprecated concepts |
| `include_relationships` | bool | False | Include concept relationships in response |
| `include_synonyms` | bool | False | Include concept synonyms in response |
| `sort_by` | str | "name" | Sort field ("name", "concept\_id", "concept\_code") |
| `sort_order` | str | "asc" | Sort order ("asc", "desc") |
| `page` | int | 1 | Page number |
| `page_size` | int | 20 | Results per page (max 1000) |
# Concepts
Source: https://docs.omophub.com/sdks/r/concepts
Working with medical concepts in the R SDK
## Get a Concept
Retrieve a concept by its OMOP concept ID:
```r theme={null}
concept <- client$concepts$get(201826)
print(concept$concept_name) # "Type 2 diabetes mellitus"
print(concept$vocabulary_id) # "SNOMED"
print(concept$domain_id) # "Condition"
```
## Get by Vocabulary Code
Look up a concept using a vocabulary-specific code:
```r theme={null}
# Get SNOMED concept by its code
concept <- client$concepts$get_by_code("SNOMED", "44054006")
print(concept$concept_name) # "Type 2 diabetes mellitus"
# Get ICD-10-CM concept
concept <- client$concepts$get_by_code("ICD10CM", "E11")
# Include synonyms and relationships
concept <- client$concepts$get_by_code(
"SNOMED",
"44054006",
include_synonyms = TRUE,
include_relationships = TRUE
)
print(concept$synonyms) # List of synonym names
print(concept$relationships) # list(parents = [...], children = [...])
# Specify vocabulary release version
concept <- client$concepts$get_by_code(
"SNOMED",
"44054006",
vocab_release = "2025.2"
)
```
## Batch Get Concepts
Retrieve multiple concepts in a single request (max 100):
```r theme={null}
# Basic batch request
result <- client$concepts$batch(c(201826, 4329847, 1112807))
for (concept in result$data$concepts) {
cat(sprintf("%s: %s\n", concept$concept_id, concept$concept_name))
}
# With optional parameters
result <- client$concepts$batch(
c(201826, 4329847, 1112807),
include_relationships = TRUE,
include_synonyms = TRUE,
include_mappings = TRUE,
vocabulary_filter = c("SNOMED"),
standard_only = FALSE # default
)
# Check summary
cat(sprintf("Retrieved: %d\n", result$data$summary$successful_retrievals))
cat(sprintf("Failed: %d\n", result$data$summary$failed_retrievals))
```
## Autocomplete Suggestions
Get concept suggestions for autocomplete functionality:
```r theme={null}
# Basic suggestions with pagination
result <- client$concepts$suggest("diab", page_size = 10)
for (concept in result$data) {
print(concept$concept_name)
}
# Check pagination metadata
print(result$meta$pagination$total_items)
print(result$meta$pagination$has_next)
# Filter by vocabulary and domain
result <- client$concepts$suggest(
"diab",
vocabulary_ids = c("SNOMED"),
domain_ids = c("Condition"),
page_size = 20
)
# Navigate pages
result <- client$concepts$suggest("diab", page = 2, page_size = 10)
```
## Get Relationships
Get relationships for a concept:
```r theme={null}
# Get all relationships with pagination
result <- client$relationships$get(201826)
for (rel in result$data$relationships) {
cat(sprintf("%s: %s\n", rel$relationship_id, rel$concept_2$concept_name))
}
# Check pagination
cat(sprintf("Page %d of %d\n",
result$meta$pagination$page,
result$meta$pagination$total_pages
))
cat(sprintf("Total relationships: %d\n", result$meta$pagination$total_items))
# Filter by relationship type and vocabulary
result <- client$relationships$get(
201826,
relationship_ids = c("Is a", "Subsumes"),
vocabulary_ids = "SNOMED",
standard_only = TRUE,
page = 1,
page_size = 50
)
# Include reverse relationships
result <- client$relationships$get(
201826,
include_reverse = TRUE,
domain_ids = c("Condition")
)
```
## Navigate Hierarchy
### Get Complete Hierarchy
Get both ancestors and descendants in a single request:
```r theme={null}
# Flat format (default) - separate arrays for ancestors and descendants
result <- client$hierarchy$get(
201826,
max_levels = 3,
vocabulary_ids = c("SNOMED")
)
cat(sprintf("Total ancestors: %d\n", result$data$total_ancestors))
cat(sprintf("Total descendants: %d\n", result$data$total_descendants))
# Graph format for visualization (D3.js, Cytoscape, etc.)
graph <- client$hierarchy$get(
201826,
format = "graph",
max_levels = 3
)
nodes <- graph$data$nodes # Concept nodes
edges <- graph$data$edges # Relationship edges
cat(sprintf("Nodes: %d, Edges: %d\n", length(nodes), length(edges)))
# Advanced filtering options
result <- client$hierarchy$get(
201826,
domain_ids = c("Condition"),
relationship_types = c("Is a"),
max_results = 100,
include_invalid = FALSE
)
```
#### Hierarchy Get Parameters
| Parameter | Type | Default | Description |
| -------------------- | --------- | -------- | ----------------------------------- |
| `concept_id` | integer | required | The concept ID |
| `format` | character | "flat" | Response format ("flat" or "graph") |
| `vocabulary_ids` | character | NULL | Filter to specific vocabularies |
| `domain_ids` | character | NULL | Filter to specific domains |
| `max_levels` | integer | 10 | Maximum hierarchy levels (max 20) |
| `max_results` | integer | NULL | Maximum results per direction |
| `relationship_types` | character | NULL | Relationship types to follow |
| `include_invalid` | logical | TRUE | Include deprecated/invalid concepts |
### Get Ancestors
Find parent concepts in the hierarchy:
```r theme={null}
result <- client$hierarchy$ancestors(
201826,
max_levels = 3,
vocabulary_ids = c("SNOMED"), # Filter to specific vocabularies
relationship_types = c("Is a"), # Relationship types to follow
include_distance = TRUE, # Include hierarchy_level field
include_paths = FALSE, # Include path information
include_invalid = FALSE, # Exclude deprecated concepts
page = 1,
page_size = 100
)
# Access source concept info
cat(sprintf("Ancestors of: %s\n", result$concept_name))
# Access ancestors
for (ancestor in result$ancestors) {
level <- ancestor$hierarchy_level %||% 0
cat(sprintf("%s%s\n", strrep(" ", level), ancestor$concept_name))
}
# Access hierarchy summary
cat(sprintf("Max depth: %d\n", result$hierarchy_summary$max_hierarchy_depth))
# Access pagination
cat(sprintf("Page %d of %d\n",
result$meta$pagination$page,
result$meta$pagination$total_pages
))
```
#### Ancestors Parameters
| Parameter | Type | Default | Description |
| -------------------- | --------- | -------- | ---------------------------------------------- |
| `concept_id` | integer | required | The concept ID |
| `vocabulary_ids` | character | NULL | Filter to specific vocabularies |
| `max_levels` | integer | NULL | Maximum hierarchy levels to traverse |
| `relationship_types` | character | NULL | Relationship types to follow (default: "Is a") |
| `include_paths` | logical | FALSE | Include path information |
| `include_distance` | logical | TRUE | Include hierarchy\_level field |
| `include_invalid` | logical | TRUE | Include deprecated/invalid concepts |
| `page` | integer | 1 | Page number |
| `page_size` | integer | 100 | Results per page |
### Get Descendants
Find child concepts in the hierarchy:
```r theme={null}
result <- client$hierarchy$descendants(
201826,
max_levels = 2,
vocabulary_ids = c("SNOMED"), # Filter to specific vocabularies
relationship_types = c("Is a"), # Relationship types to follow
include_distance = TRUE, # Include hierarchy_level field
include_paths = FALSE, # Include path information
include_invalid = FALSE, # Exclude deprecated concepts
domain_ids = c("Condition"), # Filter by domain
page = 1,
page_size = 100
)
# Access descendants
for (descendant in result$descendants) {
print(descendant$concept_name)
}
# Access pagination
cat(sprintf("Page %d of %d\n",
result$meta$pagination$page,
result$meta$pagination$total_pages
))
```
#### Descendants Parameters
| Parameter | Type | Default | Description |
| -------------------- | --------- | -------- | ---------------------------------------------- |
| `concept_id` | integer | required | The concept ID |
| `vocabulary_ids` | character | NULL | Filter to specific vocabularies |
| `max_levels` | integer | 10 | Maximum hierarchy levels (max 20) |
| `relationship_types` | character | NULL | Relationship types to follow (default: "Is a") |
| `include_distance` | logical | TRUE | Include hierarchy\_level field |
| `include_paths` | logical | FALSE | Include path information |
| `include_invalid` | logical | TRUE | Include deprecated/invalid concepts |
| `domain_ids` | character | NULL | Filter by domains |
| `page` | integer | 1 | Page number |
| `page_size` | integer | 100 | Results per page |
## Get Relationship Types
Get available relationship types from the OMOP CDM:
```r theme={null}
# Get relationship types with pagination
result <- client$relationships$types(page = 1, page_size = 100)
for (rel_type in result$data) {
cat(sprintf("%s: %s\n", rel_type$relationship_id, rel_type$relationship_name))
}
# Access pagination
cat(sprintf("Total types: %d\n", result$meta$pagination$total_items))
```
## With Options
Include synonyms, relationships, hierarchy, and specify vocabulary release:
```r theme={null}
# Get concept with synonyms
concept <- client$concepts$get(
201826,
include_synonyms = TRUE
)
# Get concept with synonyms and relationships
concept <- client$concepts$get(
201826,
include_synonyms = TRUE,
include_relationships = TRUE
)
print(concept$relationships) # list(parents = [...], children = [...])
# Get concept with hierarchy information
concept <- client$concepts$get(
201826,
include_hierarchy = TRUE
)
print(concept$hierarchy) # Ancestor/descendant summary
# Specify vocabulary release version
concept <- client$concepts$get(
201826,
vocab_release = "2025.2"
)
```
### Parameters
| Parameter | Type | Default | Description |
| ----------------------- | --------- | -------- | -------------------------------------------- |
| `concept_id` | integer | required | The OMOP concept ID |
| `include_relationships` | logical | FALSE | Include related concepts (parents/children) |
| `include_synonyms` | logical | FALSE | Include concept synonyms |
| `include_hierarchy` | logical | FALSE | Include hierarchy information |
| `vocab_release` | character | NULL | Specific vocabulary release (e.g., "2025.2") |
## Get Concept Relationships
Get detailed relationships for a concept with filtering options:
```r theme={null}
# Get all relationships
result <- client$concepts$relationships(201826)
for (rel in result$data$relationships) {
cat(sprintf("%s: %s\n", rel$relationship_id, rel$concept_2$concept_name))
}
# Filter by relationship type and vocabulary
result <- client$concepts$relationships(
201826,
relationship_ids = c("Is a", "Subsumes"),
vocabulary_ids = "SNOMED",
standard_only = TRUE
)
# Include reverse relationships
result <- client$concepts$relationships(
201826,
include_reverse = TRUE,
domain_ids = c("Condition")
)
```
### Relationships Parameters
| Parameter | Type | Default | Description |
| ------------------ | --------- | -------- | ----------------------------------------------- |
| `concept_id` | integer | required | The concept ID |
| `relationship_ids` | character | NULL | Filter by relationship type IDs |
| `vocabulary_ids` | character | NULL | Filter by target vocabulary IDs |
| `domain_ids` | character | NULL | Filter by target domain IDs |
| `include_invalid` | logical | TRUE | Include relationships to invalid concepts |
| `standard_only` | logical | FALSE | Only include relationships to standard concepts |
| `include_reverse` | logical | FALSE | Include reverse relationships |
| `vocab_release` | character | NULL | Specific vocabulary release version |
## Get Related Concepts
Find concepts related to a given concept:
```r theme={null}
# Basic related concepts
result <- client$concepts$related(201826, page_size = 10)
for (related in result$data) {
cat(sprintf("%s (%s): score %.2f\n",
related$concept_name,
related$relationship_id,
related$relationship_score
))
}
# Filter by relationship type and minimum score
result <- client$concepts$related(
201826,
relationship_types = c("Is a", "Maps to"),
min_score = 0.5,
page_size = 20
)
# Specify vocabulary release
result <- client$concepts$related(
201826,
vocab_release = "2025.1"
)
```
## Get Recommended Concepts
Get curated concept recommendations using the OHDSI Phoebe algorithm:
```r theme={null}
# Basic recommendations for diabetes-related concepts
result <- client$concepts$recommended(c(201826, 4329847))
for (source_id in names(result$data)) {
cat(sprintf("\nRecommendations for concept %s:\n", source_id))
for (rec in head(result$data[[source_id]], 5)) {
cat(sprintf(" - %s (%s) via %s\n",
rec$concept_name,
rec$vocabulary_id,
rec$relationship_id
))
}
}
# With filters
result <- client$concepts$recommended(
c(201826),
relationship_types = c("Has finding", "Associated finding"),
vocabulary_ids = c("SNOMED", "LOINC"),
domain_ids = c("Condition", "Measurement"),
standard_only = TRUE,
page_size = 50
)
# Access pagination metadata
cat(sprintf("Page %d of %d\n",
result$meta$pagination$page,
result$meta$pagination$total_pages
))
cat(sprintf("Total recommendations: %d\n", result$meta$pagination$total_items))
```
# Domains
Source: https://docs.omophub.com/sdks/r/domains
Working with OMOP domains in the R SDK
## List All Domains
Get a list of all available OMOP domains:
```r theme={null}
# Basic list
result <- client$domains$list()
for (domain in result$data$domains) {
print(domain$domain_id)
}
# Include concept counts and statistics
result <- client$domains$list(include_stats = TRUE)
for (domain in result$data$domains) {
cat(sprintf("%s: %d concepts\n", domain$domain_id, domain$concept_count))
}
```
## Get Domain Concepts
Retrieve all concepts within a specific domain:
```r theme={null}
# Basic usage
result <- client$domains$concepts("Condition", page_size = 100)
for (concept in result$data$concepts) {
cat(sprintf("%s (%d)\n", concept$concept_name, concept$concept_id))
}
# Filter by vocabulary
result <- client$domains$concepts(
"Condition",
vocabulary_ids = c("SNOMED", "ICD10CM"),
page_size = 50
)
# Standard concepts only
result <- client$domains$concepts(
"Drug",
vocabulary_ids = "RxNorm",
standard_only = TRUE,
page_size = 100
)
# Include invalid/deprecated concepts
result <- client$domains$concepts(
"Procedure",
include_invalid = TRUE,
page = 1,
page_size = 50
)
# Pagination
cat(sprintf("Page %d of %d\n",
result$meta$pagination$page,
result$meta$pagination$total_pages
))
cat(sprintf("Total: %d concepts\n", result$meta$pagination$total_items))
```
### Parameters
| Parameter | Type | Default | Description |
| ----------------- | --------- | -------- | --------------------------------------------- |
| `domain_id` | character | required | Domain identifier (e.g., "Condition", "Drug") |
| `vocabulary_ids` | character | NULL | Filter to specific vocabularies |
| `standard_only` | logical | FALSE | Return only standard concepts |
| `include_invalid` | logical | TRUE | Include deprecated concepts |
| `page` | integer | 1 | Page number |
| `page_size` | integer | 50 | Results per page (max 1000) |
# Error Handling
Source: https://docs.omophub.com/sdks/r/error-handling
Handle API errors gracefully
## Error Handling with tryCatch
The R SDK uses R's condition system for error handling. Use `tryCatch` to handle specific error types:
```r theme={null}
library(omophub)
client <- OMOPHubClient$new(api_key = "oh_xxx")
tryCatch(
{
concept <- client$concepts$get(999999999)
},
omophub_not_found = function(e) {
cat(sprintf("Not found: %s\n", e$message))
},
omophub_auth_error = function(e) {
cat(sprintf("Auth failed: %s\n", e$message))
},
omophub_rate_limit_error = function(e) {
cat(sprintf("Rate limited. Retry after %ds\n", e$retry_after))
},
omophub_api_error = function(e) {
cat(sprintf("API error %d: %s\n", e$status_code, e$message))
}
)
```
## Error Types
The SDK defines the following error conditions:
```
omophub_error # Base error for all SDK errors
├── omophub_api_error # API returned an error response
│ ├── omophub_auth_error # 401 - Invalid or missing API key
│ ├── omophub_not_found # 404 - Resource not found
│ ├── omophub_validation_error # 400 - Invalid request parameters
│ ├── omophub_rate_limit_error # 429 - Too many requests
│ └── omophub_server_error # 500+ - Server-side error
└── omophub_connection_error # Network connectivity issues
```
## Error Properties
All errors include helpful properties:
```r theme={null}
tryCatch(
{
concept <- client$concepts$get(999999999)
},
omophub_api_error = function(e) {
print(e$message) # Human-readable error message
print(e$status_code) # HTTP status code
print(e$request_id) # Request ID for debugging
}
)
```
## Rate Limiting
Handle rate limits with retry logic:
```r theme={null}
tryCatch(
{
results <- client$search$basic("diabetes")
},
omophub_rate_limit_error = function(e) {
cat(sprintf("Rate limited. Retry after %d seconds\n", e$retry_after))
Sys.sleep(e$retry_after)
# Retry the request
results <- client$search$basic("diabetes")
}
)
```
The SDK automatically retries failed requests up to `max_retries` times (default: 3) with exponential backoff. You typically don't need to handle transient errors manually.
## Connection Errors
Handle network issues:
```r theme={null}
tryCatch(
{
concept <- client$concepts$get(201826)
},
omophub_connection_error = function(e) {
cat(sprintf("Network error: %s\n", e$message))
# Implement your retry logic or fallback
}
)
```
## Best Practices
### Catch Specific Errors First
```r theme={null}
tryCatch(
{
concept <- client$concepts$get(concept_id)
},
omophub_not_found = function(e) {
# Handle missing concept
concept <- NULL
},
omophub_rate_limit_error = function(e) {
# Wait and retry
Sys.sleep(e$retry_after)
},
omophub_api_error = function(e) {
# Log and handle other API errors
warning(sprintf("API error: %s", e$message))
stop(e)
}
)
```
### Use withCallingHandlers for Logging
```r theme={null}
withCallingHandlers(
{
results <- client$search$basic("diabetes")
},
omophub_api_error = function(e) {
# Log but don't handle - let it propagate
message(sprintf("API call failed: %s", e$message))
}
)
```
### Wrap in a Safe Function
Create a helper function for common error handling:
```r theme={null}
safe_get_concept <- function(client, concept_id) {
tryCatch(
{
client$concepts$get(concept_id)
},
omophub_not_found = function(e) {
NULL
},
omophub_api_error = function(e) {
warning(sprintf("Failed to get concept %d: %s", concept_id, e$message))
NULL
}
)
}
# Usage
concept <- safe_get_concept(client, 201826)
if (!is.null(concept)) {
print(concept$concept_name)
}
```
# Mappings
Source: https://docs.omophub.com/sdks/r/mappings
Map concepts between vocabularies
## Get Mappings for a Concept
Find how a concept maps to other vocabularies:
```r theme={null}
result <- client$mappings$get(201826) # Type 2 diabetes mellitus
for (mapping in result$data$mappings) {
cat(sprintf("%s: %s\n", mapping$relationship_id, mapping$target_concept_name))
}
```
## Filter by Target Vocabulary
Get mappings to a specific vocabulary only:
```r theme={null}
result <- client$mappings$get(
201826,
target_vocabulary = "ICD10CM"
)
for (mapping in result$data$mappings) {
cat(sprintf("%s: %s\n", mapping$target_concept_id, mapping$target_concept_name))
}
```
## Include Invalid Mappings
Include deprecated or invalid mappings in results:
```r theme={null}
result <- client$mappings$get(
201826,
target_vocabulary = "ICD10CM",
include_invalid = TRUE
)
```
## Use Specific Vocabulary Version
Query mappings from a specific vocabulary release:
```r theme={null}
result <- client$mappings$get(
201826,
target_vocabulary = "ICD10CM",
vocab_release = "2025.1"
)
```
## Map Multiple Concepts
Map a batch of concept IDs to a target vocabulary:
```r theme={null}
result <- client$mappings$map(
target_vocabulary = "ICD10CM",
source_concepts = c(201826, 4329847) # Diabetes, MI
)
for (mapping in result$data$mappings) {
source <- mapping$source_concept_id
target <- mapping$target_concept_id
cat(sprintf("%s -> %s\n", source, target))
}
```
## Map Using Vocabulary Codes
Map concepts directly using vocabulary codes instead of OMOP concept IDs:
```r theme={null}
result <- client$mappings$map(
target_vocabulary = "RxNorm",
source_codes = list(
list(vocabulary_id = "SNOMED", concept_code = "387517004"), # Acetaminophen
list(vocabulary_id = "SNOMED", concept_code = "108774000") # Anastrozole
)
)
for (mapping in result$data$mappings) {
cat(sprintf("%s -> %s\n", mapping$source_concept_name, mapping$target_concept_name))
}
```
Use `source_codes` when you have vocabulary-specific codes (e.g., SNOMED codes from your source system).
Use `source_concepts` when you already have OMOP concept IDs.
You cannot use both parameters in the same request.
## Map with Specific Mapping Type
Filter mappings by type:
```r theme={null}
result <- client$mappings$map(
target_vocabulary = "ICD10CM",
source_concepts = c(201826, 192671),
mapping_type = "direct" # "direct", "equivalent", "broader", "narrower"
)
```
## Common Use Case: SNOMED to ICD-10
**Option 1: Direct mapping using vocabulary codes (recommended)**
```r theme={null}
result <- client$mappings$map(
target_vocabulary = "ICD10CM",
source_codes = list(
list(vocabulary_id = "SNOMED", concept_code = "44054006") # Type 2 diabetes
)
)
for (m in result$data$mappings) {
cat(sprintf(" -> %s (%s)\n", m$target_concept_name, m$target_concept_code))
}
```
**Option 2: Using OMOP concept IDs**
```r theme={null}
# First get the OMOP concept ID
concept <- client$concepts$get_by_code("SNOMED", "44054006")
# Then map using the concept ID
mappings <- client$mappings$get(
concept$concept_id,
target_vocabulary = "ICD10CM"
)
cat(sprintf("Mapping %s:\n", concept$concept_name))
for (m in mappings$data$mappings) {
cat(sprintf(" -> %s (confidence: %s)\n", m$target_concept_name, m$confidence))
}
```
## Working with Mapping Results
Convert mappings to a data frame for analysis:
```r theme={null}
result <- client$mappings$get(201826)
# Create a data frame from mappings
mapping_df <- do.call(rbind, lapply(result$data$mappings, function(m) {
data.frame(
source_id = m$source_concept_id,
source_name = m$source_concept_name,
target_id = m$target_concept_id,
target_name = m$target_concept_name,
relationship = m$relationship_id,
confidence = m$confidence,
stringsAsFactors = FALSE
)
}))
print(mapping_df)
```
# Introduction
Source: https://docs.omophub.com/sdks/r/overview
Get started with the OMOPHub R SDK
## Prerequisites
* R 4.1+
* [OMOPHub API key](https://dashboard.omophub.com/api-keys)
## Installation
```r CRAN theme={null}
# Install from CRAN (recommended)
install.packages("omophub")
```
```r GitHub theme={null}
# Install from GitHub
devtools::install_github("omophub/omophub-R")
```
## Quick Start
```r theme={null}
library(omophub)
client <- OMOPHubClient$new(api_key = "oh_xxxxxxxxx")
# Get a concept
concept <- client$concepts$get(201826)
print(concept$concept_name) # "Type 2 diabetes mellitus"
# Search concepts
results <- client$search$basic("diabetes", page_size = 5)
for (concept in results$data) {
cat(sprintf("%s: %s\n", concept$concept_id, concept$concept_name))
}
```
## Configuration
```r theme={null}
client <- OMOPHubClient$new(
api_key = "oh_xxx",
timeout = 30,
max_retries = 3,
vocab_version = "2025.1"
)
```
You can also set your API key via environment variable:
```bash theme={null}
export OMOPHUB_API_KEY=oh_xxxxxxxxx
```
```r theme={null}
# API key is read from environment
client <- OMOPHubClient$new()
```
## Features
* **R6 Class Design** - Familiar object-oriented interface for R users
* **Automatic Rate Limiting** - Built-in request throttling to respect API limits
* **Automatic Retries** - Transient errors are automatically retried with exponential backoff
* **Pagination Helpers** - Easy iteration through large result sets
* **Tibble Integration** - Results can be converted to tibbles for tidyverse workflows
## Next Steps
Get, batch, and explore concepts
Search and autocomplete
Map between vocabularies
Handle API errors gracefully
# Search
Source: https://docs.omophub.com/sdks/r/search
Search for medical concepts
## Basic Search
Search for concepts by text:
```r theme={null}
results <- client$search$basic("diabetes mellitus", page_size = 20)
for (concept in results$data) {
cat(sprintf("%s: %s\n", concept$concept_id, concept$concept_name))
}
```
### Parameters
| Parameter | Type | Default | Description |
| ------------------- | --------- | -------- | ---------------------------------------------- |
| `query` | character | required | Search query string |
| `vocabulary_ids` | character | NULL | Filter by vocabulary IDs |
| `domain_ids` | character | NULL | Filter by domain IDs |
| `concept_class_ids` | character | NULL | Filter by concept class IDs |
| `standard_concept` | character | NULL | Filter by standard concept ("S", "C", or NULL) |
| `include_synonyms` | logical | FALSE | Search in synonyms |
| `include_invalid` | logical | TRUE | Include invalid concepts |
| `min_score` | numeric | NULL | Minimum relevance score (0.0-1.0) |
| `exact_match` | logical | FALSE | Require exact match |
| `page` | integer | 1 | Page number (1-based) |
| `page_size` | integer | 20 | Results per page |
| `sort_by` | character | NULL | Sort field |
| `sort_order` | character | NULL | Sort order ("asc" or "desc") |
## Filter by Vocabulary
Restrict search to specific vocabularies:
```r theme={null}
results <- client$search$basic(
"heart attack",
vocabulary_ids = "SNOMED"
)
# Multiple vocabularies
results <- client$search$basic(
"heart attack",
vocabulary_ids = c("SNOMED", "ICD10CM")
)
```
## Filter by Domain
Search within specific domains:
```r theme={null}
results <- client$search$basic(
"aspirin",
domain_ids = "Drug",
page_size = 10
)
```
## Filter by Concept Class
Search for specific concept classes:
```r theme={null}
results <- client$search$basic(
"aspirin",
concept_class_ids = c("Clinical Drug", "Ingredient"),
page_size = 10
)
```
## Standard Concepts Only
Filter to standard concepts:
```r theme={null}
results <- client$search$basic(
"diabetes",
standard_concept = "S", # "S" = Standard, "C" = Classification
vocabulary_ids = "SNOMED"
)
```
## Search with Synonyms
Include concept synonyms in search:
```r theme={null}
results <- client$search$basic(
"heart attack",
include_synonyms = TRUE,
vocabulary_ids = "SNOMED"
)
```
## Combined Filters
```r theme={null}
results <- client$search$basic(
"myocardial infarction",
vocabulary_ids = "SNOMED",
domain_ids = "Condition",
standard_concept = "S",
include_synonyms = TRUE,
min_score = 0.5,
page_size = 20
)
```
## Autocomplete
Get suggestions as the user types:
```r theme={null}
result <- client$search$autocomplete("diab", page_size = 10)
for (suggestion in result$suggestions) {
print(suggestion$suggestion)
}
# With vocabulary and domain filters
result <- client$search$autocomplete(
"diab",
vocabulary_ids = c("SNOMED"),
domains = c("Condition"),
page_size = 10
)
```
## Pagination
### Manual Pagination
```r theme={null}
# Get first page
results <- client$search$basic("diabetes", page = 1, page_size = 50)
# Access pagination metadata
pagination <- results$meta
cat(sprintf("Total: %d\n", pagination$total_items))
cat(sprintf("Pages: %d\n", pagination$total_pages))
cat(sprintf("Has next: %s\n", pagination$has_next))
# Get next page
if (isTRUE(pagination$has_next)) {
results <- client$search$basic("diabetes", page = 2, page_size = 50)
}
```
### Auto-Pagination with basic\_all()
Fetch all results automatically and return as a tibble:
```r theme={null}
# Fetch up to 5 pages of results
all_results <- client$search$basic_all(
"diabetes",
page_size = 100,
max_pages = 5,
progress = TRUE # Show progress bar
)
# Results are returned as a tibble
print(nrow(all_results))
print(names(all_results))
# Use with dplyr
library(dplyr)
all_results %>%
filter(vocabulary_id == "SNOMED") %>%
select(concept_id, concept_name, domain_id)
```
The `basic_all()` method returns a tibble, making it easy to integrate with
tidyverse workflows.
## Advanced Search
Use advanced search with additional filtering options and facets:
```r theme={null}
results <- client$search$advanced(
"diabetes",
vocabulary_ids = c("SNOMED", "ICD10CM"),
domain_ids = "Condition",
standard_concepts_only = TRUE,
page_size = 50
)
# Access results
for (concept in results$data) {
cat(sprintf("%s: %s\n", concept$concept_id, concept$concept_name))
}
# Access pagination
cat(sprintf("Total: %d\n", results$meta$pagination$total_items))
```
### Advanced Search Parameters
| Parameter | Type | Default | Description |
| ------------------------ | --------- | -------- | ----------------------------- |
| `query` | character | required | Search query string |
| `vocabulary_ids` | character | NULL | Filter by vocabulary IDs |
| `domain_ids` | character | NULL | Filter by domain IDs |
| `concept_class_ids` | character | NULL | Filter by concept class IDs |
| `standard_concepts_only` | logical | FALSE | Only return standard concepts |
| `include_invalid` | logical | TRUE | Include invalid concepts |
| `relationship_filters` | list | NULL | Relationship-based filters |
| `page` | integer | 1 | Page number (1-based) |
| `page_size` | integer | 20 | Results per page |
### Relationship Filters
Apply relationship-based filtering:
```r theme={null}
results <- client$search$advanced(
"diabetes",
relationship_filters = list(
list(relationship_id = "Is a", concept_id = 4116142) # Has parent concept
),
standard_concepts_only = TRUE
)
```
## Semantic Search
Search for concepts using natural language with neural embeddings. Semantic search understands meaning, not just keywords.
```r theme={null}
# Basic semantic search
results <- client$search$semantic("heart attack", page_size = 10)
# With filters
results <- client$search$semantic(
"diabetes mellitus",
vocabulary_ids = "SNOMED",
domain_ids = "Condition",
threshold = 0.5, # Higher = more similar
page_size = 20
)
# Access results with similarity scores
for (concept in results$data$results) {
cat(sprintf("%s: %.2f\n", concept$concept_name, concept$similarity_score))
}
```
### Semantic Search Parameters
| Parameter | Type | Default | Description |
| ------------------ | ---------- | -------- | ------------------------------- |
| `query` | character | required | Natural language search query |
| `vocabulary_ids` | character | NULL | Filter by vocabulary IDs |
| `domain_ids` | character | NULL | Filter by domain IDs |
| `standard_concept` | "S" \| "C" | NULL | Filter by standard concept flag |
| `concept_class_id` | character | NULL | Filter by concept class |
| `threshold` | numeric | 0.5 | Minimum similarity (0.0-1.0) |
| `page` | integer | 1 | Page number |
| `page_size` | integer | 20 | Results per page (max 100) |
### Auto-Pagination with semantic\_all()
Fetch all semantic search results automatically and return as a tibble:
```r theme={null}
# Fetch all results as tibble
all_results <- client$search$semantic_all(
"diabetes",
page_size = 100,
max_pages = 5,
progress = TRUE
)
# Returns tibble with concept_id, similarity_score, etc.
print(all_results)
# Use with dplyr
library(dplyr)
all_results %>%
filter(similarity_score > 0.6) %>%
select(concept_id, concept_name, similarity_score)
# With filters
all_results <- client$search$semantic_all(
"heart failure",
vocabulary_ids = "SNOMED",
threshold = 0.4,
page_size = 50
)
```
The `semantic_all()` method returns a tibble, making it easy to integrate with
tidyverse workflows.
## Find Similar Concepts
Find concepts similar to a reference concept or query. Provide exactly one of: `concept_id`, `concept_name`, or `query`.
```r theme={null}
# By concept ID
similar <- client$search$similar(concept_id = 4329847) # MI
# By concept name
similar <- client$search$similar(concept_name = "Type 2 diabetes mellitus")
# By natural language query
similar <- client$search$similar(query = "elevated blood sugar")
# With all options
similar <- client$search$similar(
concept_id = 4329847,
algorithm = "semantic", # or "lexical", "hybrid"
similarity_threshold = 0.7,
page_size = 50,
vocabulary_ids = "SNOMED",
domain_ids = "Condition",
include_scores = TRUE,
include_explanations = TRUE
)
# Access results
for (concept in similar$similar_concepts) {
cat(sprintf("%s: %.2f\n", concept$concept_name, concept$similarity_score))
}
```
### Similar Concepts Parameters
| Parameter | Type | Default | Description |
| ---------------------- | --------- | -------- | ----------------------------------- |
| `concept_id` | integer | NULL | Source concept ID |
| `concept_name` | character | NULL | Source concept name |
| `query` | character | NULL | Natural language query |
| `algorithm` | character | "hybrid" | "semantic", "lexical", or "hybrid" |
| `similarity_threshold` | numeric | 0.7 | Minimum similarity (0.0-1.0) |
| `page_size` | integer | 20 | Max results (max 1000) |
| `vocabulary_ids` | character | NULL | Filter by vocabulary |
| `domain_ids` | character | NULL | Filter by domain |
| `standard_concept` | character | NULL | "S", "C", or "N" |
| `include_invalid` | logical | NULL | Include invalid/deprecated concepts |
| `include_scores` | logical | NULL | Include detailed scores |
| `include_explanations` | logical | NULL | Include similarity explanations |
### Algorithm Comparison
| Algorithm | Best For | Speed |
| ---------- | ------------------------ | ------ |
| `semantic` | Meaning-based similarity | Slower |
| `lexical` | Text/string similarity | Faster |
| `hybrid` | Balanced approach | Medium |
# Vocabularies
Source: https://docs.omophub.com/sdks/r/vocabularies
Working with OMOP vocabularies in the R SDK
## List All Vocabularies
Get a paginated list of all available medical vocabularies:
```r theme={null}
# Basic list
result <- client$vocabularies$list()
for (vocab in result$data$vocabularies) {
cat(sprintf("%s: %s\n", vocab$vocabulary_id, vocab$vocabulary_name))
}
# Include statistics
result <- client$vocabularies$list(include_stats = TRUE)
for (vocab in result$data$vocabularies) {
cat(sprintf("%s: %s concepts\n", vocab$vocabulary_id, vocab$concept_count))
}
# Custom sorting and pagination
result <- client$vocabularies$list(
sort_by = "name",
sort_order = "asc",
page = 1,
page_size = 50
)
```
### Parameters
| Parameter | Type | Default | Description |
| ------------------ | --------- | ------- | ------------------------------------------ |
| `include_stats` | logical | FALSE | Include vocabulary statistics |
| `include_inactive` | logical | FALSE | Include inactive vocabularies |
| `sort_by` | character | "name" | Sort field ("name", "priority", "updated") |
| `sort_order` | character | "asc" | Sort order ("asc", "desc") |
| `page` | integer | 1 | Page number |
| `page_size` | integer | 100 | Results per page (max 1000) |
## Get Vocabulary Details
Get detailed information about a specific vocabulary:
```r theme={null}
vocab <- client$vocabularies$get("SNOMED")
cat(sprintf("Name: %s\n", vocab$data$vocabulary_name))
cat(sprintf("Version: %s\n", vocab$data$vocabulary_version))
cat(sprintf("Reference: %s\n", vocab$data$vocabulary_reference))
```
### Parameters
| Parameter | Type | Default | Description |
| --------------- | --------- | -------- | ------------------------------------------------- |
| `vocabulary_id` | character | required | Vocabulary identifier (e.g., "SNOMED", "ICD10CM") |
### Response Fields
Returns `vocabulary_id`, `vocabulary_name`, `vocabulary_reference`, `vocabulary_version`, `vocabulary_concept_id`.
For detailed statistics, use `client$vocabularies$stats(vocabulary_id)`.
## Get Vocabulary Statistics
Get statistical information about a vocabulary:
```r theme={null}
stats <- client$vocabularies$stats("SNOMED")
cat(sprintf("Total concepts: %d\n", stats$total_concepts))
cat(sprintf("Standard concepts: %d\n", stats$standard_concepts))
```
## Get Domain Statistics
Get statistics for a specific domain within a vocabulary:
```r theme={null}
result <- client$vocabularies$domain_stats("SNOMED", "Condition")
cat(sprintf("Total concepts: %d\n", result$data$total_concepts))
cat(sprintf("Standard concepts: %d\n", result$data$standard_concepts))
# View concept class breakdown
for (cls in result$data$concept_classes) {
cat(sprintf("%s: %d concepts\n", cls$concept_class_id, cls$concept_count))
}
```
### Parameters
| Parameter | Type | Default | Description |
| --------------- | --------- | -------- | ---------------------------------------------------------- |
| `vocabulary_id` | character | required | Vocabulary identifier (e.g., "SNOMED", "ICD10CM") |
| `domain_id` | character | required | Domain identifier (e.g., "Condition", "Drug", "Procedure") |
## Get Vocabulary Domains
Get all standard OHDSI domains:
```r theme={null}
result <- client$vocabularies$domains()
for (domain in result$data$domains) {
cat(sprintf("%s: %s\n", domain$domain_id, domain$domain_name))
}
```
Returns all available domains with `domain_id`, `domain_name`, and `description`.
## Get Concept Classes
Get all available concept classes:
```r theme={null}
result <- client$vocabularies$concept_classes()
for (cls in result$data) {
cat(sprintf("%s: %s\n", cls$concept_class_id, cls$concept_class_name))
}
```
Returns concept classes with `concept_class_id`, `concept_class_name`, and `concept_class_concept_id`.
## Get Vocabulary Concepts
Retrieve concepts within a specific vocabulary with filtering and pagination:
```r theme={null}
# Basic usage
result <- client$vocabularies$concepts("SNOMED", page_size = 100)
for (concept in result$data$concepts) {
cat(sprintf("%s (%d)\n", concept$concept_name, concept$concept_id))
}
# Search within vocabulary
result <- client$vocabularies$concepts(
"SNOMED",
search = "diabetes",
standard_concept = "S",
page_size = 50
)
# Include additional data
result <- client$vocabularies$concepts(
"SNOMED",
search = "hypertension",
include_relationships = TRUE,
include_synonyms = TRUE
)
# With sorting
result <- client$vocabularies$concepts(
"RxNorm",
sort_by = "concept_id",
sort_order = "desc",
page_size = 100
)
# Include invalid/deprecated concepts
result <- client$vocabularies$concepts(
"ICD10CM",
include_invalid = TRUE,
page = 1,
page_size = 50
)
# Pagination
cat(sprintf("Page %d of %d\n",
result$meta$pagination$page,
result$meta$pagination$total_pages
))
cat(sprintf("Total: %d concepts\n", result$meta$pagination$total_items))
```
### Parameters
| Parameter | Type | Default | Description |
| ----------------------- | --------- | -------- | --------------------------------------------------- |
| `vocabulary_id` | character | required | Vocabulary identifier (e.g., "SNOMED", "ICD10CM") |
| `search` | character | NULL | Search term to filter concepts by name or code |
| `standard_concept` | character | "all" | Filter by standard concept status ("S", "C", "all") |
| `include_invalid` | logical | TRUE | Include invalid or deprecated concepts |
| `include_relationships` | logical | FALSE | Include concept relationships in response |
| `include_synonyms` | logical | FALSE | Include concept synonyms in response |
| `sort_by` | character | "name" | Sort field ("name", "concept\_id", "concept\_code") |
| `sort_order` | character | "asc" | Sort order ("asc", "desc") |
| `page` | integer | 1 | Page number |
| `page_size` | integer | 20 | Results per page (max 1000) |
# Vocabulary Releases
Source: https://docs.omophub.com/vocabulary-versions
Learn more about available vocabulary release versions, how to select them, and version management
The OMOPHub API supports multiple vocabulary release versions through a sophisticated schema-based versioning system. Each release contains the complete OHDSI vocabulary data for that time period, ensuring data consistency and enabling time-based analysis.
## Current Available Releases
The following vocabulary releases are currently available in the system:
| Version | Release Date | Status | Default | Release Notes |
| ---------- | ------------ | ------ | ------- | ----------------------------------------------------------------------------------------- |
| **2026.1** | 2026-02-27 | Active | Yes | [Link](https://github.com/OHDSI/Vocabulary-v5.0/releases/tag/v20260227_1772176757.000000) |
| **2025.2** | 2025-08-27 | Active | No | [Link](https://github.com/OHDSI/Vocabulary-v5.0/releases/tag/v20250827_1756288395.000000) |
| **2025.1** | 2025-02-27 | Active | No | [Link](https://github.com/OHDSI/Vocabulary-v5.0/releases/tag/v20250227_1740652703.000000) |
| **2024.2** | 2024-08-30 | Active | No | [Link](https://github.com/OHDSI/Vocabulary-v5.0/releases/tag/v20240830_1725004358.000000) |
## Version Abbreviations
For convenience, the API supports several abbreviations for version specification:
| Abbreviation | Maps To | Description |
| ------------ | -------- | ---------------------------------------- |
| `latest` | `2026.1` | Always points to the most recent release |
| `default` | `2026.1` | Same as latest - the system default |
| `current` | `2026.1` | Alias for the default version |
| `2026v1` | `2026.1` | Half-based abbreviation |
| `2025v2` | `2025.2` | Half-based abbreviation |
| `2025v1` | `2025.1` | Half-based abbreviation |
| `2024v2` | `2024.2` | Half-based abbreviation |
## How to Select Vocabulary Versions
You can specify which vocabulary version to use in your API requests using any of these methods:
### Method 1: Query Parameter (Recommended)
Add the `vocab_release` parameter to any API endpoint:
```bash cURL theme={null}
# Using specific version
curl -X GET "https://api.omophub.com/v1/concepts/search?query=diabetes&vocab_release=2024.1" \
-H "Authorization: Bearer YOUR_API_KEY"
# Using abbreviation
curl -X GET "https://api.omophub.com/v1/concepts/search?query=diabetes&vocab_release=2024v1" \
-H "Authorization: Bearer YOUR_API_KEY"
# Using latest (default behavior)
curl -X GET "https://api.omophub.com/v1/concepts/search?query=diabetes&vocab_release=latest" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```javascript JavaScript theme={null}
// Using specific version
const response = await fetch('https://api.omophub.com/v1/concepts/search?query=diabetes&vocab_release=2024.1', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
// Using abbreviation
const responseAbbrev = await fetch('https://api.omophub.com/v1/concepts/search?query=diabetes&vocab_release=2024v1', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
// Using latest (default)
const responseLatest = await fetch('https://api.omophub.com/v1/concepts/search?query=diabetes&vocab_release=latest', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
```
```python Python theme={null}
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
# Using specific version
response = requests.get(
'https://api.omophub.com/v1/concepts/search',
headers=headers,
params={
'query': 'diabetes',
'vocab_release': '2024.2'
}
)
# Using abbreviation
response_abbrev = requests.get(
'https://api.omophub.com/v1/concepts/search',
headers=headers,
params={
'query': 'diabetes',
'vocab_release': '2024v2'
}
)
# Using latest (default)
response_latest = requests.get(
'https://api.omophub.com/v1/concepts/search',
headers=headers,
params={
'query': 'diabetes',
'vocab_release': 'latest'
}
)
```
```r R theme={null}
library(httr)
# Using specific version
response <- GET(
"https://api.omophub.com/v1/concepts/search",
add_headers(Authorization = "Bearer YOUR_API_KEY"),
query = list(
query = "diabetes",
vocab_release = "2024.2"
)
)
# Using abbreviation
response_abbrev <- GET(
"https://api.omophub.com/v1/concepts/search",
add_headers(Authorization = "Bearer YOUR_API_KEY"),
query = list(
query = "diabetes",
vocab_release = "2024v2"
)
)
# Using latest (default)
response_latest <- GET(
"https://api.omophub.com/v1/concepts/search",
add_headers(Authorization = "Bearer YOUR_API_KEY"),
query = list(
query = "diabetes",
vocab_release = "latest"
)
)
```
### Method 2: HTTP Header
Use the `X-Vocab-Release` header:
```bash cURL theme={null}
curl -X GET "https://api.omophub.com/v1/concepts/search?query=diabetes" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "X-Vocab-Release: 2024.2"
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.omophub.com/v1/concepts/search?query=diabetes', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'X-Vocab-Release': '2024.2'
}
});
```
```python Python theme={null}
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'X-Vocab-Release': '2024.2'
}
response = requests.get(
'https://api.omophub.com/v1/concepts/search',
headers=headers,
params={'query': 'diabetes'}
)
```
## Default Version Behavior
When no version is specified:
* **Default Version**: `2025.2`
* **Fallback**: If the default version is unavailable, the system uses the latest available active version
* **Caching**: Responses are cached with version-specific keys to ensure consistency
## Version Comparison Example
Here's how to compare concept availability across versions:
```python Python theme={null}
import requests
def compare_concept_across_versions(concept_code, vocabulary_id):
"""Compare a concept across different vocabulary versions."""
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
versions = ['2025.2', '2025.1', '2024.2']
results = {}
for version in versions:
response = requests.get(
'https://api.omophub.com/v1/concepts/search',
headers=headers,
params={
'query': concept_code,
'vocabulary_ids': vocabulary_id,
'vocab_release': version,
'page_size': 1
}
)
data = response.json()
if data.get('data'):
concept = data['data'][0]
results[version] = {
'found': True,
'concept_name': concept['concept_name'],
'standard_concept': concept.get('standard_concept'),
'valid_start_date': concept.get('valid_start_date'),
'valid_end_date': concept.get('valid_end_date')
}
else:
results[version] = {'found': False}
return results
# Example usage
concept_comparison = compare_concept_across_versions('E11.9', 'ICD10CM')
for version, info in concept_comparison.items():
if info['found']:
print(f"Version {version}: {info['concept_name']}")
else:
print(f"Version {version}: Not found")
```
```javascript JavaScript theme={null}
async function compareConcept(conceptCode, vocabularyId) {
const headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
};
const versions = ['2026.1', '2025.2', '2025.1', '2024.2'];
const results = {};
for (const version of versions) {
try {
const response = await fetch(
`https://api.omophub.com/v1/concepts/search?query=${conceptCode}&vocabulary_ids=${vocabularyId}&vocab_release=${version}&page_size=1`,
{ headers }
);
const data = await response.json();
if (data.data && data.data.length > 0) {
const concept = data.data[0];
results[version] = {
found: true,
conceptName: concept.concept_name,
standardConcept: concept.standard_concept,
validStartDate: concept.valid_start_date,
validEndDate: concept.valid_end_date
};
} else {
results[version] = { found: false };
}
} catch (error) {
results[version] = { found: false, error: error.message };
}
}
return results;
}
// Example usage
compareConcept('E11.9', 'ICD10CM').then(results => {
for (const [version, info] of Object.entries(results)) {
if (info.found) {
console.log(`Version ${version}: ${info.conceptName}`);
} else {
console.log(`Version ${version}: Not found`);
}
}
});
```
## Version-Specific Features
### Temporal Queries
Query concepts as they existed at specific points in time:
```bash theme={null}
# Get diabetes concepts as they existed in 2024v2
curl -X GET "https://api.omophub.com/v1/concepts/search?query=diabetes&vocab_release=2024.2" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Historical Analysis
Compare vocabulary evolution over time:
```bash theme={null}
# Compare concept relationships across versions
curl -X GET "https://api.omophub.com/v1/concepts/123456/relationships?vocab_release=2024.2" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Migration Support
Validate concept mappings when upgrading between versions:
```bash theme={null}
# Check if concepts are still valid in newer version
curl -X GET "https://api.omophub.com/v1/concepts/validate?codes=E11.9,I10&vocab_release=latest" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Version Support Policy
### Active Versions
* **Current + 2 previous releases** are actively supported
### Deprecated Versions
* **Removal notice** provided 90 days before discontinuation
## Troubleshooting
### Common Issues
#### Version Not Found
```json theme={null}
{
"error": "VOCABULARY_VERSION_NOT_FOUND",
"message": "Vocabulary version '2023.1' is not available",
"available_versions": ["2026.1", "2025.2", "2025.1", "2024.2"]
}
```
**Solution**: Use the `/v1/vocabularies/releases` endpoint to check available versions.
#### Version Mismatch in Results
If you receive unexpected results, verify the version is being applied correctly:
```bash theme={null}
# Check which version was actually used
curl -X GET "https://api.omophub.com/v1/concepts/search?query=diabetes&vocab_release=2024.2" \
-H "Authorization: Bearer YOUR_API_KEY" \
-v # Verbose mode shows response headers
```
Look for the `X-Vocab-Release-Used` header in the response to confirm the version.
## FAQ
The system automatically uses the default version (`2026.1`). This provides the most recent vocabulary data and optimal performance through caching.
No, each API request operates against a single vocabulary version. To compare across versions, make separate requests for each version.
Major vocabulary updates typically occur twice a year (February and August), following the [OHDSI vocabulary release cycles](https://github.com/OHDSI/Vocabulary-v5.0/wiki/Release-planning).