Skip to content

HTTP API

This is the endpoint reference for a running nidus serve. Every route maps one-to-one onto a library method; bodies and responses are JSON. To run the server, set a bind address, and configure auth, see the HTTP server guide.

Base URL is wherever the server is bound (the examples use localhost:7700). Auth: when the server is started with a token, every request except GET /health must send Authorization: Bearer <token> — see Authentication. Errors return {"error": "<message>"} with a status code; see Errors.

Method & pathOperationLibrary method
GET /healthliveness check (always unauthenticated)
GET /statsdimension, distance, ann config, collections, footprintdimension / footprint
GET /collectionslist collection namescollections
POST /collections/{name}create a collectioncreate_collection
DELETE /collections/{name}drop a collection and its recordsdrop_collection
GET /collections/{name}/metaread collection metadataget_meta
PUT /collections/{name}/metareplace collection metadataset_meta
POST /collections/{name}/upsertinsert or overwrite recordsupsert
POST /collections/{name}/deletedelete by ids or by filterdelete / delete_where
GET /collections/{name}/recordsevery record in a collectionget_all
POST /collections/{name}/fts-schemadeclare full-text-indexed fieldsset_fts_schema
POST /searchnearest-neighbour searchsearch
POST /text-searchBM25 full-text searchtext_search
POST /hybrid-searchfused vector + BM25 (RRF)hybrid_search
POST /listmetadata-only query (no vector)list
POST /flushflush buffered writes to diskflush
POST /compactreclaim dead rows and superseded log recordscompact

Liveness probe. Returns 200 with the body ok. Always reachable without a token, so a load balancer or docker healthcheck needs no credential.

Store-wide introspection — the network equivalent of nidus stats.

{
"dimension": 768,
"distance": "Cosine",
"ann": null,
"collections": ["docs", "notes"],
"footprint": {
"rows": 1240,
"dead_rows": 12,
"dimension": 768,
"vector_bytes": 3809280,
"doc_count": 1228
}
}

rows counts every vector slot on disk (including superseded ones); dead_rows is how many a compact would reclaim; doc_count is the live record count. ann is null for exact brute-force search (the default), or echoes the active ANN configuration when the server was started with --ann hnsw/--ann ivf (only the knobs that apply to the chosen index are reported):

"ann": { "kind": "Hnsw", "overscan": 4, "seed": 11400714819323198485,
"m": 16, "ef_construction": 200, "ef_search": 64 }

Returns the collection names as a JSON array: ["docs", "notes"].

Create a collection. The body is ignored. Upsert auto-creates a collection, so an explicit create is only needed to register an empty one (e.g. to attach metadata before any records land).

Terminal window
curl -s -X POST localhost:7700/collections/docs # → {"created": "docs"}

Drop a collection and its records. The body is ignored.

Terminal window
curl -s -X DELETE localhost:7700/collections/docs # → {"dropped": "docs"}

Read a collection’s free-form string→string metadata map.

Terminal window
curl -s localhost:7700/collections/docs/meta
# → {"model": "text-embedding-3-small", "owner": "search-team"}

Replace a collection’s metadata map wholesale.

Terminal window
curl -s -X PUT localhost:7700/collections/docs/meta \
-H 'content-type: application/json' \
-d '{"model": "text-embedding-3-small", "owner": "search-team"}'
# → {"ok": true}

Declare which attribute fields of a collection are full-text indexed for BM25 (US English analyzer). Run it once before (or after) upserting; see Full-text search for the ranking model.

Terminal window
curl -s -X POST localhost:7700/collections/docs/fts-schema \
-H 'content-type: application/json' \
-d '{"fields": ["body"]}'
# → {"ok": true}

Insert or overwrite records by id. Each record is {id, vector, attrs}; vector length must match the store dimension. attrs values are tagged ({"Str": …}, {"Int": …}, {"Bool": …}, {"List": […]}, {"Null": null}).

Terminal window
curl -s localhost:7700/collections/docs/upsert \
-H 'content-type: application/json' \
-d '{"records": [
{
"id": "a",
"vector": [1, 0, 0],
"attrs": {"lang": {"Str": "rust"}, "ts": {"Int": 1781000000}}
}
]}'
# → {"upserted": 1}

