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.
GET /tenants
List all tenants for the current project.
Response
JSON
{
  "tenants": [
    {
      "tenant_id": "acme",
      "status": "active",
      "databases": [
        {
          "blueprint": "main",
          "database_type": "postgresql",
          "isolation_level": "L1"
        }
      ],
      "created_at": "2025-01-20T14:00:00Z"
    }
  ],
  "count": 1
}
POST /tenants
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
FieldTypeDescription
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.

HTTP 201
{
  "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:

PostgreSQL
"postgresql://tdb_2abf90d3:[email protected]:5432/fintech__acme?sslmode=require"
MySQL
"mysql://tdb_2abf90d3:tdb_d2bf66ed7898c448@mysql.tenantsdb.com:3306/fintech__acme"
MongoDB
"mongodb://tdb_2abf90d3:[email protected]:27017/fintech__acme?authMechanism=PLAIN&directConnection=true&tls=true"
Redis
"rediss://acme:[email protected]:6379/0"
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.

HTTP 201
{
  "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 /tenants/{id}
Get tenant details with all database connections.
Response
JSON
{
  "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.
DELETE /tenants/{id}
Soft-delete a tenant. Add ?hard=true for permanent deletion. Returns 409 Conflict if tenant is provisioning or migrating.
POST /tenants/{id}/restore
Restore a soft-deleted tenant. L1 tenants get databases recreated instantly. L2 tenants are restored from VM snapshots.
Response
HTTP 200
{
  "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.
POST /tenants/{id}/suspend
Suspend a tenant (block all queries). Only ready tenants can be suspended. Returns 409 Conflict if wrong status.
POST /tenants/{id}/resume
Resume a suspended tenant. Only suspended tenants can be resumed. Returns 409 Conflict if wrong status.
PATCH /tenants/{id}/isolation
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
FieldTypeDescription
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)
HTTP 200
{
  "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)
HTTP 200
{
  "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
StatusQueriesDescription
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.
POST /tenants/{id}/backup
Trigger an on-demand backup to S3. Backs up all databases for the tenant.
Response
HTTP 200
{
  "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"]
}
GET /tenants/{id}/backups
List all backups for a tenant across all database types.
Response
HTTP 200
{
  "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"
    }
  ]
}
POST /tenants/{id}/rollback
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
FieldTypeDescription
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
HTTP 200
{
  "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"]
}
POST /tenants/{id}/recover
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
FieldTypeDescription
timestamp string required ISO 8601 timestamp (e.g., "2026-02-03T17:10:59Z")
Response
HTTP 200
{
  "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"
  }
}
POST /tenants/{id}/recover-undo
Undo a point-in-time recovery by swapping back to the original VM. Available within 24 hours of recovery. L2 only.
Response
HTTP 200
{
  "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 /tenants/{id}/deployments
Get deployment history for a tenant. Shows which blueprint versions have been applied.
Query Parameters
ParamTypeDescription
limit integer optional Max results (default: all)
Response
HTTP 200
{
  "success": true,
  "http_status": 200,
  "code": "ok",
  "tenant_id": "acme",
  "count": 3,
  "deployments": [ ... ]
}
GET /tenants/{id}/logs
Get query execution logs.
Query Parameters
ParamTypeDescription
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
HTTP 200
{
  "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 /tenants/{id}/logs/stats
Get aggregated query statistics: total queries, success/failure counts, average duration, slow query count, and breakdown by source.
Response
HTTP 200
{
  "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 /tenants/{id}/metrics
Get performance metrics (query count, latency, success rate).