HTTP API reference¶
All endpoints live under /v1. Requests and responses are JSON.
- Auth:
Authorization: Bearer <token>(Security)./healthis auth-exempt. - Names: collection and alias names are capped at 247 bytes.
- Metadata values use the tagged encoding described in Filtering → Payload values; filters use lowercase operator names (Filtering → Operators).
- Read options on search/get endpoints:
read_consistency(0 any-replica · 1 leader · 2 linearizable · 3 bounded-staleness),max_staleness(Raft-entry bound for level 3),on_partition_unavailable(0 partial · 1 fail). - Write options on point writes:
write_consistency_factor(replica acks),wait,expected_version(CAS).
Errors¶
Errors return {"error": "..."} with a conventional status code (400 invalid
request, 401/403 auth, 404 not found, 412 feature not configured, 5xx server).
During a Raft leader election, writes can return a retryable 503.
Health & metrics¶
| Method & path | Description |
|---|---|
GET /v1/health |
liveness — the process is serving (auth-exempt) |
GET /v1/ready |
readiness — every hosted shard can serve (auth-exempt) |
GET /metrics, GET /v1/metrics |
Prometheus text (scope-gated) |
Collections (dense)¶
| Method & path | Body | Description |
|---|---|---|
POST /v1/collections |
{"name", "config"} |
create |
DELETE /v1/collections/{name} |
— | drop collection + data |
config mirrors vector.Config:
{
"dim": 768,
"metric": "cosine", // "cosine" (default) | "l2" | "dot"
"m": 16, "ef_construction": 200, "ef_search": 64, "seed": 0,
"quant": "sq8", // "" | "sq8" | "bq1" | "pq" | "sq" | "prq"
"rescore_factor": 3, "sq_bits": 0, "prq_layers": 0, "quant_pq_m": 0, "pq_nbits": 0,
"persistent": false,
"partitions": 1,
"index_type": "hnsw", // "" | "hnsw" | "ivf" | "vamana"
"ivf_nlist": 0, "ivf_nprobe": 0, "ivf_pq": false, "ivf_pq_m": 0, "ivf_rerank": false,
"vamana_r": 0, "vamana_l": 0, "vamana_alpha": 0,
"filter_first_relative_bp": 0,
"full_text": {"analyzer": "english", "k1": 1.2, "b": 0.75},
"extend_candidates": false, "level0_full_degree": false, "quantized_build": false,
"opq": false, "soar": false, "anisotropic_eta": 0
}
Only dim is required; zero values take engine defaults.
Points (dense)¶
| Method & path | Description |
|---|---|
POST /v1/collections/{n}/points |
insert (or upsert with "upsert": true) |
POST /v1/collections/{n}/points/batch |
bulk insert/upsert {"upsert", "points": [...]}; per-point {"id","version","status"} results |
POST /v1/collections/{n}/points/bulk |
stage vectors (and optional metadata) for a concurrent build |
POST /v1/collections/{n}/points/bulk/build |
build the staged index {"workers": 0} (0 = all cores) |
GET /v1/collections/{n}/points/{id} |
fetch; query params with_vector, with_payload, read options |
POST /v1/collections/{n}/points/batch-get |
{"ids", "with_vector", "with_payload"} → {"points", "missing"} |
DELETE /v1/collections/{n}/points/{id} |
delete; query params write_consistency_factor, wait, expected_version |
POST /v1/collections/{n}/points/delete |
delete by filter {"filter"} → count |
POST /v1/collections/{n}/points/scroll |
{"filter", "limit", "cursor"} → page + next_cursor |
Point write body:
{
"id": 1,
"vector": [0.1, 0.2],
"content": "raw document text", // stored under $content
"metadata": {"tenant": {"kind":"string","str":"acme"}},
"sparse": {"indices":[3,17], "values":[0.4,0.9]},
"ttl_ms": 0,
"key_ttl_ms": {"session": 60000}, // per-payload-key TTL
"upsert": true,
"expected_version": 7, // CAS (optional)
"write_consistency_factor": 2, "wait": true
}
Binary bulk ingest¶
/points/bulk and /points/batch also accept a dense binary body, selected by
Content-Type: application/octet-stream. It carries the same request as the JSON
body — the server decodes it into the identical call — but ships the vectors as
raw f32 instead of base-10 text, which is what dominates a large initial load.
Any other content type takes the JSON path unchanged.
magic "RVB1" 4 bytes
flags u32 bit0 payloads present, bit1 upsert
count u32 number of points
dim u32 vector dimension (uniform across the body)
rows count × [ id u64 ][ dim × f32 ]
pays count × [ len u32 ][ len bytes of JSON ] // only when bit0
Everything is big-endian, matching the internal op wire, so a row needs no
conversion server-side. A payload is a JSON object in the tagged metadata form
({"tenant":{"kind":"string","str":"acme"}}); len = 0 means the point has none.
Both routes carry payloads: /points/batch indexes each point inline, while
/points/bulk stages them for the multi-core build. upsert applies to
/points/batch only — /points/bulk rejects a body that sets it rather than
silently ignoring it, as does the JSON staging body. Responses are the same as
the JSON path ({"staged":n} / {"count":n}).
Limits. One request carries at most 256 MiB and 262,144 points, and
dim must equal the collection's configured dimension. Split a larger load
across requests — the reference Python client does this for you.
count and dim are validated against those limits before any row is read, and
every section is read in bounded windows that grow only as bytes arrive: a body
that over-declares its size is refused having consumed only what it actually
sent. A dimension that does not match the collection is rejected by the shard
that owns the collection (400), which also covers the JSON staging body — a
staged vector whose length is not the collection's dim is refused at stage
time, not at build time. Either way nothing is ingested.
Payload mutations¶
All accept write_consistency_factor, wait, expected_version query params:
| Method & path | Description |
|---|---|
POST .../points/{id}/payload |
merge patch (values + optional key_ttl_ms) |
POST .../points/{id}/payload/overwrite (also PUT .../payload) |
replace entire payload |
POST .../points/{id}/payload/delete |
remove keys {"keys": [...]} |
POST .../points/{id}/payload/clear |
empty the payload |
Search (dense)¶
All search bodies accept filter and the read options.
| Method & path | Body highlights | Returns |
|---|---|---|
POST .../points/search |
{"query", "k"} |
{"results":[{"id","score",...}], "degraded", "missing"} |
POST .../points/search/docs |
same | hits + content + payload |
POST .../points/search/groups |
{"query","k","group_by","group_size","fetch_k"} |
top-k per group |
POST .../points/search/hybrid |
{"dense","sparse","k","method","alpha","rrf_k","dense_k","sparse_k"} |
fused dense+sparse |
POST .../points/search/text |
{"text","k"} |
BM25 full-text (requires full_text) |
POST .../points/search/hybrid-text |
{"dense","text","k","method",...} |
fused dense+BM25 |
POST /v1/collections/{n}/query |
{"root","prefetch":[...],"mode":"fusion"\|"rerank","method","alpha","rrf_k","k"} |
unified multi-lane Query API |
method is "rrf" (default), "weighted" (with alpha), or "dbsf".
Binary query body¶
/points/search and /points/search/docs also accept a dense binary body,
selected by Content-Type: application/octet-stream — the same selection rule as
binary bulk ingest, and the same guarantee: the server
decodes it into the identical request the JSON body would have produced, then
runs the identical validation and dispatch. Any other content type takes the
JSON path unchanged.
magic "RVQ1" 4 bytes
flags u32 bit0 filter present
k u32
dim u32 length of the query vector
rc u8 read_consistency
opa u8 on_partition_unavailable
_ u16 reserved, must be zero
staleness u64 max_staleness (the bound for rc=3)
query dim × f32
filter [ len u32 ][ len bytes of filter JSON ] // only when bit0
Big-endian throughout, matching RVB1 and the op wire.
It exists because encoding the query vector dominates a small request: at dim=768, k=10 on a kept-alive connection, building the JSON body was 0.258 ms of a 0.845 ms search — 31% — against 0.011 ms to write the same vector as bytes, and the server's matching decode of those literals disappears with it.
Limits. dim at most 65,536, the filter blob at most 1 MiB, the body
at most 8 MiB. NaN and ±Inf are rejected: JSON cannot express them, and
this body is meant to say exactly what its JSON twin can. Trailing bytes after
the frame are rejected rather than ignored.
A client that cannot assume a new enough server can simply try it: a server
without this support hands the body to its JSON decoder and answers
400 invalid JSON body: ..., which is unambiguous enough to fall back on. The
reference Python client does this automatically.
Partitioning (dense)¶
| Method & path | Description |
|---|---|
POST /v1/collections/{n}/resplit |
offline repartition {"new_partitions"} — quiesce writes first |
POST /v1/collections/{n}/resplit/cleanup |
drop orphaned partitions |
POST /v1/collections/{n}/reshard |
online repartition (dual-write, atomic cutover) |
POST /v1/collections/{n}/reshard/abort |
abort an in-flight reshard (pre-cutover) |
Multi-vector collections¶
Prefix /v1/multivector/{name} — late-interaction MaxSim collections
(Hybrid → Late-interaction):
| Method & path | Description |
|---|---|
POST /v1/multivector/{n} / DELETE /v1/multivector/{n} |
create / drop |
POST .../docs |
add doc {"id","tokens":[[...],...],"metadata"} (insert-or-replace) |
DELETE .../docs/{id} |
delete doc |
GET .../points/{id} · POST .../points/batch-get |
fetch tokens + payload |
POST .../points/{id}/payload[/overwrite|/delete|/clear] |
payload mutations |
POST .../search |
MaxSim top-k |
POST .../hybrid-search |
MaxSim + sparse fusion |
POST .../query |
multi-lane Query API |
POST .../scroll |
paginated listing |
POST .../resplit[...] · POST .../reshard[...] |
partitioning (as dense) |
Named-vector collections¶
Prefix /v1/named/{name} — multi-space collections
(Hybrid → Named-vector):
| Method & path | Description |
|---|---|
POST /v1/named/{n} / DELETE /v1/named/{n} |
create {"named_vectors": {...}} / drop |
GET .../config |
configured spaces |
POST .../points |
upsert {"id","vectors":{space:[...]},"metadata","ttl_ms"} |
GET .../points/{id} · DELETE .../points/{id} · POST .../points/delete · POST .../points/batch-get |
point ops |
POST .../points/{id}/payload[...] |
payload mutations |
POST .../search |
kNN in one space {"vector_name","query","k","filter"} |
POST .../sparse-search |
sparse top-k in a sparse space |
POST .../hybrid-search |
cross-space dense+sparse fusion |
POST .../query |
multi-space Query API |
POST .../search/docs · POST .../scroll |
docs variant, listing |
Aliases¶
| Method & path | Description |
|---|---|
POST /v1/aliases |
{"alias","collection"} create/repoint |
DELETE /v1/aliases/{alias} |
delete (idempotent) |
GET /v1/aliases[?collection=x] |
list |
POST /v1/aliases/batch |
atomic {"actions":[{"create":{...}},{"delete":{...}}]} |
Admin¶
| Method & path | Description |
|---|---|
POST /v1/admin/keys |
add API key {"token","tenant","scopes","cert_cn"} (admin scope) |
DELETE /v1/admin/keys |
revoke {"token"} — token in body, never in the path |
GET /v1/admin/keys |
list keys (redacted fingerprints) |
POST /v1/admin/backup |
trigger a backup now (412 if no object store configured) |
GET /v1/admin/backups |
list backups |
POST /v1/collections/{n}/evict |
evict to cold tier |
POST /v1/collections/{n}/restore |
restore from object store |