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.
| Flag | Backend | When to use |
|---|---|---|
local_onnx | Embedded ONNX runtime | Default. Zero external dependencies. |
ollama | Local Ollama daemon | Larger models on a workstation. |
lmstudio | LM Studio HTTP server | OpenAI-compatible local models. |
openai | OpenAI / OpenAI-compatible | Hosted 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
- API reference: every endpoint, every body shape.
- Multi-tenancy: roles and shared indexes.
- Deployment: Docker Compose and the 3-node cluster.