Tenant Filtering

Multi-tenant data isolation for embedded applications.

Overview

When you embed dialektai in your multi-tenant SaaS application, you need to ensure each customer only sees their own data. Tenant filtering provides automatic row-level security by adding filters to all database queries.

When to Use Tenant Filtering

Use tenant filtering when:

Skip tenant filtering when:

Setup

1. Enable Tenant Filtering in Database Settings

In your dialektai dashboard:

  1. Go to Settings → Databases
  2. Select your database
  3. Enable "Requires Organization Scoping"
  4. Set the Tenant Filter Column (e.g., customer_id, organization_id, tenant_id)

2. Send X-Tenant-Id Header with Requests

Important: Always derive the tenant ID from your authenticated user's session. Never trust client-provided values without validation.

Usage Examples

Chat Widget Integration

<script src="https://cdn.dialektai.com/widget.js"></script>
<script>
  // Get tenant ID from authenticated user session
  const userSession = getCurrentUserSession(); // Your auth system

  DialektAI.create('#dialektai-chat', {
    apiKey: 'pk_your_api_key',
    databaseId: 'your-database-id',

    // Tenant filtering for multi-tenant isolation
    tenantId: userSession.organizationId,  // Must be a positive integer

    // Optional: Track which user is chatting
    scopeId: userSession.userId
  });
</script>

API Usage (cURL)

# Get tenant ID from your authenticated user's session
TENANT_ID=82  # From user's session/JWT

curl -X POST https://api.dialektai.com/api/v1/chat/message \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "X-Tenant-Id: $TENANT_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Show all projects",
    "database_id": "your-database-id"
  }'

API Usage (JavaScript)

// Get tenant ID from authenticated user
const user = await getCurrentUser(); // Your auth system

const response = await fetch('https://api.dialektai.com/api/v1/chat/message', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${API_KEY}`,
    'X-Tenant-Id': user.organizationId.toString(),  // Must be string of positive integer
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    message: 'Show all projects',
    database_id: 'your-database-id'
  })
});

API Usage (Python)

import requests

# Get tenant ID from authenticated user
user = get_current_user()  # Your auth system

response = requests.post(
    'https://api.dialektai.com/api/v1/chat/message',
    headers={
        'Authorization': f'Bearer {API_KEY}',
        'X-Tenant-Id': str(user.organization_id),  # Must be string of positive integer
        'Content-Type': 'application/json'
    },
    json={
        'message': 'Show all projects',
        'database_id': 'your-database-id'
    }
)

result = response.json()

How It Works

When tenant filtering is enabled:

  1. Request arrives with X-Tenant-Id: 82

  2. Validation checks:

    • Tenant ID must be a positive integer
    • Database must have requires_organization_scoping = true
    • Returns 403 Forbidden if validation fails
  3. Database query generation automatically includes tenant filter:

    SQL Database (PostgreSQL, MySQL, SQL Server):

    SELECT * FROM projects WHERE customer_id = 82
    

    NoSQL Database (MongoDB):

    db.projects.find({ customer_id: 82 })
    
  4. Results returned contain only tenant 82's data

Advanced: Additional Filtering with X-Scope-Id

For more granular access control (e.g., user-level, department-level), use the optional X-Scope-Id header:

// Multi-tenant with user-level filtering
const response = await fetch('https://api.dialektai.com/api/v1/chat/message', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${API_KEY}`,
    'X-Tenant-Id': '82',           // Organization filter
    'X-Scope-Id': 'user-john-456', // User filter
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    message: 'Show my tasks',
    database_id: 'your-database-id'
  })
});

Generated queries with both filters:

SQL Database:

SELECT * FROM tasks
WHERE customer_id = 82
  AND assigned_to = 'user-john-456'

MongoDB:

db.tasks.find({
  customer_id: 82,
  assigned_to: 'user-john-456'
})

Security Best Practices

✅ DO:

❌ DON'T:

Error Handling

403 Forbidden - Invalid X-Tenant-Id

{
  "error": "Invalid X-Tenant-Id format. Must be a positive integer."
}

Solution: Ensure tenant ID is a positive integer (e.g., 82, not "customer-82")

403 Forbidden - Missing X-Tenant-Id

{
  "error": "X-Tenant-Id header is required when organization scoping is enabled."
}

Solution: Include X-Tenant-Id header with every request

403 Forbidden - Tenant Scoping Not Enabled

{
  "error": "Organization scoping is not enabled for this database."
}

Solution: Enable "Requires Organization Scoping" in database settings

Testing

Test Valid Tenant ID

curl -X POST /api/v1/chat/message \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Tenant-Id: 82" \
  -d '{"message": "Show all data", "database_id": "123"}'

# Expected: 200 OK, data for tenant 82 only

Test Invalid Tenant ID

curl -X POST /api/v1/chat/message \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Tenant-Id: invalid" \
  -d '{"message": "Show all data", "database_id": "123"}'

# Expected: 403 Forbidden

Test Missing Tenant ID

curl -X POST /api/v1/chat/message \
  -H "Authorization: Bearer $API_KEY" \
  -d '{"message": "Show all data", "database_id": "123"}'

# Expected: 403 Forbidden (if scoping enabled)

Common Use Cases

Multi-Tenant SaaS Platform

Scenario: Project management tool with 1000 customers
Solution: Set tenant filter column to `customer_id`
          Send X-Tenant-Id from authenticated user's organization
Result:   Each customer only sees their projects

White-Label Application

Scenario: Analytics platform resold to multiple clients
Solution: Set tenant filter column to `client_id`
          Embed widget with tenantId from user session
Result:   Automatic data isolation per client

Department-Based Access

Scenario: Enterprise app with department-level access
Solution: Use X-Tenant-Id for organization, X-Scope-Id for department
          Both filters applied to all queries
Result:   Users see only their department's data within their org

Next Steps

Support

Having issues with tenant filtering?