docs/en/concepts/11-multi-tenant.md
OpenViking multi-tenancy does not mean "deploy one isolated server per team." Instead, a single OpenViking Server uses account and user identity boundaries to control sharing and isolation.
This model fits two common scenarios:
With multi-tenancy enabled, you can:
accountresources within the same accountuseraccount_idaccount is the outer tenant boundary. You can think of it as a workspace, team, or customer space.
account values by defaultaccountsresources, user, and session all live inside an accountuser_iduser is the per-account user boundary.
user_idaccount| Role | Scope | Typical capabilities |
|---|---|---|
| ROOT | Global | Create/delete accounts, cross-tenant access, user management |
| ADMIN | Single account | Manage users in the same account, regenerate user keys |
| USER | Single account | Access its own user/peer/session data and shared resources in the same account |
OpenViking Server supports two multi-tenant related authentication modes:
| Mode | Config | Identity source | Typical use case |
|---|---|---|---|
api_key | server.auth_mode = "api_key" | Root key or user key | Standard deployment |
trusted | server.auth_mode = "trusted" | Upstream-injected X-OpenViking-Account / X-OpenViking-User | Behind a trusted gateway |
root_api_key DoesOnce server.root_api_key is configured, OpenViking enters formal multi-tenant mode:
account_id, user_id, and role from the user keyIf auth_mode = "api_key" and root_api_key is not configured, the server runs in dev mode:
default/default| Data type | Shared across accounts | Shared inside one account | Default isolation boundary |
|---|---|---|---|
Shared resources (viking://resources) | No | Yes | account |
User resources (viking://user/{user_id}/resources) | No | No | user |
Peer resources (viking://user/{user_id}/peers/{peer_id}/resources) | No | No | user / peer |
| Memories | No | No | user / peer |
| Skills | No | No | user |
| Sessions | No | No | user / session |
For users, URIs still look like normal viking://... paths:
viking://resources/project-a/
viking://user/alice/memories/
viking://user/alice/resources/
viking://user/alice/peers/web-visitor-alice/resources/
But the underlying storage automatically gains an account prefix:
/local/{account_id}/resources/project-a/
/local/{account_id}/user/alice/memories/
/local/{account_id}/user/alice/resources/
/local/{account_id}/user/alice/peers/web-visitor-alice/resources/
So multi-tenant isolation does not rely on a special public URI format. It relies on request context, account_id and user_id, applied consistently through the stack.
Filesystem operations and semantic retrieval are tenant-aware:
account_idresources can include account-shared resourcesmemory, user resources, and skill are further filtered by the current user spaceviking://user/{user}/peers to one peer for filesystem and retrieval operationsThis keeps "what you can search" aligned with "what you can read."
peer_id is a content scope inside the current user boundary. It never changes the
tenant or user identity.
Set X-OpenViking-Actor-Peer: <peer_id> (or SDK/CLI actor_peer_id) when a request
should only see one peer from the current user's peer collection:
viking://resources.viking://user/{user}/peers, only that peer's memories/resources are selected.viking://user/{user}/peers.web-visitor-alice.{
"server": {
"auth_mode": "api_key",
"root_api_key": "your-secret-root-key"
}
}
curl -X POST http://localhost:1933/api/v1/admin/accounts \
-H "Content-Type: application/json" \
-H "X-API-Key: your-secret-root-key" \
-d '{
"account_id": "acme",
"admin_user_id": "alice"
}'
curl -X POST http://localhost:1933/api/v1/admin/accounts/acme/users \
-H "Content-Type: application/json" \
-H "X-API-Key: <admin-or-root-key>" \
-d '{
"user_id": "bob",
"role": "user"
}'
For normal reads, writes, searches, and session commits, prefer a user key:
curl http://localhost:1933/api/v1/fs/ls?uri=viking:// \
-H "X-API-Key: <bob-user-key>"
This lets the server resolve identity directly from the key, without extra tenant headers.
In api_key mode, tenant-scoped data APIs such as ls, find, and sessions
resolve the effective account and user from the API key itself. Do not send
X-OpenViking-Account or X-OpenViking-User in this mode; header-based identity
assertion belongs to trusted mode.
An ADMIN key can call data APIs as its own account/user:
curl http://localhost:1933/api/v1/fs/ls?uri=viking:// \
-H "X-API-Key: <admin-user-key>"
A ROOT key is for Admin APIs and selected system/monitoring APIs. It cannot
access tenant-scoped data APIs in api_key mode because it is not bound to a
tenant user. Use a user/admin key for data access, or trusted mode for upstream
identity assertion.
The current OpenClaw plugin follows a "plugin holds one user identity" model:
baseUrl + apiKey, with optional peer_role / peer_prefixapiKey should normally be a user keyaccount_id and user_id from that user keyTypical config:
openclaw config set plugins.entries.openviking.config.mode remote
openclaw config set plugins.entries.openviking.config.baseUrl "http://your-server:1933"
openclaw config set plugins.entries.openviking.config.apiKey "<user-api-key>"
openclaw config set plugins.entries.openviking.config.peer_role assistant
openclaw config set plugins.entries.openviking.config.peer_prefix "<peer-prefix>"
Characteristics of this model:
peer_prefix distinguishes OpenClaw runtime identities when building peer/session metadataresources can be shared inside the same account, while user memory stays user-scopedaccount / userIn api_key mode, a user key is already enough to express identity:
account and user are resolved server-side from the keypeer_prefix for runtime identity labelingpeer_id for per-message speaker identityIf you give the plugin a root key directly, normal tenant-scoped data APIs will not have a key-bound tenant user, so that is not a good default for day-to-day access.
Vikingbot uses a different practice. It behaves more like a platform serving many end users:
account_idExample config:
{
"bot": {
"ov_server": {
"server_url": "http://127.0.0.1:1933",
"root_api_key": "test",
"account_id": "default",
"admin_user_id": "default"
}
}
}
Characteristics of this model:
resources| Scenario | Recommended pattern |
|---|---|
| One OpenClaw instance maps to one fixed identity | OpenClaw plugin + user key |
| One gateway or bot service serves many end users | Vikingbot + root-key-managed users |
| A trusted gateway injects identity upstream | trusted mode |
| Local single-user experience without formal tenant isolation | Dev mode without root_api_key |
root_api_key is not the normal business-access keyThe root key is mainly for:
Normal application traffic should use user keys or admin keys, depending on the caller identity it should run as.
peer_id does not define the tenantpeer_id identifies an interaction peer under the current user. It does not create a tenant,
but peer content can be selected through explicit peer URIs or the peer collection filter, such as
viking://user/{user_id}/peers/{peer_id}/memories or
viking://user/{user_id}/peers/{peer_id}/resources.
account_iduser_idroot_api_key does not mean "formal single-tenant production mode"That is only dev mode:
root_api_key and auth_mode