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

# Searching OMOP concepts

> Search OMOP concepts across SNOMED, ICD-10, LOINC, and RxNorm from the OMOPHub Node.js SDK with keyword, semantic, and filtered queries.

## Basic Search

Search for concepts by text. The SDK normalises three possible server response shapes (`{ concepts: [...] }`, legacy `{ data: [...] }`, bare `Concept[]`) into a stable `SearchResult` - `data.concepts` is always a `Concept[]`:

```ts theme={null}
const { data, error } = await client.search.basic('diabetes mellitus', {
  pageSize: 20,
});
if (error) throw new Error(error.message);

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

### Parameters

| Parameter            | Type                                        | Default  | Description                       |
| -------------------- | ------------------------------------------- | -------- | --------------------------------- |
| `query` (positional) | string                                      | required | Search query string               |
| `vocabularyIds`      | string\[]                                   | -        | Filter by vocabulary IDs          |
| `domainIds`          | string\[]                                   | -        | Filter by domain IDs              |
| `conceptClassIds`    | string\[]                                   | -        | Filter by concept class IDs       |
| `standardConcept`    | `'S' \| 'C' \| 'N'`                         | -        | Filter by standard concept flag   |
| `includeSynonyms`    | boolean                                     | false    | Search synonyms too               |
| `includeInvalid`     | boolean                                     | false    | Include invalid concepts          |
| `minScore`           | number                                      | -        | Minimum relevance score (0.0–1.0) |
| `exactMatch`         | boolean                                     | false    | Require exact match               |
| `page`               | number                                      | 1        | Page number (1-based)             |
| `pageSize`           | number                                      | 20       | Results per page                  |
| `sortBy`             | `'relevance' \| 'name' \| 'code' \| 'date'` | -        | Sort field                        |
| `sortOrder`          | `'asc' \| 'desc'`                           | -        | Sort order                        |

## Filter Combinations

```ts theme={null}
// By vocabulary
await client.search.basic('heart attack', {
  vocabularyIds: ['SNOMED', 'ICD10CM'],
});

// By domain
await client.search.basic('aspirin', {
  domainIds: ['Drug'],
  pageSize: 10,
});

// Standard concepts only
await client.search.basic('diabetes', {
  standardConcept: 'S',
  vocabularyIds: ['SNOMED'],
});

// Combined
await client.search.basic('myocardial infarction', {
  vocabularyIds: ['SNOMED'],
  domainIds: ['Condition'],
  standardConcept: 'S',
  includeSynonyms: true,
  minScore: 0.5,
  pageSize: 20,
});
```

## Autocomplete

Get suggestions as the user types. The response is `{ query, suggestions: [...] }` - each entry nests the concept under `suggestion`:

```ts theme={null}
const { data } = await client.search.autocomplete('diab', { pageSize: 10 });
console.log(data?.query); // "diab" (echoed)

for (const entry of data?.suggestions ?? []) {
  console.log(entry.suggestion.concept_name);
  console.log(entry.match_score); // optional scoring field
}
```

## Pagination

### Manual Pagination

```ts theme={null}
const first = await client.search.basic('diabetes', { page: 1, pageSize: 50 });

const pagination = first.meta?.pagination;
console.log(`Total: ${pagination?.total_items}`);
console.log(`Pages: ${pagination?.total_pages}`);
console.log(`Has next: ${pagination?.has_next}`);

if (pagination?.has_next) {
  const second = await client.search.basic('diabetes', { page: 2, pageSize: 50 });
}
```

### Auto-Pagination Iterator

`basicIter` is an async generator that walks every page and yields one concept at a time:

```ts theme={null}
let count = 0;
for await (const concept of client.search.basicIter('diabetes', { pageSize: 100 })) {
  console.log(concept.concept_name);
  count++;
  if (count >= 500) break;
}
```

<Warning>
  Async iterators throw `OMOPHubIteratorError` on page failure (generators can't gracefully yield discriminated errors). Use the eager `basicAll(...)` variant if you prefer accumulating errors as values. See [Error Handling](/sdks/node/error-handling#async-iterators).
</Warning>

### Eager Collect

```ts theme={null}
const { data, errors, pagesFetched } = await client.search.basicAll('diabetes', {
  pageSize: 100,
  maxPages: 10,
});

