Search & RAG — reference
Exhaustive reference for content search and the three streaming inference surfaces: every parameter, field, mode, limit, error code, the stream event vocabulary, and an honest "Notes & limits" section stating what each feature does not do.
For request/response wire detail at the endpoint level, see the generated API reference (the OpenAPI / Scalar spec). This page documents the SDK-level surface and the behavior that the spec alone does not capture.
Version note. The API spec is at
0.45.0. Nothing on the search side of this page is 0.26-only, so it works on any current client. The optional inference flagallowGlobalRegion(region serving, below) is a 0.27 addition. Where the SDK method name differs from a raw field name, the SDK name is given.
Content search — client.search.content(req)
Unified search across documents and records. Requires the search:r scope to call. Results
are additionally enforced per row against what the calling token could read directly: a
scoped token sees only results it holds the read grant for — documents:r for documents,
records:r (or records:r:<type> for a single type) for records — and only within its
ownership data scope. A token with search:r but no read grant returns an empty result set.
The same per-row enforcement applies to RAG retrieval (/v1/rag).
Request parameters
| Parameter | Type | Default | Notes |
|---|---|---|---|
query | string | — (required) | Natural-language or keyword query. |
mode | 'TEXT' | 'SEMANTIC' | 'HYBRID' | HYBRID | Keyword relevance / meaning-based similarity / fused ranking. |
limit | integer | 20 | Max results. Valid range 1–100; out-of-range is rejected with 400. |
offset | integer | 0 | Results to skip — paging (see Paging below). |
contentTypes | ('documents' | 'records')[] | both | Narrow to one content type. Omitted, empty, or both values ⇒ unified. |
typeName | string | — | Restrict hits to one schema type — for example patient or runbook. Applies to both documents and records: any item whose bound schema type matches. On its own it narrows both content types; combine it with contentTypes to narrow within one (e.g. contentTypes: ['documents'] + typeName: 'runbook' ⇒ runbook documents). Untyped content (no schema) never matches. Documents ingested before this facet shipped must be reindexed to be matchable — see the note below. |
folderId | string (uuid) | — | Restrict to content in this exact folder. |
rootFolderId | string (uuid) | — | Restrict to this folder and all descendants. Use instead of folderId. |
userId | string (uuid) | — | Ownership filter — content owned by this user. |
scope | string | — | Ownership filter — content owned by this entity, as <namespace>:<value> (org:..., client:..., or a namespace you registered). One entry per query; pair it with userId for a second dimension, or use scopeFilters (below) for more than one scope namespace at once. Mutually exclusive with scopeFilters. |
scopeFilters | string[] | — | Ownership filter for MORE THAN ONE scope namespace in a single query — e.g. ["org:<id>", "client:<id>"] to narrow to one specific client within one specific org. Each entry uses the same <namespace>:<value> form as scope. Naming the same namespace twice is rejected (400), as is supplying both scope and scopeFilters. Capped at 16 entries. |
filters | object | — | Field-level metadata filters; AND-combined across keys. See Filter grammar. |
createdAfter | string (ISO-8601) | — | Content created at/after this UTC timestamp. |
createdBefore | string (ISO-8601) | — | Content created at/before this UTC timestamp. |
uniqueDocuments | boolean | false | When true, at most one hit per source document. |
minSimilarity | number 0.0–1.0 | — | Minimum semantic similarity; hits below are excluded (semantic / hybrid). |
minTextRelevance | number 0.0–1.0 | — | Relative keyword-relevance floor, as a fraction of the top hit's score (e.g. 0.5 keeps hits at least half as relevant as the best). Applies to TEXT / HYBRID. Omit or ≤0 keeps all. |
textMode | 'OR' | 'AND' | 'PHRASE' | 'COMPLEX' | OR | Keyword sub-mode (see Keyword sub-modes). Applies to TEXT / HYBRID. |
slop | integer ≥0 | 0 | Phrase-match slop: intervening positions tolerated between terms when textMode='PHRASE'. Ignored otherwise. |
requireComplete | boolean | false | Fail-closed override: return 503 instead of degraded partial results when a leg is unavailable. |
Scoping documents by type — reindex note. The document side of the
typeNamefacet relies on an index key that is written when a document is indexed. Records have always carried it; documents ingested before this facet shipped do not, so they will not match atypeNamefilter until they are reindexed. New and updated documents pick it up automatically. To make older documents matchable, re-index them (re-ingest, or touch them so they re-index). Records are unaffected.
Keyword sub-modes (textMode)
For TEXT and HYBRID searches, textMode controls how query terms combine in the
keyword leg:
OR(default) — match any term; broadest recall.AND— require all terms; higher precision.PHRASE— require terms as a contiguous sequence (tunable withslop).COMPLEX— full keyword query syntax (boolean operators, field-scoped clauses, range filters). Use only when you need expression-level control; the query is parsed as a structured expression rather than a bag of terms.
Filter grammar (filters)
Each top-level key is a metadata field declared filterable on the schema (or a built-in document field). Top-level keys are AND-combined. Each value is one of:
- Scalar (string / number / boolean) — equality, e.g.
{ "status": "open" }. - Array of scalars — OR-set (match any), e.g.
{ "tag": ["red", "blue"] }. - Operator map — closed set of operators:
- Scalar operand:
$eq,$ne,$gt,$gte,$lt,$lte. Operators in one map are AND-combined, so{ "price": { "$gte": 100, "$lte": 500 } }is a closed range. - Array operand:
$in,$nin. Cannot be combined with other operators in the same map.
- Scalar operand:
Numbers and booleans match typed (the field must have been ingested under a typed schema).
A numeric filter operand is held to the same signed 64-bit range as stored values — an
out-of-range number in the request body is rejected with 400; match a large id you stored
as a string with string equality, not a numeric operator.
Dates may be ISO-8601 strings or epoch millis. Filter keys are validated (^[A-Za-z_] [A-Za-z0-9_-]*$); unknown operators, non-scalar operands, malformed keys, and any attempt
to filter on a reserved tenancy/ownership key are rejected with 400. You cannot widen your
access through the filter map — ownership scope is enforced separately.
Response shape
search.content returns a flat object (it is not wrapped in the { data, nextCursor }
cursor envelope used by list/lookup endpoints — see Paging):
| Field | Type | Notes |
|---|---|---|
results | SearchResult[] | Matched chunks, ranked (highest score first). May be empty. |
totalResults | integer | Approximate matching-pool size. 0 for a miss. Not a reliable paging signal — use hasMore instead. |
hasMore | boolean | True when more matching results exist past this page; false once you've reached the last page or the offset maximum (200). |
searchTimeMs | integer | Server-side execution time. Reported even on an empty result. |
degraded | boolean | True when one leg was unavailable and results came from the survivor only. |
degradedLegs | string[] | Which legs were unavailable: "text" (keyword) and/or "vector" (semantic). Empty when not degraded. |
Each SearchResult:
| Field | Type | Notes |
|---|---|---|
documentId | string (uuid) | Source entity id. Use with getDocument / getRecord. (This is the source id, not an internal index id.) |
externalId | string | null | The matched item's externalId as you supplied it, alongside documentId — lets you correlate a hit back to your own record identity without a follow-up lookup. Null when the item was ingested without one, or was indexed before this field existed and hasn't been reindexed since. |
sourceType | 'PartnerDocument' | 'GenericRecord' | Document vs. record discriminator — the two literal strings the API returns; branch on it when rendering mixed results. |
score | number | Primary sort key, higher is more relevant. Its scale depends on mode — see Notes below; a HYBRID score is not comparable to a TEXT- or SEMANTIC-only score. |
textScore | number | Keyword (relevance) sub-score. Non-zero in HYBRID when the keyword leg contributed. In TEXT-only mode it's rank-derived (see Notes below) — meaningful for ordering within one response, not a raw BM25 magnitude. |
semanticScore | number | Semantic similarity sub-score. Non-zero in SEMANTIC / HYBRID. |
chunkText | string | The specific chunk that matched. Feed this (or contextText) to a model. |
contextText | string | The wider surrounding passage containing the chunk — better grounding context. |
snippet | string | Highlighted excerpt with query terms emphasized, for display. May be null for a semantic-only hit (use chunkText). |
metadata | object | Metadata supplied at ingest (title, folderId, custom fields). |
createdAt | string (ISO-8601) | Source content creation time. |
Paging
search.content has no nextCursor — it is not enveloped. Page by combining limit
with offset:
const page1 = await client.search.content({ query, mode, limit: 20, offset: 0 });
const page2 = await client.search.content({ query, mode, limit: 20, offset: 20 });
limit is capped at 100; consecutive pages are disjoint. This differs from list/lookup
endpoints, which return { data, nextCursor } and are drained by feeding nextCursor back
as startFrom. For pulling recent content deterministically, prefer a createdAfter
window over deep offset paging.
Notes & limits — search
- Index/search mode must line up. Content indexed for one strategy only will not appear in a search requiring the other. Indexing mode is a property of the content (set at ingest / on the schema); search mode is a property of the query.
- Only searchable fields participate in keyword relevance. A query matching only a non-searchable field returns nothing — by design.
- Sensitive fields never enter the index. They cannot be searched, ranked, or surfaced under any scope (index-time exclusion).
textScoreinTEXT-only mode is rank-derived, not a raw BM25 magnitude. That path returns highlighted snippets and already-correctly-ordered hits but never exposes the raw per-hit keyword score, sotextScorethere reflects the hit's relative rank — meaningful for ordering within one response, not comparable across requests or againstHYBRID/SEMANTIC.HYBRID'sscoreis a Reciprocal Rank Fusion (RRF) value, not a 0–1 confidence — its magnitude looks nothing likeTEXT/SEMANTIC's. RRF combines the keyword and semantic legs by RANK POSITION only (never by raw score), as1 / (k + rank)per leg, summed across legs it appears in, with the standardk = 60. That makes everyHYBRIDscore small and tightly clustered near the same constant: a hit ranked top on a single leg scores1/61 ≈ 0.0164; ranked top on both legs,2/61 ≈ 0.033— near the practical ceiling. This is expected, not a sign every result is a weak match: RRF is designed to fuse two differently-scaled rankings by ORDER, not to reproduce either leg's own scale, so don't read aHYBRIDscore of0.02as "2% relevant," and don't set a similarity-style threshold (e.g.score > 0.5) against it — it will discard everything. By contrast,TEXT- orSEMANTIC-only mode'sscoreis the search engine's own native score for that leg (roughly a 0–1 cosine similarity forSEMANTIC) — a genuinely different scale, which is why the two modes' scores are never comparable to each other or toHYBRID.minTextRelevanceapplies only toTEXT/HYBRID;minSimilarityapplies only toSEMANTIC/HYBRID.- A miss is a
200, not a404. Emptyresults,totalResults: 0. - Degradation is silent unless you check. Inspect
degraded/degradedLegs, or setrequireComplete: trueto turn a degraded leg into a503. INDEXEDmeans indexing is complete for both search strategies; if a search still misses freshly written content, suspect your rate limit before indexing lag. The keyword index is immediately consistent — a document is matchable byTEXTthe moment it isINDEXED— and in practice the semantic (vector) index is normally immediately queryable too. A rate-limited request (yours or a retry the SDK made on your behalf) can silently take some time to actually run, which looks identical to "my data isn't there yet" — see the rate limits guide. A freshly indexed document that matches on text is designed to surface inHYBRIDandRAGat keyword speed (its semantic score fills in once the vectors catch up), but this fallback exists in code only foruniqueDocuments: false, and like/v1/search's default (uniqueDocuments: true, which RAG's grounding search also uses) it rests only on a unit test with mocked inputs, not a live-verified guarantee; treat it as best-effort on either path. A query that can match a document only semantically (pure-SEMANTIC, or a hybrid query sharing no words with it) has no fallback at all. See explanation.md § "Freshly indexed content".- Cross-content search returns mixed types. Always branch on
sourceTypewhen rendering unified results.
Inference surfaces — client.inference.*
Three streaming surfaces, all returning an async iterable of SSE events, all requiring the
inference:r scope, all sharing one pre-flight check sequence. Inference runs against
AWS-hosted models inside the Vectros perimeter (in-perimeter for the data plane).
Streaming model (shared)
Each surface returns an async iterable; iterate it to consume events in arrival order. Every
event carries an event field naming its type, so a consumer can dispatch on one key. On the
wire it is standard Server-Sent Events (event: <type> / data: <json> framed by a blank
line) — any compliant SSE reader works; the SDK presents it as an async iterator.
Shared event vocabulary:
| Event | Fields | When |
|---|---|---|
content_delta | delta (string) | One chunk of generated text. Append each to build the answer. |
done | inputTokens, outputTokens, model, platformCreditsCharged, inferenceBalanceCentsCharged, optionally cacheReadTokens / cacheCreateTokens | Terminal event with token counts, resolved model id, and per-call cost. Exactly one. |
error | message, code | A mid-stream model failure. |
Surface-specific events are listed under each surface below.
Pre-flight checks (shared, fixed order)
Run before any model invocation; cheaper checks first:
- Action scope →
403. A scoped token must carryinference:r(or a wildcard). Rootsk_*keys carry wildcard scope and pass by construction. The403does not enumerate the missing action. - Monthly credit limit →
402. Once the period's cumulative credits exceed the plan's ceiling, inference rejects until the period rolls or the plan is upgraded. - Burst rate limit →
429. Per-tenant request-rate ceiling, scaling with plan tier. - Inference billing gate →
402. In balance mode (default on lower tiers), a per-account pre-funded balance must be positive, else402 Insufficient inference balance. In usage mode (Enterprise-shaped, post-billed), accumulated usage is checked against a contractual cap, else402when the cap is reached.
The token cost of a call is metered and recorded on stream finalization (after the stream closes, or on a broken pipe with partial output). A transient accounting failure does not fail the in-flight response — the balance on the next call may briefly lag.
Grounded corpus answers — client.inference.ragInference(req)
Retrieve-then-generate over your indexed content.
Request parameters:
| Parameter | Type | Default | Notes |
|---|---|---|---|
query | string | — (required) | The question to ground and answer. |
model | string | tier default | Model alias. See Model catalog. |
maxTokens | integer | 1024 | Output cap. Capped at 4096 (tighter than chat — retrieved context shares the input budget). |
temperature | number | 0.3 | Sampling temperature. |
instructions | string | — | Optional extra instructions for the answer. |
search | object | — | Retrieval params (below). |
search sub-object mirrors content search: mode (default HYBRID), limit
(default 10, capped at 50 — this is the RAG topK), userId, scope, scopeFilters,
folderId, rootFolderId, typeName, filters, contentTypes, createdAfter,
createdBefore, requireComplete. scope/scopeFilters behave exactly as documented in the
content-search parameter table above — scope for one ownership namespace, scopeFilters (an
array, mutually exclusive with scope) for more than one.
Event sequence: search_results → optional truncation_warning → content_delta+ →
done.
| Event | Fields | Notes |
|---|---|---|
search_results | results[], totalResults, searchTimeMs, degraded, degradedLegs | Always emitted, even when results is empty. Each entry: documentId, externalId, score, textScore, semanticScore, chunkText, contextText, snippet, metadata, sourceType, typeName, createdAt. These are your citations. |
truncation_warning | resultsRequested, resultsUsed, truncatedCount, noContentCount, reason | Emitted before the answer if any retrieved passage was dropped from grounding — either because it didn't fit the context budget (truncatedCount) or because it had no groundable text at all (noContentCount, rare in practice). reason is "context_window_budget", "no_groundable_content", or "context_window_budget_and_no_content" when both occurred; the two count fields let you attribute the drop precisely without parsing reason. |
Behavior:
- With
search.requireComplete: true, a degraded retrieval leg causes the call to reject before the stream opens (503) instead of grounding on partial results. - An empty retrieval still emits
search_results(empty) and still streams an answer (typically stating that nothing relevant was found). - A scoped token's data scope is enforced on the retrieval step.
Single-document Q&A — client.inference.documentAsk(req)
Ask one question against one document's full text. No retrieval step. Requires inference:r
(to run the model) and documents:r (to read the document) — a scoped token must hold
documents:r for the document, within its data scope; inference:r alone returns 404.
Request parameters:
| Parameter | Type | Default | Notes |
|---|---|---|---|
id | string (uuid) | — (required) | The document to ask against (in the request body). |
prompt | string | — (required) | The question. |
model | string | tier default | Model alias. |
maxTokens | integer | 2048 | Output cap. Capped at 8192. |
Event sequence: document_context → content_delta+ → done.
| Event | Fields | Notes |
|---|---|---|
document_context | documentId, title, textBytes, model | The document loaded, its size, and the resolved model. Fires before any generated text. |
Errors:
409(before the stream opens) — the document is not askable yet: it is still processing (not yet fully indexed), it failed ingest, or its text is not retained (a file uploaded withstoreText: false— the extracted text is discarded after indexing, so there is no full text to load). Freshly-ingested documents commonly return409until indexing completes.413(before the stream opens, no credits charged) — the document's estimated input size exceeds the cap (32,000 input tokens, ~25 pages). Payload:message,estimatedTokens,limitTokens. Branch on this and re-route to RAG.404— the document does not exist, belongs to another tenant, is out of your token's data scope, or your token lacksdocuments:rfor it. All return the identical404; the endpoint never reveals existence outside your read access.
Stateless chat — client.inference.chatInference(req)
Single-turn completion. No retrieval, no stored state.
Request parameters:
| Parameter | Type | Default | Notes |
|---|---|---|---|
messages | { role, content }[] | — (required) | Conversation. A system role message becomes the system prompt; user / assistant messages pass through. |
model | string | tier default | Model alias. |
maxTokens | integer | 2048 | Output cap. Capped at 8192. |
temperature | number | 0.7 | Sampling temperature. |
topP | number | — | Nucleus-sampling parameter (optional). |
Event sequence: content_delta+ → done.
Chat stores nothing. For multi-turn, append the assistant's reply to your messages array
and re-send the whole array next turn.
Model catalog — client.inference.listInferenceModels()
Lists the models the calling key's plan tier can reach.
Response:
| Field | Type | Notes |
|---|---|---|
models | Model[] | Available models for this key. |
defaultModel | string | The alias used when a call omits model. Reachable on the free plan. |
Each Model:
| Field | Type | Notes |
|---|---|---|
id | string | Alias, e.g. claude-haiku-4-5, claude-sonnet-5, claude-opus-4-8. Matches the model vendor's marketing names. |
name | string | Display name. |
provider | string | Model provider. |
contextWindow | integer | Context window size in tokens. |
inputCreditsPer1kTokens | number | Base (non-US) input rate — equal to regionPricing.base.input. Default-served (US) requests are billed regionPricing.us.input instead; see below. |
outputCreditsPer1kTokens | number | Base (non-US) output rate — equal to regionPricing.base.output. Default-served (US) requests are billed regionPricing.us.output instead; see below. |
regionPricing | object | Per-region rate breakdown for this model — base, us, regionPremiumFactor, globalAvailable. See "Region pricing" below. |
availableOn | string[] | Plan tiers that may call this model (e.g. free, starter, pro, scale, enterprise). |
Requesting a model your plan does not include returns a 402 pointing to upgrade. A lighter
model is available on every tier; more capable models require higher tiers.
Region serving (allowGlobalRegion)
All three inference surfaces (chat, RAG, document-ask) accept an optional boolean
allowGlobalRegion in the request body.
| Field | Type | Default | Meaning |
|---|---|---|---|
allowGlobalRegion | boolean | tenant residency default | Opt this request into the lower-cost global (non-US) region path. |
- The tenant's residency default is US serving, applied when the flag is omitted. US serving is the fail-closed default and carries a region premium.
- Setting
allowGlobalRegion: truelets an entitled tenant serve the request from the global region at a lower rate. Entitlement is gated on a signed global-processing waiver (dev portal → Settings → Data Residency & Region; see the operations & trust reference for the residency posture this entitlement governs). - If
allowGlobalRegion: trueis sent by a tenant that is not entitled, the request is rejected with403— it is not silently downgraded or upgraded. Region choice never changes which content is retrieved, only where the model runs and the price.
Region pricing (regionPricing)
Every model returned by the model catalog
carries a regionPricing object so you can compute the exact billed rate for either region
without a client-side multiply:
| Field | Type | Notes |
|---|---|---|
regionPricing.base | { input, output } | Per-1K credit rates billed when a request is served from the global (non-US) region — i.e. allowGlobalRegion: true on an entitled tenant. Equal to the model's inputCreditsPer1kTokens / outputCreditsPer1kTokens. |
regionPricing.us | { input, output } | Per-1K credit rates billed when a request is served from the US region — the default. Equal to base multiplied by regionPremiumFactor. |
regionPricing.regionPremiumFactor | number | Multiplier applied to base to get us. 1.0 means no premium (base == us); a Bedrock cross-region model typically carries a premium above 1.0 (e.g. 1.1). |
regionPricing.globalAvailable | boolean | Whether this model can be served from the global region at all. A model with globalAvailable: false always bills us regardless of allowGlobalRegion. |
A default-served (no allowGlobalRegion, or an unentitled tenant) request is always billed
regionPricing.us; an entitled tenant opting in with allowGlobalRegion: true is billed
regionPricing.base. Use this object to show accurate per-region pricing in a model picker
before the caller decides whether to opt into global serving.
Error codes (inference)
| Code | Surface | Meaning |
|---|---|---|
400 | all | Malformed request (e.g. missing query / messages / prompt, bad filter key). |
402 | all | Monthly credit limit exceeded, insufficient inference balance, usage cap reached, or a requested model the plan does not include. |
403 | all | Token scope does not permit inference (inference:r missing); or allowGlobalRegion: true was sent but this tenant is not entitled to global-region serving (no signed global-processing waiver). |
404 | document-ask | Document not found / cross-tenant / out-of-scope (uniform — existence never revealed). |
409 | document-ask | Document not askable yet — still processing (not yet indexed), failed ingest, or text not retained (a file uploaded with storeText: false). Returned before the stream opens. |
413 | document-ask | Document exceeds the input-token cap (before the stream opens; no credits charged). |
429 | all | Burst rate limit exceeded. |
503 | RAG, search | A retrieval/search leg was unavailable and requireComplete / requireComplete: true was set. |
Notes & limits — inference
- Hard output caps per surface: chat 8192, RAG 4096, document-ask 8192 output tokens.
Document-ask additionally caps input at 32,000 tokens (~25 pages) with a
413before the stream opens. AmaxTokensabove a surface's cap is floored to the cap. - RAG topK capped at 50.
search.limitdefaults to 10, max 50 — retrieved context shares the model's input budget, so unbounded topK would push the prompt past the context window. - Chat is stateless; there is no managed conversation state. No server-side thread
store, no assistants registry. Multi-turn is the caller's responsibility — re-send the
messagesarray each turn and budget the history against the model's context window. - Document-ask is single-document. No multi-document Q&A endpoint. For multi-document
grounding, use RAG (retrieval picks relevant passages across the corpus) or stitch
multiple
/askcalls at the application layer. - Cost is recorded on finalization. The
doneevent carriesplatformCreditsChargedandinferenceBalanceCentsCharged; an accounting hiccup will not fail an in-flight response, so the next call's balance may briefly lag. - Cache-token fields are forward-declared.
donemay carrycacheReadTokens/cacheCreateTokens; the current billing formula does not yet apply a cache discount. Consumers already reading these will see the reduction when it lands, with no code change. - In-perimeter scope. The in-perimeter (no third-party model-vendor egress) guarantee is for the data plane — the content you store and retrieve through Vectros. Specific compliance coverage terms are addressed in the security and compliance documentation, not asserted here.
- The model catalog is the source of truth. Handlers gate on the live catalog at request
time, so a model going generally available or being retired takes effect immediately —
what
listInferenceModelsreturns is what the deployed handlers accept.
Where to go next
- how-to.md — runnable guides for each call on this page.
- explanation.md — the concepts behind the modes, grounding context, and the three inference surfaces.
- ../data-model/reference.md — schema field declarations (searchable, filterable, sensitive) that govern what search indexes.
- ../operations-trust/compliance.md — sensitive-field protections, isolation guarantees, and the in-perimeter inference posture.