Search
This page covers every way to read and prune data in a VectorDB index: similarity queries, payload filters, score thresholds, paging through raw points, and deleting points. Each pattern comes with a runnable request and its expected output. If the words vector and embedding are new, start with the overview.
Every operation is shown three ways where all three surfaces have it — platformctl, curl, and the console. The curl tabs need the setup from the quickstart; the platformctl tabs need none of it, because the CLI resolves the address and your project itself.
export VDB="https://api.codyhill.dev" # VectorDB is served on the shared public API
export CAI_PROJECT="00000000-0000-0000-0000-000000000000" # your project UUID
export CAI_TOKEN="<your session token or API key>"
Every example assumes an index named docs with 4 dimensions containing:
{"points":[
{"id":1,"vector":[0.1,0.2,0.3,0.4],"payload":{"tag":"alpha"}},
{"id":2,"vector":[0.9,0.8,0.7,0.6],"payload":{"tag":"beta"}}]}
Search for nearest neighbors
A query takes a vector and returns the points closest to it, best first.
The technical name for this is ANN search, short for approximate nearest neighbor. It is "approximate" because the engine does not compare your query against every stored vector. It follows a shortcut structure built over the data instead. You give up a tiny bit of accuracy and get back a lot of speed.
- platformctl
- curl
- Console
platformctl vectordb query docs --vector '[0.1,0.2,0.3,0.4]' --top-k 5
You should see:
ID SCORE PAYLOAD
1 1.0000 {"tag":"alpha"}
2 0.8427 {"tag":"beta"}
--vector accepts a literal JSON array, @file to read a file, or - to read standard input, so any body written for the curl tab pipes straight in. Add -o json for the full response, including vectors when you asked for them.
curl -sX POST "$VDB/v1/projects/$CAI_PROJECT/indexes/docs:query" \
-H "Authorization: Bearer $CAI_TOKEN" -H 'Content-Type: application/json' \
-d '{"vector":[0.1,0.2,0.3,0.4],"top_k":5}'
You should see:
{"index":"docs","results":[{"id":1,"score":1.0,"payload":{"tag":"alpha"}},{"id":2,"score":...,"payload":{"tag":"beta"}}]}
Note the :query suffix. Searching is a custom method: a literal :verb on the end of the index path, always with POST.
Open your index at Data services → VectorDB → your index. Its detail page has a Query panel:
- Paste a vector, either as a JSON array or as numbers separated by commas or spaces.
- Set Top K.
- Or click Fill with a random vector to check that querying works at all — it writes as many random values as the index is wide.
Results come back as a ranked table of id, score, and payload. Click a row for the whole payload.
Request fields
| Field | Type | Default | Notes |
|---|---|---|---|
vector | array of numbers | required | Must match the index's dimensions exactly |
top_k | integer | 10 | How many nearest points to return; max 1000 |
filter | JSON object | none | Payload filter (below); max 64 KiB |
with_payload | boolean | true | Include each hit's payload |
with_vector | boolean | false | Include each hit's stored vector |
score_threshold | number | none | Drop hits scoring below this value |
offset | integer | 0 | Skip the first N hits (must not be negative) |
Scores
Every hit carries a score, computed with the index's distance metric. Results come back best first. With cosine, a vector identical to a stored one scores a perfect 1.0. That is why the quickstart's exact-match query returns "score":1.0.
Let the engine drop weak matches for you with score_threshold, rather than fetching them and discarding them in your own code:
- platformctl
- curl
platformctl vectordb query docs --vector '[0.1,0.2,0.3,0.4]' \
--top-k 10 --score-threshold 0.9
You should see only the strong match:
ID SCORE PAYLOAD
1 1.0000 {"tag":"alpha"}
curl -sX POST "$VDB/v1/projects/$CAI_PROJECT/indexes/docs:query" \
-H "Authorization: Bearer $CAI_TOKEN" -H 'Content-Type: application/json' \
-d '{"vector":[0.1,0.2,0.3,0.4],"top_k":10,"score_threshold":0.9}'
You should see only the strong match:
{"index":"docs","results":[{"id":1,"score":1.0,"payload":{"tag":"alpha"}}]}
There is no Console tab here: the query panel offers a vector, Top K, and a payload filter, and no score threshold. Filter in the browser, threshold from the CLI or the API.
Second stage: rerank the candidates
Vector search is fast and blunt, and its scores are often too close together to threshold on. Measured on a real corpus: the right passage scored 0.5468 against a wrong one at 0.4262. Running the same two candidates through the platform's cross-encoder reranker moved them to 0.9489 and 0.0619 — a gap you can act on.
The pattern is: retrieve wide, rerank, keep few.
# 1. retrieve about 4x what you need, at least 20 candidates
platformctl vectordb query docs --vector @q.json --top-k 20 -o json
# 2. score the candidates' text against the same query - one argument per candidate
platformctl inference rerank "how do I reset my password" \
"<candidate 1 text>" "<candidate 2 text>"
A reranker reads the query together with each document rather than comparing two vectors that were embedded separately, which is why it is sharper — and why it costs one model pass per candidate. That is affordable over 20 candidates and not over the whole index, which is exactly why it is a second stage.
Step 2 needs the candidates' text, and a query returns ids, scores, and payloads — never the source text unless you stored it. Put the text in the payload when you upsert, or the second stage has nothing to read.
Results come back with an index — the position of the document in the array you sent — and a relevance_score. Reorder by score and map each result back to your own record through index; do not use the position in results. The CLI already sorts for you and prints SCORE, INDEX, DOCUMENT.
The platform's ADK agent memory bank runs this two-stage retrieval internally. Over an index you built yourself, nothing reranks for you — call /v1/rerank from your own code. See Inference for the endpoint and its response shape.
Narrow results with a payload filter
A filter answers questions like "nearest neighbors, but only among documents tagged alpha". It narrows the search to points whose payload matches a condition.
A filter is a JSON object holding one or more clauses. Each clause holds a list of conditions, and a condition names a payload key plus the test to apply to it:
- platformctl
- curl
- Console
platformctl vectordb query docs --vector '[0.5,0.5,0.5,0.5]' --top-k 5 \
--filter '{"must":[{"key":"tag","match":{"value":"beta"}}]}'
You should see:
ID SCORE PAYLOAD
2 0.9891 {"tag":"beta"}
--filter takes the same three forms --vector does: a literal, @file, or - for standard input.
curl -sX POST "$VDB/v1/projects/$CAI_PROJECT/indexes/docs:query" \
-H "Authorization: Bearer $CAI_TOKEN" -H 'Content-Type: application/json' \
-d '{"vector":[0.5,0.5,0.5,0.5],"top_k":5,
"filter":{"must":[{"key":"tag","match":{"value":"beta"}}]}}'
You should see:
{"index":"docs","results":[{"id":2,"score":...,"payload":{"tag":"beta"}}]}
The Query panel has a Payload filter box below Top K. Paste the filter object into it and run the query. The filter is sent verbatim, so the syntax below is exactly what you type there.
Filter syntax
Clauses — combine them in one filter object, and a point must satisfy every clause you list:
| Clause | Meaning |
|---|---|
must | Every condition in the list has to hold (logical AND). |
should | At least one condition in the list has to hold (logical OR). |
must_not | None of the conditions may hold (logical NOT). |
Conditions — each entry in a clause list is an object with a key naming a payload field, plus one test:
| Test | Shape | Matches |
|---|---|---|
| Exact value | {"key":"tag","match":{"value":"beta"}} | Payload field equals that string, number, or boolean. |
| One of several | {"key":"tag","match":{"any":["beta","gamma"]}} | Field equals any value in the list. |
| None of several | {"key":"tag","match":{"except":["draft"]}} | Field equals none of the listed values. |
| Numeric range | {"key":"score","range":{"gte":0.5,"lt":1.0}} | Field falls in the range. Any of gt, gte, lt, lte, alone or combined. |
Nested payload fields are addressed with dots: {"key":"meta.author","match":{"value":"ada"}}. A clause list may also contain a nested filter object, so you can express "A and (B or C)".
A worked example — published documents tagged beta or gamma, scoring at least 0.5, excluding anything by ada:
{"must":[{"key":"published","match":{"value":true}},
{"key":"score","range":{"gte":0.5}},
{"should":[{"key":"tag","match":{"value":"beta"}},
{"key":"tag","match":{"value":"gamma"}}]}],
"must_not":[{"key":"meta.author","match":{"value":"ada"}}]}
Filter rules:
- The filter must be a JSON object (400
filter must be a JSON object). - It is capped at 64 KiB (400
filter is too large). - A payload key that no point carries is not an error — it simply matches nothing.
- Some filter mistakes are caught by the search layer rather than by the platform's own validation. Those come back as 400
vector database rejected the request: <message>, with the underlying explanation passed through word for word, so you see the real problem instead of a generic message.
Browse without a query: scroll
Scroll lists points in id order and needs no vector at all. It is the "just show me what's in here" operation, and it is what the console's Data browser uses.
- platformctl
- curl
- Console
platformctl vectordb scroll docs --limit 50
You should see:
ID PAYLOAD
1 {"tag":"alpha"}
2 {"tag":"beta"}
When another page remains, the CLI prints the cursor as the flag you hand back:
more points: --offset 2
curl -sX POST "$VDB/v1/projects/$CAI_PROJECT/indexes/docs:scroll" \
-H "Authorization: Bearer $CAI_TOKEN" -H 'Content-Type: application/json' \
-d '{"limit":50}'
You should see:
{"index":"docs","points":[{"id":1,"payload":{"tag":"alpha"}},{"id":2,"payload":{"tag":"beta"}}],"next_offset":...}
The Data tab on the index's page walks through scroll pages for you and shows each point's id and payload. Click a row for the whole payload; it is elided in the table because a payload can be far wider than the screen.
limitdefaults to 50, max 1000 (400limit must be at most 1000).- Scrolled points carry payloads but never vectors.
- Paging works by bookmark, not by page number. When more points remain, the response includes
next_offset, which is a point id. Send it back asoffseton your next request to pick up where you stopped. Whennext_offsetis absent, you have seen everything. A malformed offset gets 400offset must be an unsigned integer or a UUID string.
Delete points
Delete points by listing their ids, or by writing a filter that matches them. Send exactly one of the two, never both:
- platformctl
- curl
- Console
platformctl vectordb delete-points docs --ids 1
You should see:
ids_requested 1
index docs
status ok
Or by filter:
platformctl vectordb delete-points docs \
--filter '{"must":[{"key":"tag","match":{"value":"beta"}}]}'
--ids is repeatable and also accepts a comma-separated list. Passing both --ids and --filter, or neither, is refused before any request is made.
curl -sX POST "$VDB/v1/projects/$CAI_PROJECT/indexes/docs:delete-points" \
-H "Authorization: Bearer $CAI_TOKEN" -H 'Content-Type: application/json' \
-d '{"ids":[1]}'
You should see:
{"index":"docs","status":"ok","ids_requested":1}
Or by filter:
curl -sX POST "$VDB/v1/projects/$CAI_PROJECT/indexes/docs:delete-points" \
-H "Authorization: Bearer $CAI_TOKEN" -H 'Content-Type: application/json' \
-d '{"filter":{"must":[{"key":"tag","match":{"value":"beta"}}]}}'
You should see:
{"index":"docs","status":"ok"}
Both the Data tab and the Query results have a delete action on each row, which removes that one point by its id. There is no delete-by-filter in the browser — a filter that matches more than you meant is not something to discover from a single click — so use the CLI or the API for that.
Rules:
- Provide exactly one of
idsorfilter(400provide exactly one of ids or filter). - At most 1000 ids per request (400
at most 1000 ids per request). - An empty filter is deliberately refused, because it would match everything:
{"error":"an empty filter would delete every point; delete the index instead if that is the intent","request_id":"..."}
Common errors at a glance
| Symptom (verbatim error) | Cause | Fix |
|---|---|---|
index is not ready yet; its collection has not been created | Querying right after create | Poll GET .../indexes/{name} until ready: true |
query vector has N dimensions; index "docs" expects M | Query vector from the wrong embedding model | Embed queries with the same model you indexed with |
top_k must be at most 1000 | Asking for too many results | Page with offset, or rethink why you need 1000+ neighbors |
filter is too large | Filter over 64 KiB | Simplify the filter; move logic into payload design |
offset must not be negative | Negative offset in a query | Use 0 or a positive integer |
Next steps
- Indexes and points — the data model behind these queries.
- Use with agents — semantic search as agent memory.
- API reference — the full endpoint reference.