console.log(`Collected ${data.length} concepts across ${pagesFetched} pages`);
if (errors.length > 0) console.warn('Partial result:', errors);
```

## Advanced Search

POST-based search with relationship filters:

```ts theme={null}
const { data } = await client.search.advanced('diabetes', {
  vocabularyIds: ['SNOMED', 'ICD10CM'],
  domainIds: ['Condition'],
  standardConceptsOnly: true,
  pageSize: 50,
});

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

### Advanced Search Parameters

| Parameter              | Type                   | Default  | Description                 |
| ---------------------- | ---------------------- | -------- | --------------------------- |
| `query` (positional)   | string                 | required | Search query string         |
| `vocabularyIds`        | string\[]              | -        | Filter by vocabulary IDs    |
| `domainIds`            | string\[]              | -        | Filter by domain IDs        |
| `conceptClassIds`      | string\[]              | -        | Filter by concept class IDs |
| `standardConceptsOnly` | boolean                | false    | Only standard concepts      |
| `includeInvalid`       | boolean                | false    | Include invalid concepts    |
| `relationshipFilters`  | `RelationshipFilter[]` | -        | Relationship-based filters  |
| `page`                 | number                 | 1        | Page number                 |
| `pageSize`             | number                 | 20       | Results per page            |

### Relationship Filters

```ts theme={null}
await client.search.advanced('diabetes', {
  relationshipFilters: [
    { relationshipId: 'Is a', targetConceptId: 4116142 },
  ],
  standardConceptsOnly: true,
});
```

## Semantic Search

Embedding-based natural-language search. The SDK normalises the response so `data.results` is always a populated array:

```ts theme={null}
const { data } = await client.search.semantic('heart attack', { pageSize: 10 });

for (const concept of data?.results ?? []) {
  console.log(`${concept.concept_name}: ${concept.similarity_score.toFixed(2)}`);
}
```

With filters:

```ts theme={null}
const filtered = await client.search.semantic('diabetes mellitus', {
  vocabularyIds: ['SNOMED'],
  domainIds: ['Condition'],
  threshold: 0.5,
  pageSize: 20,
});
```

### Semantic Search Parameters

| Parameter            | Type                | Default  | Description                |
| -------------------- | ------------------- | -------- | -------------------------- |
| `query` (positional) | string              | required | Natural language query     |
| `vocabularyIds`      | string\[]           | -        | Filter by vocabularies     |
| `domainIds`          | string\[]           | -        | Filter by domains          |
| `standardConcept`    | `'S' \| 'C' \| 'N'` | -        | Filter by standard flag    |
| `conceptClassId`     | string              | -        | Filter by concept class    |
| `threshold`          | number              | 0.5      | Minimum similarity (0–1)   |
| `page`               | number              | 1        | Page number                |
| `pageSize`           | number              | 20       | Results per page (max 100) |

### Semantic Search Iterator

```ts theme={null}
for await (const concept of client.search.semanticIter('diabetes', { pageSize: 50 })) {
  console.log(concept.concept_name);
}

// Collect with bounds
const { data } = await client.search.semanticAll('heart failure', {
  vocabularyIds: ['SNOMED'],
  threshold: 0.4,
  maxPages: 5,
});
```

## Bulk Lexical Search

Run up to **50** keyword searches in a single API call:

<Note>
  The per-search entries (`BulkBasicSearchInput`) use **snake\_case** keys (`search_id`, `vocabulary_ids`, `domain_ids`, `page_size`) — they're a direct pass-through of the API payload, not a camelCase options object. The outer `defaults` object follows the same convention. This is the only place in the SDK where snake\_case appears at the TypeScript surface.
</Note>

```ts theme={null}
const { data } = await client.search.bulkBasic(
  [
    { search_id: 'q1', query: 'diabetes mellitus' },
    { search_id: 'q2', query: 'hypertension' },
    { search_id: 'q3', query: 'aspirin' },
  ],
  { defaults: { vocabulary_ids: ['SNOMED'], page_size: 5 } },
);

for (const item of data?.results ?? []) {
  console.log(`${item.search_id}: ${item.results.length} results (${item.status})`);
}
```

