Getting started

From zero to a running server with one tenant, one index, and a working search query in under five minutes.

Pick an embedding backend

Every search and ingest call routes text through an embedding server. GraphANN™ ships with four backends.

FlagBackendWhen to use
local_onnxEmbedded ONNX runtimeDefault. Zero external dependencies.
ollamaLocal Ollama daemonLarger models on a workstation.
lmstudioLM Studio HTTP serverOpenAI-compatible local models.
openaiOpenAI / OpenAI-compatibleHosted models, requires API key.

Run the server

The defaults are fine for local work.

./graphann serve --embedding-server local_onnx --port 38888

# Switch backends with a flag — no rebuild
./graphann serve --embedding-server ollama --embedding-model nomic-embed-text:v1.5
./graphann serve --embedding-server openai --embedding-key sk-...

Health check: GET /health returns {"status":"healthy"}. Readiness: GET /ready.

Create a tenant

Tenants are the isolation boundary. Pass a custom id for idempotency, or omit it for a generated t_<uuid>.

curl -s -X POST http://localhost:38888/v1/tenants \
-H 'content-type: application/json' \
-d '{"id":"t_acme","name":"Acme Corp"}'

# 201 Created
# {
# "id": "t_acme",
# "name": "Acme Corp",
# "created_at": "2026-04-17T10:00:00Z"
# }

Create an index

Indexes live inside a tenant. The same idempotency rules apply for the id field.

curl -s -X POST http://localhost:38888/v1/tenants/t_acme/indexes \
-H 'content-type: application/json' \
-d '{"id":"i_docs","name":"product-docs","description":"Public-facing docs"}'

# 201 Created
# {
# "id": "i_docs",
# "tenant_id": "t_acme",
# "name": "product-docs",
# "status": "ready",
# ...
# }

Ingest documents

POST /documents chunks, embeds, and adds to the live index. The endpoint is idempotent if you supply your own document id. Metadata is stored verbatim and returned with search hits.

curl -s -X POST \
http://localhost:38888/v1/tenants/t_acme/indexes/i_docs/documents \
-H 'content-type: application/json' \
-d '{
"documents": [
{
"id": "doc-001",
"text": "Vector search recomputes embeddings on demand.",
"metadata": {"source": "intro.md", "version": 3}
}
]
}'

# 200 OK — returns chunk ids and live index stats

Search

POST /search accepts either a text query or a raw vector. k defaults to 10.

curl -s -X POST \
http://localhost:38888/v1/tenants/t_acme/indexes/i_docs/search \
-H 'content-type: application/json' \
-d '{"query":"how does search work","k":5}'

# 200 OK
# {
# "results": [
# {"id":"...","text":"...","score":0.83,"metadata":{...}},
# ...
# ],
# "total": 5
# }

Same flow from Python

No first-party Python client exists yet. Use the standard library or httpx against the HTTP API.

import httpx

base = "http://localhost:38888"
tenant = "t_acme"
index = "i_docs"

with httpx.Client(base_url=base, timeout=30) as c:
c.post(f"/v1/tenants", json={"id": tenant, "name": "Acme Corp"})
c.post(f"/v1/tenants/{tenant}/indexes",
json={"id": index, "name": "product-docs"})

c.post(f"/v1/tenants/{tenant}/indexes/{index}/documents", json={
"documents": [
{"id": "doc-001", "text": "Vector search recomputes on demand."}
]
})

r = c.post(f"/v1/tenants/{tenant}/indexes/{index}/search",
json={"query": "how does search work", "k": 5})
for hit in r.json()["results"]:
print(hit["score"], hit["text"][:80])

Next steps