The whole batch is all-or-nothing: a dimension mismatch or other fault rolls the store back, and the call returns 400 having changed nothing.

Delete by explicit ids, or by an attribute filter — supply ids or filter; filter wins if both are present.

Terminal window
# By id
curl -s localhost:7700/collections/docs/delete \
-H 'content-type: application/json' -d '{"ids": ["a", "b"]}'
# By filter (delete everything archived)
curl -s localhost:7700/collections/docs/delete \
-H 'content-type: application/json' \
-d '{"filter": [{"Eq": ["status", {"Str": "archived"}]}]}'
# → {"deleted": 7}

Every live record in the collection (id, vector, attrs) as a JSON array. Useful for export or for re-embedding against a new model. There is no pagination here — use POST /list when you want filtering or paging.

Nearest-neighbour search. query is the only required field. An empty or omitted scope searches every collection in one merged ranking (sound because all collections share one embedding space).

Terminal window
curl -s localhost:7700/search \
-H 'content-type: application/json' \
-d '{
"query": [1, 0, 0],
"scope": ["docs"],
"top_k": 5,
"min_score": 0.2,
"filter": [{"Eq": ["lang", {"Str": "rust"}]}]
}'
FieldDefaultMeaning
query— (required)query vector; length must equal the store dimension
scopeall collectionscollection names to search
top_k10maximum hits to return
min_scorenonedrop hits scoring below this similarity
filternoneAND of predicates applied before scoring

Returns hits sorted by descending score:

[{"collection": "docs", "id": "a", "score": 1.0, "attrs": {"lang": {"Str": "rust"}}}]

BM25 full-text search of a declared field. Returns the same hit shape as /search. Takes field, query, scope, top_k, filter, and min_score — here a raw BM25 floor (not cosine).

Terminal window
curl -s localhost:7700/text-search \
-H 'content-type: application/json' \
-d '{"field": "body", "query": "running quickly", "scope": ["docs"], "top_k": 5}'

Fuse a vector query and a BM25 text query with Reciprocal Rank Fusion. Takes vector

  • field + text, plus top_k, filter, rrf_k (default 60), and candidates (default 100). There is no min_score (a fused RRF score has no absolute scale). Returns the same hit shape as /search.
Terminal window
curl -s localhost:7700/hybrid-search \
-H 'content-type: application/json' \
-d '{"vector": [1,0,0], "field": "body", "text": "vector database", "top_k": 5}'

Metadata-only query — no vector, no scoring. Same scope and filter as search, plus offset/limit for pagination.

Terminal window
curl -s localhost:7700/list \
-H 'content-type: application/json' \
-d '{
"scope": ["docs"],
"filter": [{"Eq": ["lang", {"Str": "rust"}]}],
"offset": 0,
"limit": 100
}'

limit defaults to 100, offset to 0. The response shape matches search (hits with a score of 0, since nothing is scored).

The filter in both /search and /list is an AND of predicates: Eq, Ne, Glob, In, NotIn, Lt, Le, Gt, Ge. See Search & filters for the full predicate grammar.

Force buffered writes to disk (relevant under Fsync::OnFlush). Returns {"ok": true}.

Terminal window
curl -s -X POST localhost:7700/flush # → {"ok": true}

Rewrite the store to reclaim dead_rows and superseded log records. Returns {"ok": true}.

Terminal window
curl -s -X POST localhost:7700/compact # → {"ok": true}

Every error returns {"error": "<message>"}. The status code separates a client mistake from a server fault:

StatusWhen
400 Bad Requestmalformed JSON, or a query/vector whose length ≠ store dimension
401 Unauthorizedmissing or wrong bearer token (when a token is configured)
403 Forbiddena write against a --read-only server
409 Conflictthe store’s writer lock is held by another process
413 Payload Too Largerequest body exceeds --max-body-bytes
507 Insufficient Storagean allocation guard (max_vector_bytes) or OOM tripped
500 Internal Server Erroranything else (an IO fault, a bug)