# 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 `account`s
- `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

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

## Authentication Modes

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 |

### 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 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 |

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

### Filesystem And Retrieval Layer

Filesystem operations and semantic retrieval are tenant-aware:

- 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
- An actor peer filters `viking://user/{user}/peers` to one peer for filesystem and retrieval operations

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

### Peer Collection Filter

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

- Empty-target retrieval still includes the current user root and shared `viking://resources`.
- When retrieval resolves `viking://user/{user}/peers`, only that peer's memories/resources are selected.
- Filesystem operations cannot read, list/tree, grep/find/search, write, move, or delete another peer under `viking://user/{user}/peers`.
- User-scoped memories, resources, skills, shared resources, and session ownership are otherwise unchanged.
- The peer ID must be a safe single path segment, for example `web-visitor-alice`.

## 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

| 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` |

## 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 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`.

- 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"

## Related Documentation

- [Authentication](../guides/04-authentication.md) - Auth modes, headers, and key rules
- [Configuration](../guides/01-configuration.md) - `root_api_key` and `auth_mode`
- [Admin API](../api/08-admin.md) - Admin API reference
- [API Overview](../api/01-overview.md) - CLI and HTTP connection patterns
- [Data Encryption](./10-encryption.md) - At-rest encryption in multi-tenant deployments
- [Multi-tenant Example](https://github.com/volcengine/OpenViking/blob/main/examples/multi_tenant/README.md) - End-to-end management workflow
- [OpenClaw Plugin](https://github.com/volcengine/OpenViking/blob/main/examples/openclaw-plugin/README.md) - OpenClaw integration
- [Vikingbot](https://github.com/volcengine/OpenViking/blob/main/bot/README.md) - Bot-side multi-user integration
