gRPC API
The server also speaks gRPC, on port 6901 by default: the quiver.v1.Quiver service, defined in crates/grpc/proto/quiver.proto. It mirrors the REST API. Metadata and filters travel as JSON strings, so any nested document round-trips exactly. The proto is compiled with protox at build time, so building Quiver needs no protoc.
Calling it
The server does not offer gRPC reflection, so point your tool at the proto file. With grpcurl, from the repository root:
grpcurl -plaintext -import-path crates/grpc/proto -proto quiver.proto \
-d '{"name": "places", "dimension": 2, "metric": "METRIC_EUCLIDEAN"}' \
localhost:6901 quiver.v1.Quiver/CreateCollection{
"name": "places",
"dimension": 2,
"metric": "METRIC_EUCLIDEAN",
"index": {
"hnsw": {
"m": 16,
"efConstruction": 128,
"efSearch": 64,
"seed": "6512614579346866177"
}
}
}grpcurl -plaintext -import-path crates/grpc/proto -proto quiver.proto \
-d '{"collection": "places", "points": [{"id": "brussels", "vector": [4.35, 50.85], "metadata_json": "{\"country\": \"BE\"}"}]}' \
localhost:6901 quiver.v1.Quiver/Upsert
grpcurl -plaintext -import-path crates/grpc/proto -proto quiver.proto \
-d '{"collection": "places", "vector": [5.5, 50.5], "k": 1, "filter_json": "{\"country\": \"BE\"}"}' \
localhost:6901 quiver.v1.Quiver/Search{
"upserted": "1",
"ids": [
"brussels"
]
}
{
"hits": [
{
"id": "brussels",
"distance": 1.2020812,
"metadataJson": "{\"country\":\"BE\"}"
}
]
}With TLS enabled on the server, drop -plaintext and pass -cacert ca.pem (and -cert/-key for mutual TLS); see security.
grpcurl prints proto3 JSON: 64-bit integers as strings, field names in camelCase, and fields at their default value (such as a count of 0) left out. To generate a client, compile the same proto with your language's gRPC tooling.
Authentication
Send the key as authorization: Bearer <key> or x-api-key: <key> metadata:
grpcurl -plaintext -import-path crates/grpc/proto -proto quiver.proto \
-H "authorization: Bearer $QUIVER_API_KEY" localhost:6901 quiver.v1.Quiver/ListCollectionsWith keys configured, a call with no key may only use Health; any other RPC answers UNAUTHENTICATED. A wrong key is rejected with UNAUTHENTICATED on every RPC, Health included. A read-only key calling a write RPC gets PERMISSION_DENIED. With no keys configured, everything is open.
RPCs
| RPC | Role | Notes |
|---|---|---|
Health | public | {status, version} |
Info | read | {version, storage, snapshots_enabled, collections, points} |
ListCollections, GetCollection | read | |
CreateCollection | write | metric unset means cosine; index unset means HNSW with defaults; optional quantization (QUANTIZATION_SCALAR or QUANTIZATION_BINARY), on_disk, expiry ({field, default_ttl}), field_indexes, and the maps vectors (name → VectorParams) and sparse_vectors (name → SparseVectorParams) |
UpdateCollection | write | name, update_json: the body of PATCH /collections/{name} as JSON; returns the collection's info |
RenameCollection | write | name (not an alias), new_name; returns the collection's info |
ListAliases | read | aliases: {alias, collection} pairs |
SetAlias | write | alias, collection; creates or atomically re-points |
DeleteAlias | write | alias; NOT_FOUND if there is none |
DropCollection, Flush | write | DropCollection needs the collection's own name, not an alias |
Compact | write | returns the collection's info |
CreateFieldIndex, DropFieldIndex | write | collection, field; return the collection's info. Dropping a field with no index is NOT_FOUND |
Upsert | write | an empty id gets a generated UUID; points carry vectors (name → DenseVector) and sparse_vectors (name → SparseVector); returns upserted and ids |
GetPoints | read | ids, with_vectors; missing ids are skipped |
DeletePoints | write | ids or filter_json; {} is refused |
Scroll | read | cursor, limit (0 means 100), filter_json, order_by (field, direction DIRECTION_ASC or DIRECTION_DESC), with_vectors; next_cursor unset at the end |
Count | read | filter_json, empty counts all |
Facet | read | field, filter_json, limit (0 means 10, at most 1,000); returns values, each value_json and count |
UpdateMetadata | write | ids or filter_json, metadata_json (required; null allowed), mode |
Search | read | exactly one of vector, id and sparse; using (empty: the main vector; required with sparse), k (0 means 10), offset, filter_json, ef, max_distance, with_vectors, with_metadata (unset means true) |
SearchBatch | read | up to 256 SearchRequests, each naming its collection |
SearchGroups | read | the target fields of Search, group_by, groups (0 means 10), group_size (0 means 3), and the search options; returns groups, each key_json and hits |
Recommend | read | positive, negative, using, offset, and the search options |
Discover | read | target and up to 64 context pairs (positive, negative), each an Example (id or vector); using, k, offset, and the search options |
Query | read | up to 16 Prefetches (each vector or sparse, using, limit (0 means max(k + offset, 50)), weight (unset means 1), ef, max_distance), k, offset, filter_json, fusion (FUSION_RRF, FUSION_RELATIVE_SCORE or FUSION_DBSF), rrf_k (unset means 60), rescore (vector, using), with_vectors, with_metadata; returns ScoredHits, highest score first |
CreateSnapshot, DeleteSnapshot, RestoreSnapshot | write | RestoreSnapshot takes id, collection (empty: the original) and replace |
ListSnapshots | read | collection, empty for all |
GetSnapshot | read | id |
DownloadSnapshot | read | id; streams the archive as SnapshotChunks of up to 1 MiB |
UploadSnapshot | write | a client stream of SnapshotChunks, in order, up to 16 GiB in all; INVALID_ARGUMENT if the archive is not a valid snapshot |
VectorParams is {dimension, metric, index, quantization, on_disk}, SparseVectorParams is {modifier} (SPARSE_MODIFIER_NONE or SPARSE_MODIFIER_IDF), DenseVector is {values} and SparseVector is {indices, values}. CollectionInfo and SnapshotInfo carry on_disk, vectors, sparse_vectors and expiry; SearchHits and Points carry vectors and sparse_vectors when vectors are asked for.
Every RPC is counted in the REST API's /metrics.
Status codes
| Status | When |
|---|---|
INVALID_ARGUMENT | invalid input, a bad filter, a dimension mismatch, JSON that does not parse, an unknown metric or metadata mode |
NOT_FOUND | no such collection, alias, point or snapshot |
ALREADY_EXISTS | the name is taken by a collection or an alias |
UNAUTHENTICATED | a missing or unknown key |
PERMISSION_DENIED | a read-only key on a write RPC |
UNIMPLEMENTED | snapshots without a snapshot directory |
INTERNAL | an I/O or storage failure |
Messages may be up to 64 MiB in either direction, so large upserts fit in one call. The REST limits on k, batch size, ids and scroll limit apply here too.