> ## Documentation Index
> Fetch the complete documentation index at: https://docs.omophub.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Working with OMOP concepts

> Work with OMOP medical concepts in the OMOPHub Node.js SDK - retrieve, search, and inspect concept details, synonyms, and relationships from TypeScript.

## Get a Concept

Retrieve a concept by its OMOP concept ID:

```ts theme={null}
const { data, error } = await client.concepts.get(201826);
if (error) throw new Error(error.message);

console.log(data.concept_name);   // "Type 2 diabetes mellitus"
console.log(data.vocabulary_id);  // "SNOMED"
console.log(data.domain_id);      // "Condition"
```

The SDK accepts `conceptId === 0` - the OMOP "unmapped" sentinel - and forwards it to the server without client-side validation.

## Get by Vocabulary Code

Look up a concept using a vocabulary-specific code:

```ts theme={null}
// SNOMED concept by its code
const { data } = await client.concepts.getByCode('SNOMED', '44054006');
console.log(data?.concept_name); // "Type 2 diabetes mellitus"

// ICD-10-CM concept
const result = await client.concepts.getByCode('ICD10CM', 'E11');

// Include synonyms and relationships
const enriched = await client.concepts.getByCode('SNOMED', '44054006', {
  includeSynonyms: true,
  includeRelationships: true,
});
console.log(enriched.data?.synonyms);
console.log(enriched.data?.relationships);

// Specific vocabulary release
const pinned = await client.concepts.getByCode('SNOMED', '44054006', {
  vocabRelease: '2025.2',
});
```

## Batch Get Concepts

Retrieve up to 100 concepts in a single request:

```ts theme={null}
const { data, error } = await client.concepts.batch({
  conceptIds: [201826, 4329847, 1112807],
});
if (error) throw new Error(error.message);

for (const concept of data.concepts) {
  console.log(`${concept.concept_id}: ${concept.concept_name}`);
}

console.log(`Retrieved: ${data.summary?.found}`);
console.log(`Failed: ${data.summary?.failed}`);
```

With optional flags:

```ts theme={null}
const result = await client.concepts.batch({
  conceptIds: [201826, 4329847, 1112807],
  includeRelationships: true,
  includeSynonyms: true,
  includeMappings: true,
  vocabularyFilter: ['SNOMED'],
  standardOnly: false,
});
```

The SDK validates `conceptIds.length` is between 1 and 100 and returns a `validation_error` synthetically (no network call) if outside that range.

## Autocomplete Suggestions

Get concept suggestions for autocomplete UI:

```ts theme={null}
const { data, error, meta } = await client.concepts.suggest('diab', {
  pageSize: 10,
});
if (error) throw new Error(error.message);

// Suggestions are an array - pagination on outer `meta`
for (const concept of Array.isArray(data) ? data : data?.data ?? []) {
  console.log(concept.concept_name);
}

console.log(meta?.pagination?.total_items);
console.log(meta?.pagination?.has_next);
```

<Note>
  `client.concepts.suggest()` returns `data` as either `ConceptSuggestion[]` (bare array) or `{ data: ConceptSuggestion[] }` (envelope), depending on the API version answering the request. Both shapes are valid per the SDK type (`ConceptSuggestion[] | PaginatedData<ConceptSuggestion>`); the `Array.isArray(data) ? data : data?.data ?? []` pattern above handles both. Pagination always lives on the outer `meta.pagination`, never on the inner shape.
</Note>

Filter by vocabulary and domain:

```ts theme={null}
const filtered = await client.concepts.suggest('diab', {
  vocabularyIds: ['SNOMED'],
  domainIds: ['Condition'],
  pageSize: 20,
});
```

## Get Relationships

Get relationships for a concept. Both `client.concepts.relationships(...)` and `client.relationships.get(...)` hit the same wire endpoint - pick whichever reads more naturally at the call site:

```ts theme={null}
const { data, error, meta } = await client.relationships.get(201826);
if (error) throw new Error(error.message);

for (const rel of data.relationships) {
  console.log(`${rel.relationship_id}: ${rel.target_concept_name}`);
}

console.log(`Page ${meta?.pagination?.page} of ${meta?.pagination?.total_pages}`);
```

Filter by relationship type, vocabulary, and direction:

```ts theme={null}
const filtered = await client.relationships.get(201826, {
  relationshipIds: ['Is a', 'Subsumes'],
  vocabularyIds: ['SNOMED'],
  standardOnly: true,
  page: 1,
  pageSize: 50,
});

// Include reverse relationships
const bothDirections = await client.relationships.get(201826, {
  includeReverse: true,
  domainIds: ['Condition'],
});
```

## Get Related Concepts

Find concepts related to a given concept (Phoebe-style relatedness):

```ts theme={null}
const { data, error } = await client.concepts.related(201826, {
  pageSize: 10,
});
if (error) throw new Error(error.message);

for (const related of data.related_concepts) {
  console.log(
    `${related.concept_name} (${related.relatedness_type}): ${related.relatedness_score.toFixed(2)}`,
  );
}
```

Filter by relationship type and minimum score:

```ts theme={null}
const filtered = await client.concepts.related(201826, {
  relationshipTypes: ['Is a', 'Maps to'],
  minScore: 0.5,
  pageSize: 20,
  vocabRelease: '2025.1',
});
```

## Get Recommended Concepts

Get curated concept recommendations using the OHDSI Phoebe algorithm:

