Multi-Tenancy
Complete data isolation for developers and their end-users.
Overview
Raven is designed as a multi-tenant platform where developers (tenants) integrate the API to provide persistent memory for their AI agents, while their end-users get completely isolated storage.
Data Hierarchy
Tenant (Developer)
├── API Keys (authentication)
│ ├── Default Key (all scopes)
│ └── Additional Keys (limited scopes)
├── Settings (configuration)
│ ├── retention_days
│ ├── max_users
│ ├── max_conversations_per_user
│ └── webhook_url
└── Users (end-users)
├── User A
│ ├── Conversation 1
│ │ └── Memory Blobs (encrypted)
│ └── Conversation 2
│ └── Memory Blobs (encrypted)
└── User B
└── Conversation 1
└── Memory Blobs (encrypted)Tenant Isolation
Each tenant's data is completely isolated through multiple mechanisms:
API Key Authentication
Every request is authenticated with tenant-specific API keys. Keys are scoped and can be restricted to specific operations.
Storage Key Namespacing
All storage keys are prefixed with tenant ID, ensuring no data overlap: tenant:{id}:user:{id}:...
Encryption Key Scoping
Encryption keys are derived per tenant, making cross-tenant decryption impossible.
Rate Limiting
Rate limits and quotas are enforced per-tenant to prevent resource abuse.
Storage Key Structure
All keys in Redis follow a strict namespacing pattern:
# Tenant data
tenant:{tenant_id}
tenant:email:{email}
# API keys (by hash for security)
apikey:hash:{sha256_hash}
tenant:{tenant_id}:apikeys
# Users
tenant:{tenant_id}:users
tenant:{tenant_id}:user:{user_id}
tenant:{tenant_id}:user:external:{external_ref}
# Conversations
tenant:{tenant_id}:user:{user_id}:conversations
tenant:{tenant_id}:user:{user_id}:conversation:{conversation_id}
# Memory
tenant:{tenant_id}:user:{user_id}:conversation:{conversation_id}:buffer
tenant:{tenant_id}:user:{user_id}:episodic:blob_ids
tenant:{tenant_id}:user:{user_id}:semantic:blob_idsAuthentication Flow
Agent Request
↓
Authorization: Bearer rk_xxx...
↓
Auth Middleware
├── Validate API key format (rk_prefix)
├── Hash key with SHA-256
├── Lookup apikey:hash:{hash} in Redis
├── Verify key is not revoked
├── Check required scopes for endpoint
└── Attach tenant context to request
↓
Route Handler
├── req.tenant = { id, name, settings }
├── req.apiKey = { id, scopes }
└── Operations scoped to tenantTenant Configuration
Each tenant can customize their settings:
| Setting | Default | Description |
|---|---|---|
retention_days | 30 | How long to keep memory blobs |
max_users | 1000 | Maximum end-users allowed |
max_conversations_per_user | 100 | Conversations per user limit |
batch_size | 10 | Interactions before buffer flush |
encryption_mode | platform | Key management mode |
webhook_url | - | URL for event notifications |
API Key Scopes
Create keys with specific scopes for different use cases:
// Full access key (default)
const fullAccessScopes = [
'memory:read', 'memory:write',
'users:read', 'users:write',
'conversations:read', 'conversations:write',
'tenant:read', 'tenant:admin'
];
// Read-only key for analytics
const readOnlyScopes = [
'memory:read',
'users:read',
'conversations:read',
'tenant:read'
];
// Agent-only key (memory operations only)
const agentScopes = [
'memory:read', 'memory:write',
'conversations:read', 'conversations:write'
];User Management
Users represent your end-users. The external_ref field lets you link Raven users to your system:
curl -X POST http://localhost:3000/api/v1/users \
-H "Authorization: Bearer rk_live_xxxx" \
-H "Content-Type: application/json" \
-d '{
"external_ref": "your-user-123",
"display_name": "John Doe",
"metadata": {
"plan": "pro",
"region": "us-west"
}
}'Later, you can look up users by external_ref:
curl -X GET "http://localhost:3000/api/v1/users?external_ref=your-user-123" \
-H "Authorization: Bearer rk_live_xxxx"Integration Pattern
A typical integration flow:
Register as Tenant
Sign up once and receive your API key
Create Users On-Demand
Create Raven users when your users first interact with your agent
Start Conversations
Create conversations for each chat session
Ingest Memory
Store every interaction for later retrieval
Query Context
Retrieve relevant context before generating responses