LM-Kit OneDocs2026.10.10lm-kit.comEULA
Search & RAG/The Search Engine

Search: Access Model

Reference for who can reach what in Search: the four nouns (cluster, tenant, collection, API key), the caller model, the two kinds of grants with their levels and collection scope, the discovery endpoints, where to see and fix access, and the security properties the API guarantees. Deciding WHERE your boundaries go (one tenant or many, one cluster or many) is Clusters, Tenants, Collections; this page is how the boundaries are enforced.


1The four nouns#

Noun What it is Created by Analogy
Cluster One physical search backend (a PostgreSQL database, or an app-level store such as SQLite, SQL Server, MySQL) with its own connections and schema. Addressed by an operator-chosen cluster_id. Operator, in the admin panel's Search section. Never through the public API. The database server
Tenant The isolation and configuration boundary inside a cluster: embedding model, search modes, OCR, normalization are all tenant-wide, and access is granted per tenant. Addressed by a server-generated GUID. API (POST /lmkit/v1/search/tenants) or the admin panel. The database schema
Collection A dataset inside a tenant. Documents are indexed into collections; a search targets one or several (collection_ids). Addressed by a caller-chosen GUID. API (POST /lmkit/v1/search/collections), idempotent. The table
API key The caller identity. Grants attach to the key id, so every key carries exactly the reach it was given. Operator, in the admin panel's Access section. The database role

The rule that decides where a boundary goes: the tenant is the smallest unit you will ever grant or revoke independently. Anything below that granularity is a collection (scoped at query time) or a metadata filter (a convenience, never a security boundary: filters are supplied by the caller).

2The caller model#

Every tenant-access decision runs on one of exactly three caller shapes:

Caller Who Reach
Key caller A request authenticated with an API key The tenants its key was granted, inside the clusters its key may address
Operator The admin panel; also any keyless request while the server permits anonymous access, which requires loopback-only listeners, so the caller is on the server's own machine Everything
Nobody A keyless request anywhere else, including credentials that carry no key id (provider-issued tokens) Nothing

There is no fourth shape. A blank or missing key id can never be interpreted as operator reach, and every storage backend answers these three shapes identically.

3Tenant grants (what a key reaches)#

A key reaches a tenant only through an explicit grant on the pair (tenant, key). There is no tenant ownership: the key that creates a tenant is granted it, and any other key needs its own grant. Grants are managed per tenant in the admin panel's Search section, or per key in the Access section, and on PostgreSQL clusters they are additionally enforced by row-level security in the database itself.

Everything a key does inside a tenant (index, search, fetch, configure) requires the grant; knowing a tenant's GUID confers nothing.

Levels

Each grant carries a level, and the levels are cumulative:

Level What the key may do in the tenant
Read Search, answer, grounded chat; list, fetch and find similar documents; read golden sets and evaluation runs; run evaluations
Write Read, plus index and delete documents, create collections, edit golden sets
Manage Write, plus collection settings, tenant settings, promotion, demotion and deleting the tenant

A grant written before levels existed is Manage over every collection: exactly the reach it always had, so upgrading changes nothing for deployed callers. A key that provisions a tenant is granted Manage on it. A request whose level is too low answers 403 naming the level the key holds and the level the operation needs.

A read-only key (its API scope) holds Read on every tenant whatever its grants say. It may call the retrieval endpoints (POST search, documents/list, documents/get, documents/similar, plus every GET), never one that writes, deletes or generates text (answers, chat, query modes other than Original).

Collection scope

A grant covers every collection of the tenant (including collections created later), or only the collections it names. The tenant stays the isolation boundary, enforced by row-level security on PostgreSQL; the collection scope narrows a key inside it:

  • a collection outside the scope answers exactly like one that does not exist, and discovery never lists it;
  • creating a collection, or indexing into one, outside the scope answers 403;
  • tenant-wide operations (tenant settings, promotion, deletion) need a grant on every collection, whatever the level.

Use a scope when one audience must see part of a tenant's data. When a boundary must hold independently of the application (separate retention, separate configuration, row-level security), use a separate tenant instead.

4Cluster grants (where a key may operate)#

A key also carries a cluster-grant setting that bounds which clusters it may address at all: route requests to, list its granted tenants in, and provision tenants into.

Stored value Meaning
Empty list No clusters: what None in the create dialog, and an omitted clusterGrants through the API, produce. The dialog preselects the default cluster instead, so a key minted with its defaults addresses that one.
List of ids Exactly those clusters.
Null (the open setting) Any configured cluster. Keys minted before cluster grants existed keep this, so upgrades change nothing for deployed callers.

