Search DSL
This is the reference for SearchRequest — the request shape used by every search call into a Curiosity Workspace, whether from the UI, a custom endpoint, or an external integration. For the conceptual model see Core concepts → Search model.
Request lifecycle
var request = new SearchRequest("MacBook Air")
.WithTypesFacet("SupportCase", "Device")
.WithFuzziness(1);
var result = await Graph.CreateSearchAsync(request);
return result;
The request describes what to find, the graph executes it, and a SearchResult comes back. There is no separate "execute" step — passing the request to CreateSearchAsync runs it.
Request fields
Query
| Field | Type | Notes |
|---|---|---|
Query.Value |
string |
The user-entered query string. |
Query.Simple |
bool |
If true, disables operator parsing — treat the query as plain text. |
Fuzziness |
int? |
Levenshtein distance allowed per term. Typical values: 1 or 2. |
FilteredStopWords |
HashSet<string> |
Stop words to drop from this specific query. |
Tag |
string |
Free-form label, surfaced in search analytics. |
Tracker |
QueryTracker |
Optional analytics tracker; lets you tie hits to a session or feature. |
Type scope
| Field | Type | Notes |
|---|---|---|
TypesFacet |
HashSet<string> |
Node types eligible as results. |
BeforeTypesFacet |
HashSet<string> |
Applied earlier in the pipeline; narrows the candidate set before scoring. Use this when the scope is definitely known. |
IgnoreNodeTypes |
HashSet<string> |
Types to exclude even if otherwise matched. |
RestrictTo |
HashSet<string> |
Hard restriction list (intersect with whatever types match). |
IncludeMergedOnTypeFacet |
bool |
If true, items merged via MergeRelated count toward the type facet. |
BeforeTypesFacet is the more efficient option for "I always want only SupportCases here." TypesFacet is the right choice when the user is choosing the scope from the UI.
Target set
These restrict the search to nodes computed elsewhere — typically by a graph query — and are how you implement "search within context."
| Field | Type | Notes |
|---|---|---|
TargetUIDs |
ReadOnlyArray<UID128> |
Explicit UID list. Use when the set is small or precomputed. |
TargetQuery |
SimpleQueryForUIDsOnly |
A graph query that resolves to UIDs at search time. |
PreMergeTargetQuery |
SimpleQueryForUIDsOnly[] |
Target queries applied before related-merging logic runs. |
ExcludeUIDs |
ReadOnlyArray<UID128> |
UIDs to remove from the result set. |
ExcludeQuery |
SimpleQueryForUIDsOnly |
Graph-query equivalent of ExcludeUIDs. |
Facets
Each facet kind is a Dictionary<string, …> keyed by field/edge name.
| Field | Type | Use |
|---|---|---|
Filters |
HashSet<UID128> |
Active filter UIDs (e.g. specific facet values). |
RelatedFacets |
Dictionary<string, List<RelatedFacet>> |
Facet on an edge-traversed value (e.g. status). |
NumericFacets |
Dictionary<string, List<NumericFacet>> |
Ranges on numeric properties. |
TimeFacets |
Dictionary<string, TimeFacet> |
Time-window filters. |
QueryFacets |
Dictionary<string, QueryFacet> |
Saved-query-based facets. |
ValueFacets |
Dictionary<string, List<ValueFacet>> |
Single-value property filters. |
Helper methods on SearchRequest (SetTimestampFacet, SetTimeFacet, …) wrap the dictionary form for common cases.
Sort and ranking
| Field | Type | Notes |
|---|---|---|
SortMode |
SortModeEnum |
Relevance, Recency, Connectivity, Custom, etc. |
SortQuery |
SimpleQueryForUIDsOnly |
Custom sort defined as a graph query (for SortMode.Custom). |
SimilarityRanking |
SimilarityRanking |
"More like this" / "less like that" boosting, optionally driven by graph queries. |
TimeDecay |
TimeDecay |
Recency-bias scores (factor, offset, scale, top-N application). |
Vector / hybrid search
| Field | Type | Notes |
|---|---|---|
VectorSearchTypes |
string[] |
Node types to vector-search. If unset, vector retrieval is skipped. |
VectorSearchMode |
VectorSearchMode |
Hybrid (combine) or Only (vector results exclusively). |
VectorSearchResults |
HashSet<UID128> |
Pre-computed vector hits (for client-driven hybrid flows). |
Related-result handling
| Field | Type | Notes |
|---|---|---|
MergeRelated |
Dictionary<string, MergeRelated> |
Collapse multiple hits that share an edge target. |
ExpandRelated |
Dictionary<string, ExpandRelated> |
Inline related-node data into each hit. |
SkipExpandMerge |
HashSet<string> |
Per-type opt-outs. |
Behaviour flags
| Field | Type | Notes |
|---|---|---|
Lazy |
bool |
Defer expensive enrichment until the client asks for a specific hit. |
SaveToHistory |
bool |
Default true; set to false for system-issued searches. |
ShowSaveIfEmpty |
bool |
UI hint — show "save this search" when no results match. |
Pagination
There is no Page/Offset/Limit on the request itself. Result-count control lives on the query that wraps the search, not the request:
var result = await Graph.CreateSearchAsync(request);
var page = result.Results.Skip(50).Take(50); // client-side window
// or, when chaining into a graph query:
var top = Q().FromSearch(request).Skip(50).Take(50).Emit();
Workspace queries have a default limit of 50 nodes. If you want more, set .Take(n) on the wrapping query — there is no implicit upper bound but very large pages should use cursor-style pagination (carry the last-seen UID as ExcludeUIDs and re-query).
Response shape
public class SearchResult
{
public SearchRequest Request;
public UID128 QueryCache;
public UID128 SearchQueryUID;
public string Count;
public SearchStats Stats;
public ReadOnlyArray<SearchHit> Results;
public ReadOnlyArray<UID128> ResultUIDs;
public string TotalElapsedMilliseconds;
public string Highlight;
public bool Incomplete;
public bool StillIndexing;
public string ProCount;
}
SearchHit carries score, highlights, the resolved Node, child-hit pointers, and a FromVectorSearch flag so you can tell which retrieval branch produced the hit:
public class SearchHit
{
public float Score;
public bool Pinned;
public bool FromVectorSearch;
public Node Node;
public TextAndUID[] Highlights;
public UID128 SearchQueryUID;
public ChildHit[] ChildHits;
}
SearchStats gives you a per-request observability snapshot:
public class SearchStats
{
public float ElapsedMilliseconds;
public int TouchedNodes;
public int TouchedEdges;
public bool RestrictedNodesFilteredOut;
public ReadOnlyArray<string> DetailedStats;
}
RestrictedNodesFilteredOut is true when ACL enforcement dropped hits before they reached the user — useful for distinguishing "no results" from "no results you can see." See Permission-aware retrieval.
Common patterns
Search within a graph context
var request = new SearchRequest("screen flicker")
.WithTypesFacet("SupportCase")
.WithTargetQuery(Q().StartAt("Manufacturer", "Apple").Out("Device").In("ForDevice"));
var result = await Graph.CreateSearchAsync(request);
Hybrid keyword + vector
var request = new SearchRequest("battery drains overnight")
.WithTypesFacet("SupportCase");
request.VectorSearchTypes = new[] { "SupportCase" };
request.VectorSearchMode = VectorSearchMode.Hybrid;
var result = await Graph.CreateSearchAsync(request);
Permission-scoped search
var result = await Graph.CreateSearchAsUserAsync(request, userId);
CreateSearchAsUserAsync is the permission-aware variant — it applies the user's ACL graph before scoring. See Access control model.
Error semantics
The search endpoint surfaces failures as standard HTTP / token errors at the transport layer. Logical issues — unknown types, missing facet fields, ACL-filtered empties — come back as a normal SearchResult with Results.Length == 0 and signal:
Incomplete == true— the query was cut off (typically a timeout). Results are still valid but partial.StillIndexing == true— at least one matching field is still being built. Re-run the query later for completeness.RestrictedNodesFilteredOut == true— hits were removed by ACL.
For the catalog of error codes returned by REST/RPC calls (auth, schema, quota, etc.), see Error codes.