```ts theme={null}
const { data } = await client.concepts.recommended({
  conceptIds: [201826, 4329847],
});

for (const group of data?.recommendations ?? []) {
  console.log(`\nRecommendations for ${group.source_concept_id}:`);
  for (const rec of group.recommendations.slice(0, 5)) {
    console.log(`  - ${rec.concept_name} (${rec.vocabulary_id})`);
  }
}
```

With filters and pagination:

```ts theme={null}
const filtered = await client.concepts.recommended({
  conceptIds: [201826],
  relationshipTypes: ['Has finding', 'Associated finding'],
  vocabularyIds: ['SNOMED', 'LOINC'],
  domainIds: ['Condition', 'Measurement'],
  standardOnly: true,
  page: 1,
  pageSize: 50,
});
```

Server-side caps enforced client-side: `conceptIds` ≤ 100, `relationshipTypes` ≤ 20, `vocabularyIds`/`domainIds` ≤ 50. Violations return `validation_error` without hitting the network.

## Navigate Hierarchy

### Get Complete Hierarchy

Fetch ancestors and descendants in one request, in either flat or graph format:

```ts theme={null}
// Flat format (default) - concepts + paths arrays
const flat = await client.hierarchy.get(201826, {
  maxLevels: 3,
  vocabularyIds: ['SNOMED'],
});

console.log(`Total ancestors: ${flat.data?.summary?.total_ancestors}`);
console.log(`Total descendants: ${flat.data?.summary?.total_descendants}`);

// Graph format for visualisation (D3.js, Cytoscape, …)
const graph = await client.hierarchy.get(201826, {
  format: 'graph',
  maxLevels: 3,
});

console.log(`Nodes: ${graph.data?.nodes?.length}`);
console.log(`Edges: ${graph.data?.edges?.length}`);

// Advanced filtering
await client.hierarchy.get(201826, {
  domainIds: ['Condition'],
  relationshipTypes: ['Is a'],
  maxResults: 100,
  includeInvalid: false,
});
```

#### Hierarchy Get Parameters

| Parameter                | Type                | Default  | Description                              |
| ------------------------ | ------------------- | -------- | ---------------------------------------- |
| `conceptId` (positional) | number              | required | The concept ID                           |
| `format`                 | `'flat' \| 'graph'` | `'flat'` | Response format                          |
| `vocabularyIds`          | string\[]           | -        | Filter to specific vocabularies          |
| `domainIds`              | string\[]           | -        | Filter to specific domains               |
| `maxLevels`              | number              | 10       | Max hierarchy levels (server caps at 20) |
| `maxResults`             | number              | -        | Max results per direction                |
| `relationshipTypes`      | string\[]           | -        | Relationship types to follow             |
| `includeInvalid`         | boolean             | true     | Include deprecated/invalid concepts      |

### Get Ancestors

Find parent concepts in the hierarchy:

```ts theme={null}
const { data, meta } = await client.hierarchy.ancestors(201826, {
  maxLevels: 3,
  vocabularyIds: ['SNOMED'],
  relationshipTypes: ['Is a'],
  includeDistance: true,
  includePaths: false,
  includeInvalid: false,
  page: 1,
  pageSize: 100,
});

for (const ancestor of data?.ancestors ?? []) {
  const level = ancestor.min_levels_of_separation ?? 0;
  console.log(`${'  '.repeat(level)}${ancestor.concept_name}`);
}

console.log(`Max depth: ${data?.summary?.max_hierarchy_depth}`);
console.log(`Page ${meta?.pagination?.page} of ${meta?.pagination?.total_pages}`);
```

### Get Descendants

Find child concepts in the hierarchy:

```ts theme={null}
const { data, meta } = await client.hierarchy.descendants(201826, {
  maxLevels: 2,
  vocabularyIds: ['SNOMED'],
  relationshipTypes: ['Is a'],
  includeDistance: true,
  includePaths: false,
  includeInvalid: false,
  domainIds: ['Condition'],
  page: 1,
  pageSize: 100,
});

for (const descendant of data?.descendants ?? []) {
  console.log(descendant.concept_name);
}
```

## Get Relationship Types

Get the catalog of OMOP CDM relationship types:

```ts theme={null}
const { data, meta } = await client.relationships.types({
  page: 1,
  pageSize: 100,
});

for (const relType of data?.relationship_types ?? []) {
  console.log(`${relType.relationship_id}: ${relType.relationship_name}`);
}

console.log(`Total types: ${meta?.pagination?.total_items}`);
```

## Concept Get Options

Include synonyms, relationships, hierarchy, and pin a vocabulary release:

```ts theme={null}
// Concept with synonyms
const withSynonyms = await client.concepts.get(201826, {
  includeSynonyms: true,
});

// Concept with synonyms and relationships
const enriched = await client.concepts.get(201826, {
  includeSynonyms: true,
  includeRelationships: true,
});

// Concept with hierarchy information
const withHierarchy = await client.concepts.get(201826, {
  includeHierarchy: true,
});

// Pin to a specific vocabulary release
const pinned = await client.concepts.get(201826, {
  vocabRelease: '2025.2',
});
```

### Parameters

| Parameter                | Type    | Default  | Description                                                  |
| ------------------------ | ------- | -------- | ------------------------------------------------------------ |
| `conceptId` (positional) | number  | required | OMOP concept ID - `0` is accepted as the "unmapped" sentinel |
| `includeRelationships`   | boolean | false    | Include related concepts                                     |
| `includeSynonyms`        | boolean | false    | Include concept synonyms                                     |
| `includeHierarchy`       | boolean | false    | Include hierarchy summary                                    |
| `vocabRelease`           | string  | -        | Pin to a specific vocabulary release (e.g. `"2025.2"`)       |
