How-To: Store and query vectors
Alpha
The vector API is currently in alpha.Let’s get started using the vector API. In this guide, you’ll learn how to:
- Set up a Meilisearch vector component.
- Create a collection.
- Upsert records with vectors, metadata, and payloads.
- Run similarity queries with metadata filters and score thresholds.
- Run several queries in one batch.
- Retrieve and delete records by ID.
The examples in this guide call the Dapr HTTP API with curl and use 4-dimensional vectors to keep them readable. In a real application, vectors come from an embedding model and typically have hundreds or thousands of dimensions. The HTTP API uses the JSON mapping of the protobuf messages: field names are camelCase, 64-bit integers are strings, and bytes are base64-encoded. Responses include fields set to their zero value and omit optional fields that aren’t set. For .NET examples using typed payloads, metadata filters, and batch queries, see the Vector .NET SDK guide, targeting SDK 1.19.0 or later.
Pre-requisites
Start Meilisearch
Run a local Meilisearch instance with Docker:
docker run -d --name meilisearch -p 7700:7700 \
-e MEILI_MASTER_KEY=masterKey \
getmeili/meilisearch:v1.43
Set up the vector component
Create a file called vector.yaml in your components directory (for example, ./components):
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: myvectors
spec:
type: vector.meilisearch
version: v1
metadata:
- name: host
value: "http://localhost:7700"
- name: apiKey
value: "masterKey"
Warning
The above example uses secrets as plain strings. Using a secret store is recommended to store the secrets, as described here.See the vector component specs for all supported vector components.
Run the Dapr sidecar
Start a Dapr sidecar that loads the component:
dapr run --app-id vector-app --dapr-http-port 3500 --resources-path ./components
Create a collection
Create a collection called cars for 4-dimensional vectors compared with cosine similarity:
curl -X POST http://localhost:3500/v1.0-alpha1/vector/myvectors/collections/cars \
-H "Content-Type: application/json" \
-d '{
"dimensions": 4,
"metric": "DISTANCE_METRIC_COSINE"
}'
Confirm the collection was created:
curl http://localhost:3500/v1.0-alpha1/vector/myvectors/collections/cars
{
"collection": "cars",
"recordCount": "0",
"properties": {
"filterableAttributes": "daprMetadata",
"primaryKey": "id"
},
"dimensions": 4,
"metric": "DISTANCE_METRIC_COSINE"
}
Upsert records
Each record has an id, the vector values, structured metadata that you can filter on, and an optional opaque payload. Because payload is defined as bytes, the HTTP API expects it base64-encoded. In this example, the payload of car-1 is the base64 encoding of {"name":"Model 3"}.
The following request upserts three records and waits up to 10 seconds for them to be written:
curl -X POST http://localhost:3500/v1.0-alpha1/vector/myvectors/collections/cars/vectors \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"id": "car-1",
"values": [1, 0, 0, 0],
"metadata": { "make": "tesla", "year": 2023, "dealer": { "city": "Seattle" } },
"payload": "eyJuYW1lIjoiTW9kZWwgMyJ9"
},
{
"id": "car-2",
"values": [0.9, 0.1, 0, 0],
"metadata": { "make": "tesla", "year": 2025, "dealer": { "city": "Portland" } },
"payload": "eyJuYW1lIjoiTW9kZWwgWSJ9"
},
{
"id": "car-3",
"values": [0, 1, 0, 0],
"metadata": { "make": "ford", "year": 2024, "dealer": { "city": "Seattle" } }
}
],
"options": {
"mode": "INDEXING_MODE_WAIT_FOR_COMPLETION",
"waitTimeout": "10s",
"onWaitTimeout": "INDEXING_WAIT_TIMEOUT_ACTION_FAIL_REQUEST"
}
}'
{
"failedItems": [],
"ack": "INDEX_ACK_COMPLETED"
}
Records that can’t be written are listed in failedItems with their error, while the rest of the batch is still written. For example, with Meilisearch a record whose values don’t have the collection’s dimensions is reported with INVALID_ARGUMENT.
Note
If you omitoptions, Dapr returns as soon as Meilisearch queues the write (INDEX_ACK_QUEUED). The records become queryable shortly afterwards, once Meilisearch has processed the task.Query the collection
Find the two records closest to a query vector, only for Teslas sold in Seattle or Portland, and return their payloads. The query is set in the required query field of the request:
curl -X POST http://localhost:3500/v1.0-alpha1/vector/myvectors/collections/cars/query \
-H "Content-Type: application/json" \
-d '{
"query": {
"vector": { "values": [1, 0, 0, 0] },
"topK": 2,
"filter": {
"make": "tesla",
"dealer.city": { "$in": ["Seattle", "Portland"] }
},
"includePayload": true
}
}'
{
"matches": [
{
"record": {
"id": "car-1",
"values": [],
"payload": "eyJuYW1lIjoiTW9kZWwgMyJ9",
"metadata": { "dealer": { "city": "Seattle" }, "make": "tesla", "year": 2023 }
},
"score": 1
},
{
"record": {
"id": "car-2",
"values": [],
"payload": "eyJuYW1lIjoiTW9kZWwgWSJ9",
"metadata": { "dealer": { "city": "Portland" }, "make": "tesla", "year": 2025 }
},
"score": 0.9938837
}
],
"metric": "DISTANCE_METRIC_COSINE"
}
Scores are the cosine similarity between the query vector and each record, where 1 is identical. values is empty because the query didn’t set includeValues.
Filter by score
Use scoreThreshold to keep only matches that are close enough. For cosine similarity, matches with a score greater than or equal to the threshold are kept:
curl -X POST http://localhost:3500/v1.0-alpha1/vector/myvectors/collections/cars/query \
-H "Content-Type: application/json" \
-d '{
"query": {
"vector": { "values": [1, 0, 0, 0] },
"topK": 10,
"scoreThreshold": 0.5
}
}'
Query by record ID
To find records similar to one you’ve already stored, use byId instead of a query vector:
curl -X POST http://localhost:3500/v1.0-alpha1/vector/myvectors/collections/cars/query \
-H "Content-Type: application/json" \
-d '{
"query": {
"byId": "car-3",
"topK": 3
}
}'
Run a batch of queries
Send several queries to the same collection in one call. Each entry of queries has the same shape as the query of a single query request, and runs against the collection in the URL. Each query succeeds or fails independently, and results are returned in request order. In this example, the second query is invalid because it sets neither a vector nor a record ID, so its slot contains an error while the first query still returns matches (error details are omitted from the response below for brevity):
curl -X POST http://localhost:3500/v1.0-alpha1/vector/myvectors/collections/cars/batch-query \
-H "Content-Type: application/json" \
-d '{
"queries": [
{ "vector": { "values": [0, 1, 0, 0] }, "topK": 1 },
{ "topK": 1 }
]
}'
{
"results": [
{
"response": {
"matches": [
{ "record": { "id": "car-3", "values": [], "metadata": { "dealer": { "city": "Seattle" }, "make": "ford", "year": 2024 } }, "score": 1 }
],
"metric": "DISTANCE_METRIC_COSINE"
}
},
{
"error": {
"code": 3,
"message": "exactly one of vector or by_id must be set"
}
}
]
}
Get records by ID
Fetch records by ID, including their vector values. Found records are returned in request order; IDs that don’t exist are omitted:
curl -X POST http://localhost:3500/v1.0-alpha1/vector/myvectors/collections/cars/vectors/get \
-H "Content-Type: application/json" \
-d '{
"ids": [ "car-1", "car-42" ],
"includeValues": true
}'
Delete records
Deleting records is a write and takes the same options as upserting. IDs that don’t exist are not an error:
curl -X POST http://localhost:3500/v1.0-alpha1/vector/myvectors/collections/cars/vectors/delete \
-H "Content-Type: application/json" \
-d '{
"ids": [ "car-2" ]
}'
Delete the collection
curl -X DELETE http://localhost:3500/v1.0-alpha1/vector/myvectors/collections/cars
Requests without a body (get, list, and delete collection) take request metadata as metadata.<key>=<value> query parameters.