Rest API Design for OpenSearch Integration
Portal Search: REST API Reference
Author: Bryan Fauble
- 1 Portal Search: REST API Reference
- 2 Overview
- 2.1 Resources
- 2.2 Design Principles
- 3 Quick Start
- 4 Endpoint Summary
- 5 Security & Authorization
- 6 Data Model
- 6.1 SynonymSet
- 6.2 SynonymRule
- 6.3 ColumnAnalyzerOverride
- 6.4 ColumnAnalyzerOverrideEntry
- 6.5 TextAnalyzer
- 6.6 TextAnalyzerSettings
- 6.7 SearchConfiguration
- 6.8 SearchIndex
- 6.9 SearchIndexState
- 6.10 SearchQuery
- 6.11 SearchResults
- 6.12 SearchHit
- 6.13 FacetColumnResult (reused)
- 7 Enums
- 8 Filter & Sort Types
- 8.1 KeyValue (reused)
- 8.2 KeyRange (reused)
- 8.3 KeyValues
- 8.4 FacetRequest
- 8.5 SortField
- 9 List Request/Response Pattern
- 10 Configuration Notes
- 10.1 SearchConfiguration - Composition and Resolution
- 10.2 Project Settings Integration (Fallback Inheritance)
- 10.3 Defining SQL
- 10.4 Analyzer Configuration
- 10.4.1 Defaults by Column Type
- 10.4.2 Priority Order
- 10.4.3 Multi-Field Mapping
- 10.5 Synonym Configuration
- 10.5.1 Rule Types
- 10.5.2 Multi-Word Handling
- 10.5.3 Synonym Propagation
- 11 Endpoints
- 11.1 Synonym Sets
- 11.1.1 Create Synonym Set
- 11.1.2 Get Synonym Set
- 11.1.3 Update Synonym Set
- 11.1.4 Delete Synonym Set
- 11.1.5 List Synonym Sets
- 11.2 Column Analyzer Overrides
- 11.3 Text Analyzers
- 11.3.1 Get Text Analyzer
- 11.3.2 List Text Analyzers
- 11.4 Search Configurations
- 11.4.1 Create Search Configuration
- 11.4.2 Get Search Configuration
- 11.4.3 Update Search Configuration
- 11.4.4 Delete Search Configuration
- 11.4.5 List Search Configurations
- 11.5 Search Operations
- 11.5.1 Search Async Start
- 11.5.2 Search Async Get
- 11.5.3 Autocomplete
- 11.1 Synonym Sets
- 12 Query Examples
- 13 Error Responses
Overview
Portal Search provides REST endpoints for creating OpenSearch-backed search indexes over Synapse table-like entities and executing full-text search queries against them.
Resources
The API is organized around four standalone configuration resources under /repo/v1/search/* and one SearchIndex Synapse Entity managed via the standard entity controller under /repo/v1/entity/*.
All resources are logically scoped to an Organization (an existing Synapse concept — CRUD and ACL management at /schema/organizations/*), but note that SearchIndex does not have its own organizationId field; it is scoped via the standard entity parentId (Project or Folder).
Resource | Purpose |
|---|---|
SynonymSet | Reusable synonym rules (e.g., |
ColumnAnalyzerOverride | Per-column text analysis overrides. Cannot be deleted while referenced by a |
TextAnalyzer | A standalone reusable resource that defines how text is analyzed (tokenizer, filters, synonym awareness). Referenced by ColumnAnalyzerOverrideEntry ( |
SearchConfiguration | Bundles synonym sets and column overrides. References a default TextAnalyzer ( |
SearchIndex (Entity) | A Synapse Entity ( |
Resources compose as:
SearchIndex → SearchConfiguration → {SynonymSets[], ColumnAnalyzerOverrides[]}.
TextAnalyzer is a standalone resource referenced by ColumnAnalyzerOverrideEntry.indexAnalyzerId/searchAnalyzerId and SearchConfiguration.defaultAnalyzerId.
Design Principles
Organization-scoped authorization for configuration. The four configuration resources (
SynonymSet,ColumnAnalyzerOverride,TextAnalyzer,SearchConfiguration) are Organization-scoped and identified byorganizationId. They are publicly readable; mutations require Organization ACL permissions.SearchIndexis a standard Synapse Entity without its ownorganizationIdfield; it is scoped byparentId(Project or Folder), and its ACL follows normal entity rules.Secure search access. Search queries and autocomplete requests are not fully public. Callers must have:
READon theSearchIndexentity andSufficient permissions to read from the underlying table referenced by
definingSQL(mirrors the MaterializedView authorization pattern).
Build-once semantics with rebuild on update. AOSS indexes are point-in-time snapshots. There are no incremental updates. The lifecycle worker builds (or rebuilds) indexes on both CREATE and UPDATE of
SearchIndexentities.Single-entity constraint.
definingSQLmust reference exactly one entity. Multi-entity JOINs are rejected with 400 Bad Request.Shared resource protection.
SynonymSets andColumnAnalyzerOverrides cannot be deleted while referenced by anySearchConfiguration.Name uniqueness.
For
SynonymSet,ColumnAnalyzerOverride, andSearchConfiguration, names are unique per Organization:UNIQUE(organizationId, name).For
TextAnalyzer, system analyzer names are globally unique; user-defined analyzer names are unique per Organization.For
SearchIndex(as a Synapse Entity), name uniqueness follows the standard entity rules: unique within its parent container (parentId).
Single polling point. Search queries run as async jobs. If an index is still building, the worker automatically retries — the client only polls the async result endpoint.
Quick Start
Step 1: Create Supporting Resources
Create a synonym set in your Organization:
POST /repo/v1/search/synonym/set{
"organizationId": "42",
"name": "NF Disease Terms",
"description": "Synonym mappings for neurofibromatosis-related disease terminology",
"rules": [
{ "ruleType": "EQUIVALENT", "terms": ["NF1", "neurofibromatosis 1", "von Recklinghausen disease"] },
{ "ruleType": "EQUIVALENT", "terms": ["schwannoma", "vestibular schwannoma", "acoustic neuroma"] }
]
}Create a search configuration referencing the synonym set:
POST /repo/v1/search/configuration{
"organizationId": "42",
"name": "NF Portal Config",
"synonymSetIds": ["501"],
"defaultAnalyzerId": "1",
"columnAnalyzerOverrideIds": []
}Step 2: Create a Search Index Entity
SearchIndex is a Synapse Entity, not a standalone /search/index resource. Creation goes through the standard entity controller. Use the SearchIndex concrete type and provide a parentId (a Project or Folder):
POST /repo/v1/entity{
"concreteType": "org.sagebionetworks.repo.model.table.search.SearchIndex",
"parentId": "syn123",
"name": "Studies Search",
"definingSQL": "SELECT studyName, summary, diseaseFocus, species, assay FROM syn52694652",
"searchConfigurationId": "301"
}Sample Response (201 Created):
{
"id": "syn101",
"concreteType": "org.sagebionetworks.repo.model.table.search.SearchIndex",
"parentId": "syn123",
"name": "Studies Search",
"definingSQL": "SELECT studyName, summary, diseaseFocus, species, assay FROM syn52694652",
"searchConfigurationId": "301",
"versionNumber": 1,
"etag": "aaa-bbb-ccc",
"createdBy": "3350396",
"createdOn": "2026-02-18T12:00:00.000Z",
"modifiedBy": "3350396",
"modifiedOn": "2026-02-18T12:00:00.000Z"
}
Reminder for myself based on meetings from the Weekly meeting:
In this first iteration the idea is to drop the requirement for blue-green reindexing and rebuilds of the data and instead rely on the weekly stack migration to occur in order to rebuild that index. This would allow us to significantly cut down on costs.
The flow would generally look like:
Worker 1 continues to publish messages as designed in order to indicate when an index should be built/rebuilt
Worker 2 now changes to only build a NEW index in opensearch IF there is no index present
This allows us to force a rebuild of an index (Such as to remove data that is no longer public) by:
Deleting the current index in opensearch - THIS WILL CAUSE AN OUTAGE OF THE INDEX AT THIS POINT
Perform some sort of update to the table or index in question that would cause a table change event to be fired off
Worker 1 sees that and publishes a rebuild message, and worker 2 picks that message up - Sees that there is NO index, and builds it
The multi-layer config is a useful feature that we should look to include which would allow us to make a single change (Such as updating a synonymSet) in one place and have it applied to 0..* search indexes.