Multi-Tenancy¶
The RAG Chatbot supports multi-tenant architecture, allowing you to run multiple isolated instances from a single codebase. Each tenant has its own database, configuration, and API keys.
Overview¶
Multi-tenancy enables:
- Complete Data Isolation - Each tenant has a separate database
- Independent Configuration - Per-tenant LLM providers, API keys, and settings
- Shared Infrastructure - Single codebase serves all tenants
- Flexible Deployment - Add or remove tenants without code changes
Architecture¶
┌──────────────────────────────────────────────────────────────┐
│ PHP Application │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Tenant A │ │ Tenant B │ │ Tenant C │ ... │
│ │ Request │ │ Request │ │ Request │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Tenant Resolver │ │
│ │ (Extracts tenant_id from request) │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ .env │ │ .env │ │ .env │ │
│ │ Tenant A │ │ Tenant B │ │ Tenant C │ │
│ └──┬────┬───┘ └──┬────┬───┘ └──┬────┬───┘ │
│ │ │ │ │ │ │ │
│ ▼ │ ▼ │ ▼ │ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ ragdb │ │ ragdb │ │ ragdb │ │ │
│ │Tenant A│ │Tenant B│ │Tenant C│ │ │
│ └────────┘ └────────┘ └────────┘ │ │
│ (per-tenant, may be on remote servers) │ │
│ │ │
│ ┌───────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ chatdb (shared) │ │
│ │ Widget settings, prompts, │ │
│ │ inquiries, tenant prompts │ │
│ │ (isolated by tenant_id) │ │
│ └─────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
Tenant CLI¶
Manage tenants using the command-line interface.
Create a Tenant¶
Options:
| Option | Description |
|---|---|
--name=<name> | Human-readable tenant name |
--db-host=<host> | Database host (default: localhost) |
--db-port=<port> | Database port (default: 5432) |
--db-name=<name> | Database name (default: ragdb_ |
--db-user=<user> | Database username (default: raguser) |
--db-password=<pass> | Database password (auto-generated if not provided) |
Example:
Output:
Creating tenant: acme-corp
Name: ACME Corporation
Directory: /var/www/chatbot/tenants/acme-corp
Generated API Keys:
ADMIN_API_KEY: 3b637c0957511bc8920f6c9695cfb948...
CHAT_API_KEY: b0e5040f975ec8f60e9f150012b356ec...
Tenant created successfully!
Next steps:
1. Edit /var/www/chatbot/tenants/acme-corp/.env
2. Set your OPENAI_API_KEY
3. Create the database and run migrations
4. Test with: php test_db.php acme-corp
List Tenants¶
Output:
Available tenants:
- acme-corp (ACME Corporation)
- demo-tenant (Demo Tenant)
- client-xyz (Client XYZ)
Total: 3 tenants
Show Tenant Details¶
Output:
Tenant: acme-corp
Name: ACME Corporation
Directory: /var/www/chatbot/tenants/acme-corp
Config File: /var/www/chatbot/tenants/acme-corp/.env
Configuration:
DB_HOST: localhost
DB_NAME: acme_ragdb
LLM_PROVIDER: openai
OPENAI_MODEL: gpt-5.2
Regenerate API Keys¶
Generates new ADMIN_API_KEY and CHAT_API_KEY for the tenant.
Delete a Tenant¶
Warning
This deletes the tenant configuration directory. The database is NOT deleted automatically—you must drop it manually.
Tenant Configuration¶
Each tenant has its own .env file in /var/www/chatbot/tenants/<tenant-id>/.env.
Directory Structure¶
/var/www/chatbot/
├── tenants/
│ ├── acme-corp/
│ │ └── .env
│ ├── demo-tenant/
│ │ └── .env
│ └── client-xyz/
│ └── .env
├── public_html/
├── src/
└── ...
Tenant Environment Variables¶
Each tenant .env file contains:
# ===========================================
# Tenant Identification
# ===========================================
TENANT_ID=acme-corp
TENANT_NAME=ACME Corporation
# ===========================================
# Database (tenant-specific RAG data)
# ===========================================
DB_HOST=localhost
DB_PORT=5432
DB_NAME=acme_ragdb
DB_USER=raguser
DB_PASSWORD=secure_password_here
# ===========================================
# Admin Database (shared chatdb)
# ===========================================
ADMIN_DB_HOST=localhost
ADMIN_DB_PORT=5432
ADMIN_DB_NAME=chatdb
ADMIN_DB_USER=chatuser
ADMIN_DB_PASSWORD=secure_password_here
# ===========================================
# API Keys (unique per tenant)
# ===========================================
ADMIN_API_KEY=your_admin_api_key
CHAT_API_KEY=your_chat_api_key
UPLOAD_API_KEY=your_upload_api_key
# ===========================================
# LLM Provider
# ===========================================
LLM_PROVIDER=openai
OPENAI_API_KEY=sk-proj-your-openai-key
OPENAI_MODEL=gpt-5.2
# Optional: Claude
ANTHROPIC_API_KEY=
CLAUDE_MODEL=claude-sonnet-4-20250514
# ===========================================
# Docling Service
# ===========================================
DOCLING_SERVICE_URL=http://localhost:8001
# ===========================================
# Upload Settings
# ===========================================
MAX_UPLOAD_SIZE_MB=100
# ===========================================
# CORS Allowed Origins
# ===========================================
ALLOWED_ORIGINS=https://your-site.com
# ===========================================
# Admin Dashboard
# ===========================================
ADMIN_USERNAME=admin
ADMIN_PASSWORD=your_admin_password
# ===========================================
# Debug (optional)
# ===========================================
API_DEBUG_LOGGING_ENABLED=false
API_DEBUG_LOG_PATH=/var/www/chatbot/logs/api_debug
API_DEBUG_USERNAME=admin
API_DEBUG_PASSWORD=debug_password
# ===========================================
# Endpoint Toggles (default: true if not set)
# ===========================================
# ENDPOINT_CHAT_ENABLED=true
# ENDPOINT_CUSTOMER_CHAT_ENABLED=true
# ENDPOINT_WIDGET_CONFIG_ENABLED=true
# ENDPOINT_UPLOAD_ENABLED=true
# ENDPOINT_MEDIA_INFO_ENABLED=true
# ENDPOINT_ADMIN_ENABLED=true
# ENDPOINT_API_DEBUG_ENABLED=true
Required Keys
UPLOAD_API_KEY must be set for each tenant. If empty, upload and media-info endpoints return 401 Unauthorized. Keys are auto-generated when creating tenants via the CLI.
Tenant Identification¶
The system identifies tenants from incoming requests using three methods (checked in order):
1. POST Body¶
For JSON API requests, include tenant_id in the request body:
curl -X POST https://your-domain.com/chat \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "acme-corp",
"session_id": "user_123",
"message": "What is in the report?"
}'
2. Query Parameter¶
For GET requests or form submissions, use the tenant query parameter:
3. HTTP Header¶
For admin requests or when the body can't be modified, use the X-Tenant-ID header:
curl -X GET https://your-domain.com/admin/documents \
-H "X-Tenant-ID: acme-corp" \
-H "X-API-Key: your_admin_api_key"
Priority Order¶
1. POST body (tenant_id) ← Checked first
2. GET parameter (tenant) ← Checked second
3. HTTP header (X-Tenant-ID) ← Checked last
API Usage by Tenant¶
All API endpoints require tenant identification. Here are examples for each endpoint:
Chat Endpoint¶
curl -X POST https://your-domain.com/chat \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "acme-corp",
"session_id": "user_123",
"message": "What are the quarterly results?"
}'
Upload Endpoint¶
# File upload (requires UPLOAD_API_KEY)
curl -X POST "https://your-domain.com/upload?tenant=acme-corp" \
-H "X-API-Key: your_upload_api_key" \
-F "file=@document.pdf"
# Media URL import (requires UPLOAD_API_KEY)
curl -X POST "https://your-domain.com/upload?tenant=acme-corp" \
-H "X-API-Key: your_upload_api_key" \
-F "url=https://www.youtube.com/watch?v=dQw4w9WgXcQ"
Debug RAG Endpoint¶
# List documents
curl "https://your-domain.com/debug-rag.php?tenant=acme-corp"
# Test search query
curl "https://your-domain.com/debug-rag.php?tenant=acme-corp&query=quarterly+report"
Admin Endpoints¶
# List documents
curl -X GET "https://your-domain.com/admin/documents" \
-H "X-Tenant-ID: acme-corp" \
-H "X-API-Key: your_admin_api_key"
# View chat sessions
curl -X GET "https://your-domain.com/admin/sessions" \
-H "X-Tenant-ID: acme-corp" \
-H "X-API-Key: your_admin_api_key"
API Debug Logs¶
Widget Configuration¶
Configure the chat widget for a specific tenant:
Basic Widget (chat-widget.js)¶
<script>
// Set tenant before loading widget
window.RAG_CHATBOT_TENANT = 'acme-corp';
window.RAG_CHATBOT_API = '/chat';
</script>
<script src="/chat-widget.js"></script>
Customer Widget (customer-widget.js)¶
<script>
window.RAG_CHATBOT_TENANT = 'acme-corp';
window.RAG_CHATBOT_WIDGET_KEY = 'default';
</script>
<script src="/customer-widget.js"></script>
The widget automatically includes the tenant ID in all API requests.
Admin Dashboard¶
Access the admin dashboard for a specific tenant:
The dashboard includes a tenant selector dropdown when multiple tenants exist.
Authentication
Each tenant has its own ADMIN_USERNAME and ADMIN_PASSWORD. Login credentials are tenant-specific.
CLI Scripts¶
All CLI scripts require the --tenant parameter:
Import Documents¶
Reindex Documents¶
Cleanup Chat History¶
Test Database Connection¶
Run Tests¶
php tests/test_improvements.php --tenant=acme-corp --verbose
php tests/test_file_uploads.php --tenant=acme-corp --dry-run
php tests/test_integration.php --tenant=acme-corp --cleanup
Database Setup¶
The system uses two databases:
- ragdb (per-tenant) - RAG data (documents, chunks, embeddings, chat history). Each tenant can use a separate database on a different server for data isolation.
- chatdb (shared) - Admin/widget configuration (widget settings, prompts, inquiries, tenant prompts). Shared across all tenants with
tenant_idisolation.
Create Tenant RAG Database¶
# Connect as postgres admin
sudo -u postgres psql
# Create database for tenant
CREATE DATABASE acme_ragdb;
# Create user (or use existing)
CREATE USER raguser WITH PASSWORD 'secure_password';
# Grant privileges
GRANT ALL PRIVILEGES ON DATABASE acme_ragdb TO raguser;
# Connect to the new database
\c acme_ragdb
# Enable pgvector extension
CREATE EXTENSION IF NOT EXISTS vector;
# Exit psql
\q
Import Schema¶
The shared chatdb database only needs to be set up once (see Database Setup)—all tenants share it.
# Apply chatdb schema (shared, one-time setup)
sudo -u postgres psql -d chatdb -f database/chatdb.sql
Security Considerations¶
API Key Isolation¶
Each tenant has unique API keys:
ADMIN_API_KEY- For admin dashboard and management endpointsCHAT_API_KEY- For chat endpoints (if required)UPLOAD_API_KEY- For upload and media-info endpoint protection (required)
CORS Origin Whitelist¶
Each tenant can restrict cross-origin access via ALLOWED_ORIGINS in their .env. IPs that repeatedly send unrecognized origins are rate-limited.
IP Blocklist¶
The system maintains a shared IP blocklist for abuse prevention:
- After 10 failed tenant resolution attempts, the IP is blocked for 15 minutes
- Blocklist data is stored in
/var/www/chatbot/data/ip_blocklist/
Data Isolation¶
- RAG data (documents, embeddings, chat history) is stored in a per-tenant
ragdbdatabase, which can be on a separate remote server for privacy - Admin/widget config (widget settings, prompts, inquiries) is stored in the shared
chatdbdatabase, isolated bytenant_id - No cross-tenant data access is possible
- File uploads are processed in isolated temporary directories
Troubleshooting¶
"Tenant not found" Error¶
Causes:
- Tenant doesn't exist - run
php cli/tenant.php listto verify - Missing
.envfile in tenant directory - Permission issues on tenant directory
Fix:
# Check tenant exists
php cli/tenant.php show xyz
# Check permissions
ls -la /var/www/chatbot/tenants/xyz/
# Fix permissions if needed
sudo chown -R apache:apache /var/www/chatbot/tenants/xyz/
sudo chmod 640 /var/www/chatbot/tenants/xyz/.env
"Missing tenant_id" Error¶
Causes:
- No tenant identifier in request
- Wrong parameter name used
Fix:
Ensure you're passing the tenant using one of:
- POST body:
"tenant_id": "your-tenant" - Query param:
?tenant=your-tenant - Header:
X-Tenant-ID: your-tenant
Database Connection Failed¶
Causes:
- Wrong database credentials in tenant
.env - Database doesn't exist
- PostgreSQL not running
Fix:
# Test connection
php test_db.php your-tenant
# Check credentials in .env
cat /var/www/chatbot/tenants/your-tenant/.env | grep DB_
# Verify database exists
sudo -u postgres psql -c "\l" | grep your_db_name
Best Practices¶
Naming Conventions¶
- Use lowercase alphanumeric characters and hyphens for tenant IDs
- Examples:
acme-corp,client-123,demo-tenant - Avoid special characters, spaces, or uppercase letters
Backup Strategy¶
- Back up each tenant's database separately
- Include tenant
.envfiles in configuration backups - Document tenant-specific customizations
Monitoring¶
- Monitor each tenant's database size and performance
- Track API usage per tenant
- Set up alerts for tenant-specific errors