Architecture
Multi-tenant
Sandra supports full multi-tenancy on a single deployment. Each tenant gets isolated data, tools, channel credentials, knowledge base, and branding — all on shared infrastructure.
What’s isolated per tenant
| Layer | Isolation |
|---|---|
| Conversations & messages | Filtered by tenantId — no cross-tenant leakage |
| Knowledge base (RAG) | Embeddings are tagged per tenant; retrieval is tenant-scoped |
| Tools | Each tenant can have its own registered tools + enabled/disabled flags |
| Channel credentials | WhatsApp, Instagram, email, Zoom — all per-tenant via ProviderConfig |
| Google Workspace | Per-tenant service account delegation (Calendar, Drive, Gmail) |
| Agent personality | Per-tenant system prompt, name, and behavior config |
| Users & roles | Users belong to a tenant; roles (admin, user) are tenant-scoped |
| Analytics | Dashboard stats are tenant-filtered |
| API keys | Each tenant generates its own API keys |
Creating a tenant
Tenants are created through the admin dashboard or the admin API. No external provisioning needed.
Via Admin Dashboard
Navigate to Admin → Tenants → Create. Fill in the tenant name and slug. The tenant is immediately active with default settings.
Via API
curl -X POST $SANDRA_URL/api/admin/tenants \
-H "Authorization: Bearer $ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corp",
"slug": "acme",
"config": {
"agentName": "Aria",
"systemPrompt": "You are Aria, a helpful assistant for Acme Corp employees."
}
}'Via database seed
For initial setup or CI/CD, use the Prisma seed script:
// prisma/seed-tenant.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
await prisma.tenant.create({
data: {
name: 'Acme Corp',
slug: 'acme',
settings: {
agentName: 'Aria',
defaultLanguage: 'en',
},
},
});Per-tenant tool configuration
Each tenant can have its own set of tools. Register tools via the admin API and assign them to specific tenants.
// POST /api/admin/tools
{
"name": "lookupOrder",
"description": "Look up a customer order by ID",
"endpoint": "https://api.acme.com/orders/{orderId}",
"tenantId": "tenant-acme-001",
"parameters": {
"orderId": { "type": "string", "description": "The order ID to look up" }
}
}Sandra will only offer this tool to users in the Acme tenant. Other tenants won’t see or trigger it.
Per-tenant Google Workspace
Each tenant can connect their own Google Workspace for Calendar, Drive, and Gmail integration. Store the service account credentials in the tenant’s ProviderConfig:
// In ProviderConfig for tenant
{
"provider": "google",
"tenantId": "tenant-acme-001",
"config": {
"serviceAccountEmail": "sa@acme-project.iam.gserviceaccount.com",
"privateKey": "...",
"delegatedUser": "admin@acme.com",
"scopes": ["calendar", "drive", "gmail"]
}
}Per-tenant Zoom
Similarly, each tenant can bring their own Zoom credentials for meeting scheduling:
{
"provider": "zoom",
"tenantId": "tenant-acme-001",
"config": {
"accountId": "...",
"clientId": "...",
"clientSecret": "..."
}
}Passing tenantId in API calls
When calling the Chat API from a multi-tenant integration, include the tenantId field so Sandra routes to the correct tenant context:
curl -X POST $SANDRA_URL/api/chat \
-H "Content-Type: application/json" \
-d '{
"message": "Schedule a team meeting for tomorrow at 2pm",
"sessionId": "s1",
"userId": "u1",
"tenantId": "tenant-acme-001"
}'If the API key is tenant-scoped (generated from that tenant’s admin panel), the tenantId is inferred automatically — you don’t need to pass it explicitly.
User roles
Users are scoped to a tenant and assigned a role:
| Role | Permissions |
|---|---|
| super_admin | Full access across all tenants. Can create tenants, manage global config. |
| admin | Full access within their tenant. Manage tools, channels, users, analytics. |
| user | Chat with Sandra. View own conversations and preferences. |
← Knowledge Base
Index your content for RAG retrieval.
← Quickstart
Send your first message in 5 minutes.