quiver
Documentation menu

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/ListCollections

With 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

RPCRoleNotes
Healthpublic{status, version}
Inforead{version, storage, snapshots_enabled, collections, points}
ListCollections, GetCollectionread
CreateCollectionwritemetric 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)
UpdateCollectionwritename, update_json: the body of PATCH /collections/{name} as JSON; returns the collection's info
RenameCollectionwritename (not an alias), new_name; returns the collection's info
ListAliasesreadaliases: {alias, collection} pairs
SetAliaswritealias, collection; creates or atomically re-points
DeleteAliaswritealias; NOT_FOUND if there is none
DropCollection, FlushwriteDropCollection needs the collection's own name, not an alias
Compactwritereturns the collection's info
CreateFieldIndex, DropFieldIndexwritecollection, field; return the collection's info. Dropping a field with no index is NOT_FOUND
Upsertwritean empty id gets a generated UUID; points carry vectors (name → DenseVector) and sparse_vectors (name → SparseVector); returns upserted and ids
GetPointsreadids, with_vectors; missing ids are skipped
DeletePointswriteids or filter_json; {} is refused
Scrollreadcursor, limit (0 means 100), filter_json, order_by (field, direction DIRECTION_ASC or DIRECTION_DESC), with_vectors; next_cursor unset at the end
Countreadfilter_json, empty counts all
Facetreadfield, filter_json, limit (0 means 10, at most 1,000); returns values, each value_json and count
UpdateMetadatawriteids or filter_json, metadata_json (required; null allowed), mode
Searchreadexactly 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)
SearchBatchreadup to 256 SearchRequests, each naming its collection
SearchGroupsreadthe 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
Recommendreadpositive, negative, using, offset, and the search options
Discoverreadtarget and up to 64 context pairs (positive, negative), each an Example (id or vector); using, k, offset, and the search options
Queryreadup 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, RestoreSnapshotwriteRestoreSnapshot takes id, collection (empty: the original) and replace
ListSnapshotsreadcollection, empty for all
GetSnapshotreadid
DownloadSnapshotreadid; streams the archive as SnapshotChunks of up to 1 MiB
UploadSnapshotwritea 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

StatusWhen
INVALID_ARGUMENTinvalid input, a bad filter, a dimension mismatch, JSON that does not parse, an unknown metric or metadata mode
NOT_FOUNDno such collection, alias, point or snapshot
ALREADY_EXISTSthe name is taken by a collection or an alias
UNAUTHENTICATEDa missing or unknown key
PERMISSION_DENIEDa read-only key on a write RPC
UNIMPLEMENTEDsnapshots without a snapshot directory
INTERNALan 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.