Skip to content

Multi-Tenant

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:

  • Multiple teams or customers share one OpenViking service, but their data must stay isolated
  • Multiple users inside one team need shared resources but isolated memories

What It Enables

With multi-tenancy enabled, you can:

  • Serve multiple teams, customers, or applications from one OpenViking Server
  • Isolate different teams with account
  • Share resources within the same account
  • Isolate user memories and sessions with user
  • Manage permissions with ROOT / ADMIN / USER roles
  • Support different integration patterns such as OpenClaw plugin, Vikingbot, CLI, and HTTP SDKs

Core Identity Model

account_id

account is the outer tenant boundary. You can think of it as a workspace, team, or customer space.

  • Data is isolated across different account values by default
  • ROOT can create and delete accounts
  • resources, user, and session all live inside an account

user_id

user is the per-account user boundary.

  • User memories and user sessions are isolated by user_id
  • A normal user can only access its own user space
  • An admin can manage users inside the same account

Roles

RoleScopeTypical capabilities
ROOTGlobalCreate/delete accounts, cross-tenant access, user management
ADMINSingle accountManage users in the same account, regenerate user keys
USERSingle accountAccess its own user/peer/session data and shared resources in the same account

Authentication Modes

OpenViking Server supports two multi-tenant related authentication modes:

ModeConfigIdentity sourceTypical use case
api_keyserver.auth_mode = "api_key"Root key or user keyStandard deployment
trustedserver.auth_mode = "trusted"Upstream-injected X-OpenViking-Account / X-OpenViking-UserBehind a trusted gateway

What root_api_key Does

Once server.root_api_key is configured, OpenViking enters formal multi-tenant mode:

  • The root key manages accounts and users
  • User keys are generated by the Admin API for normal data access
  • The server resolves account_id, user_id, and role from the user key

If auth_mode = "api_key" and root_api_key is not configured, the server runs in dev mode:

  • All requests are treated as ROOT
  • The default identity is default/default
  • This is only allowed on localhost

Sharing and Isolation Boundaries

Logical Layer

Data typeShared across accountsShared inside one accountDefault isolation boundary
Shared resources (viking://resources)NoYesaccount
User resources (viking://user/{user_id}/resources)NoNouser
Peer resources (viking://user/{user_id}/peers/{peer_id}/resources)NoNouser / peer
MemoriesNoNouser / peer
SkillsNoNouser
SessionsNoNouser / session

Storage Layer

For users, URIs still look like normal viking://... paths:

text
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:

text
/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.

Retrieval Layer

Semantic retrieval is tenant-aware as well:

  • Non-ROOT requests are automatically filtered by account_id
  • resources can include account-shared resources
  • memory, user resources, and skill are further filtered by the current user space
  • Passing peer_id adds only that peer's memories/resources; skills remain user-scoped

This keeps "what you can search" aligned with "what you can read."

Standard Usage Flow

1. Enable multi-tenancy

json
{
  "server": {
    "auth_mode": "api_key",
    "root_api_key": "your-secret-root-key"
  }
}

2. ROOT creates an account and the first admin

bash
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"
  }'

3. ADMIN or ROOT registers normal users

bash
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"
  }'

4. Prefer user keys for normal application traffic

For normal reads, writes, searches, and session commits, prefer a user key:

bash
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.

5. Data API identity comes from the user or admin key

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:

bash
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.

Integration Patterns

OpenClaw Plugin 2.0: one instance uses one user key

The current OpenClaw plugin follows a "plugin holds one user identity" model:

  • Remote mode config is baseUrl + apiKey, with optional peer_role / peer_prefix
  • apiKey should normally be a user key
  • The server resolves account_id and user_id from that user key
  • The plugin keeps OpenClaw agent identity in peer/session metadata, not tenant headers

Typical config:

bash
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:

  • Simple integration, because the plugin does not manage account/user lifecycle
  • Best for "one OpenClaw instance maps to one OpenViking user identity"
  • peer_prefix distinguishes OpenClaw runtime identities when building peer/session metadata
  • resources can be shared inside the same account, while user memory stays user-scoped

Why the OpenClaw plugin usually does not set account / user

In api_key mode, a user key is already enough to express identity:

  • account and user are resolved server-side from the key
  • The plugin can provide peer_prefix for runtime identity labeling
  • Internally, the plugin writes user-scoped memory and uses peer_id for per-message speaker identity

If 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: root key manages many end users

Vikingbot uses a different practice. It behaves more like a platform serving many end users:

  • The bot connects to OpenViking with a root key
  • The bot config fixes an account_id
  • The bot automatically registers users inside that account
  • The bot caches per-user user keys and uses them for memory commit/search whenever possible

Example config:

json
{
  "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:

  • Good for one bot service serving many chat users
  • All users inside the same account share resources
  • User memories are isolated through auto-managed user identities
  • The bot takes on more tenant lifecycle management logic than the OpenClaw plugin

Which Practice to Choose

ScenarioRecommended pattern
One OpenClaw instance maps to one fixed identityOpenClaw plugin + user key
One gateway or bot service serves many end usersVikingbot + root-key-managed users
A trusted gateway injects identity upstreamtrusted mode
Local single-user experience without formal tenant isolationDev mode without root_api_key

Common Misunderstandings

1. root_api_key is not the normal business-access key

The root key is mainly for:

  • Creating and deleting accounts
  • Registering users
  • Regenerating keys
  • Operations and diagnostics

Normal application traffic should use user keys or admin keys, depending on the caller identity it should run as.

2. peer_id does not define the tenant

peer_id identifies an interaction peer under the current user. It does not create a tenant, but it can select a peer content subspace such as viking://user/{user_id}/peers/{peer_id}/memories or viking://user/{user_id}/peers/{peer_id}/resources.

  • The tenant boundary is account_id
  • The user boundary is user_id
  • Peer content remains inside that user boundary

3. No root_api_key does not mean "formal single-tenant production mode"

That is only dev mode:

  • All requests run as ROOT
  • It is not suitable for public or shared deployments

4. OpenClaw plugin and Vikingbot are not the same multi-tenant pattern

  • OpenClaw plugin is closer to "a client directly uses one user identity"
  • Vikingbot is closer to "a platform manages many users and their user keys"

Released under the Apache-2.0 License.