Tenants
Tenants are your customers' isolated database instances. Each tenant gets its own physical database, created from a blueprint.
L1
Shared
Multi-tenant pool. Cost-effective, instant provisioning. Each tenant gets a separate database on a shared server.
L2
Dedicated
Own VM. Full physical isolation, dedicated resources. Zero-downtime migration via native replication.
List all tenants for the current project.
Response
{
"tenants": [
{
"tenant_id": "acme",
"status": "active",
"databases": [
{
"blueprint": "main",
"database_type": "postgresql",
"isolation_level": "L1"
}
],
"created_at": "2025-01-20T14:00:00Z"
}
],
"count": 1
}
Create a new tenant with database(s) from blueprint(s). Returns HTTP 201 Created.
Only available for tenant mode workspaces. Returns 400 Bad Request if the blueprint belongs to a control mode workspace.
L2 dedicated VMs require a paid plan. Free tier accounts receive
403 forbidden.
Upgrade →
Request Body
| Field | Type | | Description |
| tenant_id |
string |
required |
Tenant name (lowercase letters, numbers, underscores only). Cannot contain __. |
| databases |
array |
required |
Array of {blueprint, isolation_level, region}. Region is optional and applies to dedicated tenants only. |
Response — Shared (instant)
Shared tenants are created instantly. The status is ready and the connection string is available immediately.
{
"success": true,
"http_status": 201,
"code": "created",
"tenant_id": "acme",
"status": "ready",
"databases": [
{
"blueprint": "fintech",
"database_type": "PostgreSQL",
"isolation_level": 1,
"connection": {
"database": "fintech__acme",
"connection_string": "postgresql://tdb_2abf90d3:[email protected]:5432/fintech__acme?sslmode=require"
}
}
]
}
Tenant Connection Strings by Database
The connection string format in the response depends on the database type:
"mysql://tdb_2abf90d3:tdb_d2bf66ed7898c448@mysql.tenantsdb.com:3306/fintech__acme"
"mongodb://tdb_2abf90d3:[email protected]:27017/fintech__acme?authMechanism=PLAIN&directConnection=true&tls=true"
Redis uses a different auth scheme: tenant_id:api_key instead of project_id:proxy_password. The database field is always 0.
Response — Dedicated (async)
Dedicated tenants require a server to be provisioned. The response returns immediately with status: provisioning. Poll GET /tenants/{id} until the status becomes ready.
{
"success": true,
"http_status": 201,
"code": "created",
"tenant_id": "acme",
"status": "provisioning",
"databases": [
{
"blueprint": "fintech",
"database_type": "PostgreSQL",
"isolation_level": 2,
"region": "eu-central"
}
]
}
Do not attempt to connect until status is ready. Provisioning typically takes 1–2 minutes depending on region.
Get tenant details with all database connections.
Response
{
"tenant_id": "acme",
"status": "active",
"databases": [
{
"blueprint": "main",
"database_type": "postgresql",
"isolation_level": "L1",
"connection": {
"host": "proxy.tenantsdb.com",
"port": 5432,
"database": "acme",
"username": "acme",
"password": "••••••••"
},
"connection_string": "postgresql://acme:••••••••@proxy.tenantsdb.com:5432/acme?sslmode=require",
"version": 3
}
],
"recovery_undo": {
"available": true,
"original_host": "vm-pg-01",
"expires_at": "2025-02-01T14:00:00Z",
"expires_in": "23h 45m",
"command": "tdb tenants recover-undo acme"
},
"created_at": "2025-01-20T14:00:00Z"
}
recovery_undo is only present when a recent recovery can be reversed.
Soft-delete a tenant. Add ?hard=true for permanent deletion. Returns 409 Conflict if tenant is provisioning or migrating.
Restore a soft-deleted tenant. L1 tenants get databases recreated instantly. L2 tenants are restored from VM snapshots.
Response
{
"success": true,
"http_status": 200,
"code": "ok",
"message": "Tenant 'acme' restored successfully",
"tenant": {
"tenant_id": "acme",
"status": "ready"
}
}
Only tenants with status deleted can be restored. Returns 409 Conflict if tenant has a different status.
Suspend a tenant (block all queries). Only ready tenants can be suspended. Returns 409 Conflict if wrong status.
Resume a suspended tenant. Only suspended tenants can be resumed. Returns 409 Conflict if wrong status.
Migrate a tenant between shared and dedicated infrastructure, or change regions. Uses native database replication for zero-downtime migrations.
L2 dedicated VMs require a paid plan. Free tier accounts receive
403 forbidden.
Upgrade →
Request Body
| Field | Type | | Description |
| isolation_level |
int |
required |
Target level: 1 (shared) or 2 (dedicated) |
| blueprint |
string |
required |
Which database blueprint to migrate |
| region |
string |
optional |
Target region zone (e.g., eu-central, us-east). See GET /regions. |
Response — Level Change (L1 → L2)
{
"success": true,
"http_status": 200,
"code": "ok",
"tenant_id": "acme",
"blueprint": "fintech",
"status": "migrating",
"from": 1,
"to": 2,
"message": "Migration in progress for 'fintech'. Check tenant status for completion."
}
Response — Region Change (L2 → L2)
{
"success": true,
"http_status": 200,
"code": "ok",
"tenant_id": "acme",
"blueprint": "fintech",
"status": "migrating",
"from": 2,
"to": 2,
"change": "region",
"from_region": "eu-central",
"to_region": "us-east",
"message": "Migration in progress for 'fintech'. Check tenant status for completion."
}
Status Lifecycle
| Status | Queries | Description |
| syncing |
✓ Working |
Data is being replicated. Your application continues running normally on the original server. |
| migrating |
Paused |
Brief cutover (~2 seconds). Queries are held until routing switches. |
| ready |
✓ Working |
Migration complete. Tenant is live on the new server. Connection strings are unchanged. |
If replication encounters an issue, TenantsDB automatically falls back to a safe backup-and-restore approach. A safety backup is always taken before migration begins.
Trigger an on-demand backup to S3. Backs up all databases for the tenant.
Response
{
"success": true,
"http_status": 200,
"code": "ok",
"message": "Backup scheduled for tenant: acme",
"tenant_id": "acme",
"type": "manual",
"timestamp": "2026-02-15T14-30-00",
"paths": ["postgresql/manual/2026-02-15T14-30-00/tdb_2abf90d3_tenant_acme.sql.gz"]
}
List all backups for a tenant across all database types.
Response
{
"success": true,
"http_status": 200,
"code": "ok",
"tenant_id": "acme",
"count": 3,
"backups": [
{
"type": "manual",
"date": "2026-02-15T14-30-00",
"database_type": "postgresql",
"storage_path": "postgresql/manual/2026-02-15T14-30-00/tdb_2abf90d3_tenant_acme.sql.gz",
"size_bytes": 524288,
"created_at": "2026-02-14T03:00:00Z"
}
]
}
Restore a tenant from a backup using the exact storage path. Use GET /tenants/{id}/backups to list available paths. For point-in-time recovery, use POST /tenants/{id}/recover (L2 only).
Request Body
| Field | Type | | Description |
| storage_path |
string |
required |
Exact S3 path from backups list |
| storage_path |
string |
optional* |
Exact S3 path from backups list |
Use tdb tenants backups {id} to get the exact storage_path value.
Response
{
"success": true,
"http_status": 200,
"code": "ok",
"message": "Rollback scheduled for tenant: acme",
"tenant_id": "acme",
"status": "restoring",
"paths": ["postgresql/manual/2026-02-15T14-30-00/tdb_2abf90d3_tenant_acme.sql.gz"]
}
Point-in-time recovery. Creates a new VM from the target timestamp, preserving the original VM for 24 hours. L2 (dedicated) tenants only. Returns 403 Forbidden for L1 tenants.
Request Body
| Field | Type | | Description |
| timestamp |
string |
required |
ISO 8601 timestamp (e.g., "2026-02-03T17:10:59Z") |
Response
{
"success": true,
"http_status": 200,
"code": "ok",
"message": "Recovery initiated for tenant 'acme' to 2026-02-03T17:10:59Z. Original VM preserved for 24h.",
"tenant": {
"tenant_id": "acme",
"status": "recovering",
"target_timestamp": "2026-02-03T17:10:59Z"
}
}
Undo a point-in-time recovery by swapping back to the original VM. Available within 24 hours of recovery. L2 only.
Response
{
"success": true,
"http_status": 200,
"code": "ok",
"message": "Recovery undone for tenant 'acme'. Original VM restored.",
"tenant": {
"tenant_id": "acme",
"status": "ready"
}
}
The 24-hour undo window starts when recovery is initiated. After expiry, the original VM is destroyed.
Get deployment history for a tenant. Shows which blueprint versions have been applied.
Query Parameters
| Param | Type | | Description |
| limit |
integer |
optional |
Max results (default: all) |
Response
{
"success": true,
"http_status": 200,
"code": "ok",
"tenant_id": "acme",
"count": 3,
"deployments": [ ... ]
}
Get query execution logs.
Query Parameters
| Param | Type | | Description |
| limit |
integer |
optional |
Max results (default: 50, max: 1000) |
| offset |
integer |
optional |
Pagination offset |
| type |
string |
optional |
Filter: error, slow |
| search |
string |
optional |
Query text search |
| source |
string |
optional |
Filter by source: proxy, api |
| since |
string |
optional |
Time filter: 1h, 24h, 7d, 30d |
Response
{
"success": true,
"http_status": 200,
"code": "ok",
"tenant_id": "acme",
"logs": [
{
"query": "SELECT * FROM users WHERE active = true",
"source": "proxy",
"success": true,
"duration_ms": 4.2,
"created_at": "2026-02-15T14:30:00Z"
}
],
"count": 50,
"has_more": true
}
Get aggregated query statistics: total queries, success/failure counts, average duration, slow query count, and breakdown by source.
Response
{
"success": true,
"http_status": 200,
"code": "ok",
"tenant_id": "acme",
"total_queries": 48230,
"successful": 47891,
"failed": 339,
"by_source": {
"proxy": 45000,
"api": 3230
},
"avg_duration_ms": 12.4,
"slow_queries": 18
}
Get performance metrics (query count, latency, success rate).