Hebbrix
Multi-Tenancy

Multi-Tenancy & Scoping

How to keep each end-user's memory isolated in a SaaS app. The collection is the tenancy boundary — pass collection_id everywhere and one user's data never leaks into another's.

The isolation model

Collection = boundary

Create one collection per end-user (or per tenant). Everything you store and retrieve is scoped to a collection.

Scope every call

Pass collection_id on writes and reads. A scoped read returns only that collection's data — strict isolation, no cross-leak.

Account-wide = opt-in

Omit collection_id for legacy account-wide behavior. Data with no collection is "global" and excluded from scoped reads by default.

Where collection_id applies

SurfaceScoping
POST /v1/memories, /v1/memories/process, /v1/memories/rawStores into the given collection
POST /v1/search (and /search/reason, /search/similar)Returns only that collection's memories
POST /v1/chat/completionsmemory_context + profile facts scoped to the collection
GET /v1/profile, GET /v1/profile/facts, PATCH /v1/profile, POST /v1/profile/factsPer-collection profile facts (no cross-leak)
GET /v1/corrections/relevant, GET /v1/correctionsStrict collection scope (+ include_global)
POST /v1/decisions, GET /v1/decisions/similarStrict collection scope (+ include_global)
POST /v1/knowledge-graph/entities + /relationships, /queryEntities/edges tagged + filtered by collection

Note on globals: items written without a collection_id are account-wide. Scoped reads exclude them by default; corrections, decisions, and profile reads accept include_global=true to also surface account-wide entries alongside the collection's own.

End-to-end: isolate two users

Python (per-user isolation)
import os, requests

BASE = "https://api.hebbrix.com/v1"
H = {"Authorization": f"Bearer {os.environ['HEBBRIX_API_KEY']}"}

# One collection per end-user
alice = requests.post(f"{BASE}/collections", headers=H,
    json={"name": "user-alice"}).json()["id"]
bob = requests.post(f"{BASE}/collections", headers=H,
    json={"name": "user-bob"}).json()["id"]

# Store a fact for Alice only
requests.post(f"{BASE}/memories", headers=H,
    json={"content": "Allergic to shellfish.", "collection_id": alice})

# A scoped search for Bob can NEVER see Alice's fact
r = requests.post(f"{BASE}/search", headers=H,
    json={"query": "food allergies", "collection_id": bob, "limit": 5}).json()
assert all("shellfish" not in m["content"].lower() for m in r["results"])  # isolated

# Scope the profile too (so a global preference never leaks across users)
requests.patch(f"{BASE}/profile", headers=H,
    json={"diet": "pescatarian", "collection_id": alice})
prof_bob = requests.get(f"{BASE}/profile", headers=H,
    params={"collection_id": bob}).json()
# prof_bob does not contain Alice's "diet" fact

Best practices

Always pass collection_id on both writes and reads in a multi-tenant app. Treating it as optional is how cross-user leaks happen.

Use include_global sparingly. Reserve account-wide (no-collection) entries for genuinely global policy — not per-user data.

The API key is the account; the collection is the tenant. Keys scope to your account; collections scope to each of your end-users within it.

Assistant

Ask me anything about Hebbrix