POST /v1/namespaces/{ns}/search runs up to three ranked branches over one filtered row set. All branches are optional; at least one is required. label and filter apply to every branch.
Filters
Two shapes are accepted:- Single equality:
{"property": "project", "value": "conare"}— served straight from posting lists. - Array DSL:
[property, op, value]withEq,NotEq,Gt,Gte,Lt,Lte,In(array value), composed with["And", [f1, f2, ...]]/["Or", [...]]up to 16 levels.
false < true. A type mismatch or absent property matches nothing — including for NotEq — never errors. Unknown operators, empty And/Or lists, and non-array In values are 400s naming the offender.
Vector branch
Scores are cosine similarity on L2-normalized vectors, and always exact — the ANN index (IVF + RaBitQ 1-bit codes) shortlists candidates, then reranks them against full-precision vectors. Options:
Filtered vector search returns exactly the ANN-on-the-matching-subset results — the engine widens probing to compensate for selectivity, so filters don’t quietly cost you recall.
Determinism: for a fixed index state, identical query bytes with identical knobs return byte-identical hit lists — at any thread count or concurrency. Behind the scenes, a continuous sampler replays ~1% of ANN queries against brute-force ground truth and publishes recall; the credential-free
GET /recall-slo endpoint reports fleet recall health.
Keyword branch
BM25 (k1=1.2, b=0.75) over the text property. Tokenizer: lowercase, split on every non-alphanumeric character — no stemming, no stopwords (word-tokenizer parity with Turbopuffer).
match controls token combination:
"auto"(default) — require all tokens; if that yields fewer thanmax(5, top_k/8)hits, retry as any-token. The response reports which mode ran ("mode": "and"/"or")."all"— every token required."any"— ordinary disjunctive BM25.
Chronological branch
Ranks by any integer property,asc or desc — the cheap “most recent first” leg of a hybrid query.
Projecting properties
include_props controls what rides along with each hit: omit it for id+score only (zero extra cost), pass a name array to project just those keys, or true for the full property map. Projection means a reranker can consume hits without a second lookup roundtrip.
Turbopuffer-compatible shim
POST /v2/namespaces/{ns}/query accepts the Turbopuffer read shape — {rank_by, filters, top_k, include_attributes} — and returns {"rows": [{"id", "$dist", ...}]} with $dist as cosine distance. rank_by: ["id", "asc"] with filters: ["id", "Gt", cursor] gives cursor pagination/export. Unsupported shapes are explicit 400s naming the field — never a silently different query.