Multi-tenancy
A single GraphANN™ deployment hosts many isolated tenants. Each tenant owns its own indexes; selected indexes can be shared across an organisation. Roles control what each user inside a tenant may do.
Tenant isolation
Every tenant has a unique id prefixed t_. Indexes belong to exactly one tenant and inherit its isolation boundary. The server rejects any request whose path tenant does not match the index's owning tenant. No implicit cross-tenant reads.
- Document text and metadata never leave the owning tenant unless an index is explicitly marked shared.
- Quotas are enforced per tenant (max documents per index, ingest rate).
- Tenant ids are validated against
^[a-zA-Z0-9_-]{1,128}$to block path-traversal attacks via crafted ids.
# Tenant A creates an index
curl -X POST http://localhost:38888/v1/tenants/t_acme/indexes \
-d '{"id":"i_docs","name":"product-docs"}' \
-H 'content-type: application/json'
# Tenant B asks for it → 403 Forbidden
curl http://localhost:38888/v1/tenants/t_other/indexes/i_docs
# {"error":"Index does not belong to this tenant","code":"forbidden"}
Shared indexes
An index can be marked shared at the organisation level. Shared indexes remain owned by a tenant for write authority but are queryable by every user in the org. Use this for company-wide knowledge bases, public product docs, or shared reference data.
# Mark an index as shared at org-level (sync ingest path)
curl -X POST http://localhost:38888/v1/orgs/org_acme/documents \
-H 'content-type: application/json' \
-d '{
"shared": true,
"index_name": "company-handbook",
"documents": [
{"id":"hb-001","text":"Vacation policy ..."}
]
}'
Combined search
The org-level multi-index search endpoint runs a single query across a user's accessible content: their personal indexes plus any shared indexes their org grants. Results are merged and re-ranked.
# Search across a user's accessible content
# (their personal indexes + any shared org indexes)
curl -X POST \
http://localhost:38888/v1/orgs/org_acme/users/u_jane/search \
-H 'content-type: application/json' \
-d '{"query":"vacation policy","k":10}'
# 200 OK — results carry their source index id so you can show
# "from: company-handbook" or "from: my-notes" in the UI.
Roles
Each user inside a tenant has one of three roles. Roles gate write and admin actions; reads are allowed for any role inside the owning tenant.
| Role | Read | Write (ingest, delete) | Admin (tenant + index lifecycle) |
|---|---|---|---|
admin | yes | yes | yes |
editor | yes | yes | no |
viewer | yes | no | no |
The role system is enforced server-side at the tenant boundary. Authentication wires the calling user's role into every request, and role checks gate every privileged operation before it touches the index.
Per-tenant rate limits
Quotas live on the tenant. Today they cover documents-per-index and a coarse ingest cap. Per-tenant request-per-second limiting is configured at the proxy layer (Traefik/nginx) in production deployments.
Design notes
- One process, many tenants. No per-tenant subprocesses or sidecars. The server multiplexes everything in-memory with a per-index lock.
- Storage layout. Each index gets its own directory under
data/. Deleting a tenant deletes only that tenant's directories. - Idempotent creates. Pass a custom
idon tenant or index creation to make the operation safe to replay (e.g. from a sync job that may run twice). - External ids. Use
repo_id+file_path+commit_shaon documents to enable repo-scoped search filters. The same fields drive shared-index access checks at query time.