Multi-Tenancy
Backflow supports complete tenant isolation with per-tenant configurations.
Overview
Multi-tenancy allows:
- Separate configurations per tenant
- Isolated data and resources
- Per-tenant rate limits
- Tenant-specific integrations
- Usage tracking per tenant
- Hierarchical apps (sub-tenants)
- Custom domains per tenant
Tenant Identification
Via Header
curl -X GET http://localhost:3000/api/data \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-ID: tenant-123"Via JWT Claim
{
"sub": "user-123",
"tenant_id": "tenant-123",
"role": "admin"
}Tenant Configuration
Get Config
GET /tenant/config
X-Tenant-ID: tenant-123Set Config
POST /tenant/config
X-Tenant-ID: tenant-123
Content-Type: application/json
{
"routes": [...],
"credentials": {...},
"rateLimit": {...}
}Tenant Routes
Each tenant can have custom routes:
{
"routes": [
{
"path": "/custom-endpoint",
"method": "get",
"description": "Tenant-specific endpoint",
"supabaseQueries": [{
"table": "tenant_data",
"operation": "select"
}]
}
]
}Tenant Integrations
Per-tenant API credentials using secret references:
{
"credentials": {
"stripe": {
"secretKey": "{{secret:stripe_key}}"
},
"jira": {
"baseUrl": "https://tenant.atlassian.net",
"email": "{{secret:jira_email}}",
"apiToken": "{{secret:jira_token}}"
}
}
}Security
Always use {{secret:key}} references instead of hardcoding credentials. Secrets are encrypted and isolated per tenant.
Rate Limits
Per-tenant rate limiting:
{
"rateLimit": {
"enabled": true,
"default": {
"windowMs": 60000,
"max": 1000
}
}
}Secrets Management
Tenant secrets provide secure, isolated storage for API keys and credentials with double encryption. See Tenant Secrets Reference for complete documentation.
Store Secret
POST /tenant/secrets
Authorization: Bearer <token>
{
"key": "stripe_key",
"value": "sk_live_...",
"metadata": { "provider": "stripe" }
}Reference in Config
Use {{secret:key_name}} syntax to reference stored secrets:
{
"credentials": {
"stripe": {
"secretKey": "{{secret:stripe_key}}"
}
}
}Usage Tracking
Get Usage
GET /usage/summary
X-Tenant-ID: tenant-123Response:
{
"tenant_id": "tenant-123",
"period": "2024-01",
"api_calls": 15420,
"llm_tokens": 250000,
"storage_bytes": 1073741824
}Tenant Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /tenant/config | Get tenant config |
| POST | /tenant/config | Save tenant config |
| GET | /tenant/secrets | List secret keys |
| POST | /tenant/secrets | Store secret |
| DELETE | /tenant/secrets/:key | Delete secret |
Data Isolation
Queries automatically filter by tenant:
{
"table": "orders",
"operation": "select",
"filters": [
{
"column": "tenant_id",
"operator": "eq",
"value": "{{tenant.id}}"
}
]
}Dynamic Routes
Tenant routes are registered dynamically:
- Tenant saves config via
/tenant/config - Routes are validated
- Routes become available at tenant-prefixed paths
- Requests route to tenant-specific handlers
Apps (Sub-Tenants)
Tenants can create child tenants (apps) for isolated environments.
List Apps
GET /tenant/apps
Authorization: Bearer <token>
X-Tenant-ID: parent-tenant-123Response:
{
"apps": [
{
"id": "parent-tenant-123_abc123",
"name": "Production",
"parentTenantId": "parent-tenant-123",
"planTier": "free",
"createdAt": "2024-01-15T10:00:00Z",
"isActive": true
}
]
}Create App
POST /tenant/apps
Authorization: Bearer <token>
X-Tenant-ID: parent-tenant-123
Content-Type: application/json
{
"name": "Staging Environment",
"config": {
"environment": "staging"
}
}Delete App
DELETE /tenant/apps/{appId}
Authorization: Bearer <token>
X-Tenant-ID: parent-tenant-123SDK Usage
// List apps
const apps = await bf.tenant.apps.list();
// Create app
const app = await bf.tenant.apps.create('Production', { environment: 'prod' });
// Delete app
await bf.tenant.apps.delete('app-id');Use Cases
- Environment isolation: Separate dev/staging/prod configs
- Client isolation: Create apps per client for SaaS
- Feature flags: Test features in isolated environments
Custom Domains
Configure custom domains for your tenant's API.
Default Domain
Every tenant gets a default subdomain:
{tenant-slug}.api.backflow.devList Domains
GET /tenant/domains
Authorization: Bearer <token>
X-Tenant-ID: tenant-123Add Domain
POST /tenant/domains
Authorization: Bearer <token>
X-Tenant-ID: tenant-123
Content-Type: application/json
{
"domain": "api.mycompany.com",
"type": "primary"
}Response includes verification instructions:
{
"domain": {
"domain": "api.mycompany.com",
"type": "primary",
"verified": false,
"sslStatus": "pending"
},
"verificationInstructions": {
"method": "dns",
"domain": "api.mycompany.com",
"dnsRecord": {
"type": "TXT",
"name": "_backflow.api.mycompany.com",
"value": "bf-verify-abc123xyz"
}
}
}Verify Domain
After adding the DNS record:
POST /tenant/domains/{domain}/verify
Authorization: Bearer <token>
X-Tenant-ID: tenant-123SDK Usage
// List domains
const domains = await bf.tenant.domains.list();
// Add domain
const result = await bf.tenant.domains.add('api.mycompany.com', 'primary');
console.log(result.verificationInstructions);
// Verify domain
const { verified } = await bf.tenant.domains.verify('api.mycompany.com');
// Get verification instructions
const instructions = await bf.tenant.domains.getVerificationInstructions('api.mycompany.com');
// Remove domain
await bf.tenant.domains.remove('api.mycompany.com');Best Practices
- Always include
X-Tenant-IDheader - Store sensitive credentials as secrets
- Set appropriate rate limits per tenant
- Monitor usage with analytics endpoints
- Use tenant-specific database tables or RLS
- Use apps for environment isolation
- Verify custom domains before production use