### Bulk Basic Parameters

| Parameter                   | Type                     | Description                                  |
| --------------------------- | ------------------------ | -------------------------------------------- |
| `searches` (positional)     | `BulkBasicSearchInput[]` | 1–50 search inputs                           |
| `searches[].search_id`      | string                   | Unique correlation ID echoed in results      |
| `searches[].query`          | string                   | Search query                                 |
| `searches[].vocabulary_ids` | string\[]                | Per-search vocabulary filter                 |
| `searches[].domain_ids`     | string\[]                | Per-search domain filter                     |
| `searches[].page_size`      | number                   | Per-search result limit (1–100)              |
| `defaults`                  | `BulkSearchDefaults`     | Shared defaults - per-search values override |

## Bulk Semantic Search

Up to **25** semantic searches per call:

```ts theme={null}
const { data } = await client.search.bulkSemantic(
  [
    { search_id: 's1', query: 'heart failure treatment options' },
    { search_id: 's2', query: 'type 2 diabetes medication' },
    { search_id: 's3', query: 'elevated blood pressure' },
  ],
  { defaults: { threshold: 0.5, page_size: 10 } },
);

for (const item of data?.results ?? []) {
  console.log(`${item.search_id}: ${item.results.length} results`);
  for (const concept of item.results) {
    console.log(`  ${concept.concept_name} (${concept.similarity_score.toFixed(2)})`);
  }
}
```

## Find Similar Concepts

Find concepts similar to a reference. Provide **exactly one** of `conceptId`, `conceptName`, or `query`. The discriminated union enforces this at the type level; a runtime check defends against JS callers:

```ts theme={null}
// By concept ID
const byId = await client.search.similar({ conceptId: 4329847 }); // MI

// By concept name
const byName = await client.search.similar({ conceptName: 'Type 2 diabetes mellitus' });

// By natural language query
const byQuery = await client.search.similar({ query: 'elevated blood sugar' });

// With all options
const { data } = await client.search.similar({
  conceptId: 4329847,
  algorithm: 'semantic',
  similarityThreshold: 0.7,
  pageSize: 50,
  vocabularyIds: ['SNOMED'],
  domainIds: ['Condition'],
  includeScores: true,
  includeExplanations: true,
});

for (const concept of data?.similar_concepts ?? []) {
  console.log(`${concept.concept_name}: ${concept.similarity_score.toFixed(2)}`);
}
```

<Note>
  `similar()` uses a **two-arg signature** `similar(options, requestOptions?)` - its `query` XOR variant would otherwise collide with `PerCallOptions.query` (the escape-hatch params record). Pass `signal`, `headers`, or `idempotencyKey` via the second argument.
</Note>

### Similar Concepts Parameters

| Parameter             | Type                                  | Default    | Description                                    |
| --------------------- | ------------------------------------- | ---------- | ---------------------------------------------- |
| `conceptId`           | number                                | -          | Source concept ID (XOR with conceptName/query) |
| `conceptName`         | string                                | -          | Source concept name                            |
| `query`               | string                                | -          | Natural-language query                         |
| `algorithm`           | `'semantic' \| 'lexical' \| 'hybrid'` | `'hybrid'` | Matching algorithm                             |
| `similarityThreshold` | number                                | 0.7        | Minimum similarity (0–1)                       |
| `pageSize`            | number                                | 20         | Max results (server caps at 1000)              |
| `vocabularyIds`       | string\[]                             | -          | Filter by vocabulary                           |
| `domainIds`           | string\[]                             | -          | Filter by domain                               |
| `standardConcept`     | `'S' \| 'C' \| 'N'`                   | -          | Standard concept filter                        |
| `includeInvalid`      | boolean                               | -          | Include invalid/deprecated                     |
| `includeScores`       | boolean                               | -          | Include detailed scoring                       |
| `includeExplanations` | boolean                               | -          | Include similarity explanations                |

### Algorithm Comparison

| Algorithm  | Best For                 | Speed  |
| ---------- | ------------------------ | ------ |
| `semantic` | Meaning-based similarity | Slower |
| `lexical`  | Text/string similarity   | Faster |
| `hybrid`   | Balanced approach        | Medium |