The cluster boundary is hard: a tenant grant inside a cluster the key may not address is unreachable until cluster access is restored (the admin panel warns before creating that state). The OpenAI-compatible vector stores live on the default cluster and pass the same gate. A cluster the caller may not address answers exactly like one that does not exist, so probing ids can never map which clusters a server hosts.

5Discovery: what can I reach?#

Two grant-scoped endpoints answer the question without the caller knowing any identifier up front. Both list projections of the caller's own grants, never the server's topology, and both return empty lists (not errors) for a key with nothing.

GET /lmkit/v1/search/clusters

Lists the clusters the key holds tenant grants in, plus clusters its key was explicitly granted (so a fresh key can discover where it may provision before it holds any tenant). Each entry carries id, full_text_store, and is_default.

GET /lmkit/v1/search/collections

Lists every collection the caller can reach across clusters, each entry carrying the cluster_id, tenant_id, tenant_name, collection_id, collection_name, and document_count needed to address it, and access: the level the caller holds there (read, write or manage). Produced under the same grant checks as every read and write, so it can never name a collection the caller could not otherwise use. The tenant listings carry access and collection_scope too.

GET /lmkit/v1/search/clusters/{clusterId}/tenants remains the per-cluster tenant listing, scoped the same way. GET /lmkit/v1/search/status reports availability and counts only the clusters the caller may address.

6Onboarding a caller#

Mapping an existing design onto clusters, tenants, and collections (schema-per-program, per-customer isolation, compliance domains) is worked through in Clusters, Tenants, Collections. Once the shape is decided, a minimal onboarding for one service:

  1. Operator mints a key in the admin panel's Access section. The create dialog preselects the default cluster; Add tenant grants existing tenants, each at a level and over every collection or a chosen few, and the grants are written with the key. With none, the key creates its own tenants on first use (as Manage) and reaches only those; or the operator creates the tenant first from the Search section and adds it.
  2. The service calls GET /search/collections to discover what it can reach, then indexes and searches with the ids it found.
  3. Adding a second audience later is a new key plus grants; nothing about the data moves.

A key minted with no Search access (None chosen, or clusterGrants omitted through the API) meets 400 This API key has no Search access on its first Search call, whatever the Search section shows: the default cluster exists for the operator, not for a key that was never granted it. The key's row in Access says Search access none, and the Requests page records the reason beside the refused call. Grant the key the cluster from that row, and the call goes through. A key granted elsewhere that omits cluster_id hears instead that no default cluster is configured or granted to it, one text for both, so a key never learns which clusters exist beyond its grants.

7Seeing and changing who reaches what#

The admin console answers the question from three directions, all writing through the same grant editor (key, tenant, level, collections):

  • Access > Search access lists every grant, grouped by key or by tenant, with its level, collections and who granted it when; Export CSV downloads the same list for an audit.
  • The key's Search access (on its row in API keys) shows the clusters it may address and every tenant it reaches.
  • The tenant's Access section (Search > cluster > tenant) shows the keys that reach it.

Check access answers "why is this key refused?": pick a key, a tenant and an operation, and it walks the gates in the order the API applies them (key active, API scope, cluster access, tenant grant, level, collections), stops at the first refusal, and offers the one change that opens it as a button that leads straight to the right dialog, already set to the fix. Every grant change is recorded in the security trail.

8Security properties, stated plainly#

  • Least privilege at the mint. A new key reaches nothing in Search until granted.
  • Least privilege inside a tenant. A grant can be Read only, limited to some collections, and a read-only key never exceeds Read, whatever it was granted.
  • A refused credential stays refused. A read-only key refused for a write is never served as an anonymous local caller, even where anonymous access is allowed.
  • No topology disclosure. Discovery lists only the caller's own reach; a denied cluster is indistinguishable from a missing one; the status probe counts only addressable clusters.
  • Fail closed. An unknown, revoked, or deleted key loses cluster access immediately; the default caller value reaches nothing; keyless credentials hold no grants and reach no tenant.
  • Defense in depth on PostgreSQL. The grant check runs in the query predicate and again in row-level security, so an application-layer mistake cannot widen reach.
  • Provisioning is bounded. Creating a tenant requires a caller that could reach it afterwards, inside a cluster its key may address.
  • The operator allowance is explicit. Keyless callers act as the operator only while the server permits anonymous access, which is automatically withdrawn the moment any listener goes beyond loopback.