Authentication
All endpoints except /signup, /login, /regions, and /errors require an API key.
Include your API key in the Authorization header:
Authorization: Bearer YOUR_API_KEY
Authentication Errors
HTTP 401
{
  "success": false,
  "error": "Authorization header required"
}
Response Format
Every API response includes a standard envelope with three fields: success, http_status, and code. Endpoint-specific fields are merged alongside.
Success Response
HTTP 200
{
  "success": true,
  "http_status": 200,
  "code": "ok",
  "tenant_id": "acme",
  "status": "ready"
}
Error Response
HTTP 404
{
  "success": false,
  "http_status": 404,
  "code": "not_found",
  "error": "Tenant not found: acme"
}
Standard Envelope Fields
FieldTypeDescription
success boolean Whether the request succeeded
http_status integer HTTP status code (mirrors the response header)
code string Machine-readable status code (e.g., ok, created, not_found)
error string Human-readable error message (only present on errors)
The http_status field always matches the HTTP response status code. Use code for programmatic error handling and error for display messages.
Error Codes
All error responses use a standard code field. Use these codes for programmatic error handling instead of parsing error messages.
CodeHTTP StatusDescription
ok 200 Request succeeded
created 201 Resource was created
bad_request 400 Invalid input or missing required fields
auth_required 401 Authorization header missing
unauthorized 401 Invalid API key
forbidden 403 Tier limit reached, wrong project, or operation not allowed for isolation level
not_found 404 Resource does not exist
conflict 409 Resource already exists, or operation conflicts with current state
rate_limited 429 Too many requests
internal_error 500 Server error
Example Error Responses
HTTP 409 — Conflict
{
  "success": false,
  "http_status": 409,
  "code": "conflict",
  "error": "Tenant 'acme' is in trash. Use POST /tenants/acme/restore or DELETE /tenants/acme?hard=true"
}
HTTP 403 — Forbidden
{
  "success": false,
  "http_status": 403,
  "code": "forbidden",
  "error": "PITR is only available for L2 (dedicated) tenants"
}
Rate Limits
Rate limits protect shared infrastructure and ensure fair usage. Limits are applied in layers — from IP-level DDoS protection to tier-based API quotas.
IP Rate Limiting

All requests are rate-limited by IP address before authentication. This protects against abuse and DDoS. Exceeding the limit repeatedly results in a temporary ban.

ParameterValueDescription
Requests per second 100 Maximum requests per second from a single IP address
Ban threshold 5 violations After 5 rate limit violations, the IP is temporarily banned
Ban duration 5 minutes Banned IPs receive 429 for all requests during this period
Tier-Based Limits

After authentication, API requests are counted against your project's tier. Backup requests have a separate hourly limit. Dedicated tenants bypass the backup rate limit since backups run on their own server.

LimitWindowScopeDescription
API requests Per minute Project Limit varies by tier. Upgrade at tenantsdb.com/billing.
Backup requests Per hour Project Limits manual backup frequency. Dedicated tenants are exempt.
Response

Rate-limited requests return HTTP 429 Too Many Requests with a Retry-After header indicating when to retry (1 second for rate limits, 300 seconds for bans).

HTTP 429
Retry-After: 1
Content-Type: application/json

{
  "success": false,
  "error": "rate limit exceeded: max 60 requests per minute"
}
IP bans apply across both the API and database proxy connections. A ban from excessive API requests also blocks proxy connections from the same IP, and vice versa.
GET /regions
List available cloud regions for dedicated tenant provisioning. This endpoint is public and does not require authentication.
Shared databases are hosted in EMEA. For region selection, create or migrate tenants to dedicated isolation.
Request
bash
curl -s https://api.tenantsdb.com/regions
Response
HTTP 200
{
  "regions": [
    {
      "zone": "eu-central",
      "label": "Europe",
      "is_default": true
    },
    {
      "zone": "ap-southeast",
      "label": "Asia Pacific"
    },
    {
      "zone": "us-east",
      "label": "US East"
    },
    {
      "zone": "us-west",
      "label": "US West"
    }
  ]
}
Use the zone value when creating dedicated tenants or migrating to a specific region. If no region is specified, the default region is used.
GET /errors
List all error codes and their HTTP status mappings. Public endpoint — no authentication required. Useful for building client libraries.
Request
bash
curl -s https://api.tenantsdb.com/errors
Response
HTTP 200
{
  "codes": {
    "ok":             { "status": 200, "description": "Request succeeded" },
    "created":        { "status": 201, "description": "Resource was created" },
    "bad_request":    { "status": 400, "description": "Invalid input or missing fields" },
    "auth_required":  { "status": 401, "description": "Authorization header missing" },
    "unauthorized":   { "status": 401, "description": "Invalid API key" },
    "forbidden":      { "status": 403, "description": "Tier limit reached or wrong project" },
    "not_found":      { "status": 404, "description": "Resource does not exist" },
    "conflict":       { "status": 409, "description": "Already exists or operation in progress" },
    "rate_limited":   { "status": 429, "description": "Too many requests" },
    "internal_error": { "status": 500, "description": "Server error" }
  }
}

POST /signup
Create a new account. Returns an API key for your first project.
Request Body
FieldTypeDescription
email string required Email address
password string required Password (min 8 characters)
project_name string optional Project name (default: "My Project")
Response
HTTP 201
{
  "success": true,
  "http_status": 201,
  "code": "created",
  "api_key": "tenantsdb_sk_a91de156...",
  "project_id": "tdb_2abf90d3"
}
POST /login
Login to existing account. Returns all projects with API keys.
Request Body
FieldTypeDescription
email string required Email address
password string required Password
Response
HTTP 200
{
  "success": true,
  "http_status": 200,
  "code": "ok",
  "projects": [
    {
      "project_id": "tdb_2abf90d3",
      "name": "Healthcare SaaS",
      "api_key": "tenantsdb_sk_a91de156..."
    }
  ]
}

Projects
Projects are the top-level organizational boundary. Each project has its own workspaces, blueprints, tenants, and API keys.
GET /projects
List all projects for the authenticated customer.
HTTP 200
{
  "success": true,
  "http_status": 200,
  "code": "ok",
  "count": 2,
  "projects": [
    {
      "project_id": "tdb_2abf90d3",
      "name": "Healthcare SaaS",
      "api_key_count": 2,
      "created_at": "2026-01-17T20:12:06Z"
    },
    {
      "project_id": "tdb_43cd4942",
      "name": "E-commerce Platform",
      "api_key_count": 1,
      "created_at": "2026-01-17T20:12:24Z"
    }
  ]
}
POST /projects
Create a new project. Returns a new API key.
Request Body
FieldTypeDescription
name string required Project name
Response
HTTP 201
{
  "success": true,
  "http_status": 201,
  "code": "created",
  "project_id": "tdb_43cd4942",
  "name": "E-commerce Platform",
  "api_key": "tenantsdb_sk_42d5be1a...",
  "proxy_password": "tdb_a9b55759ef905535",
  "message": "Project created. Save your API key - it won't be shown again!"
}
POST /projects/{id}/switch
Switch to a different project. Returns the API key for the target project. Accepts project ID or project name.
Response
HTTP 200
{
  "success": true,
  "http_status": 200,
  "code": "ok",
  "project_id": "tdb_43cd4942",
  "name": "E-commerce Platform",
  "api_key": "tenantsdb_sk_42d5be1a...",
  "proxy_password": "tdb_a9b55759ef905535"
}
DELETE /projects/{id}
Delete a project and all its resources (workspaces, blueprints, tenants, API keys).
Response
HTTP 200
{
  "success": true,
  "http_status": 200,
  "code": "ok",
  "message": "Project 'tdb_43cd4942' deleted successfully"
}

API Keys
Manage API keys for the current project.
GET /apikeys
List all API keys for the current project.
POST /apikeys
Generate a new API key. Returns HTTP 201.
DELETE /apikeys/{id}
Delete an API key.

Workspaces
Workspaces are development environments where you design your database schema. Each workspace operates in one of two modes:
tenant
Tenant Mode
Schema changes are captured as versioned blueprints. Deploy blueprints to create isolated tenant databases. This is the default mode.
control
Control Mode
A managed database with full DDL access. No blueprints, versioning, or tenants. Ideal for your application's control plane (users, billing, config).
GET /workspaces
List all workspaces. Each workspace includes a mode field (tenant or control).
Response
JSON
{
  "workspaces": [
    {
      "id": "ws_abc123",
      "name": "main",
      "mode": "tenant",
      "database": "postgresql",
      "created_at": "2025-01-15T10:30:00Z",
      "version": 3
    },
    {
      "id": "ws_def456",
      "name": "control-plane",
      "mode": "control",
      "database": "postgresql",
      "created_at": "2025-01-15T11:00:00Z"
    }
  ],
  "count": 2
}
The version field is only present for tenant mode workspaces.
POST /workspaces
Create a workspace. Automatically creates a linked blueprint (tenant mode only).
Request Body
FieldTypeDescription
name string required Workspace name. Cannot contain __ (reserved separator).
database string required PostgreSQL, MySQL, MongoDB, or Redis
mode string required tenant or control. Tenant mode uses blueprints and deployments. Control mode creates a standalone managed database.
Response — Tenant Mode
HTTP 201
{
  "success": true,
  "http_status": 201,
  "code": "created",
  "id": "myapp",
  "mode": "tenant",
  "blueprint": "myapp",
  "database": "PostgreSQL",
  "connection": {
    "host": "pg.tenantsdb.com",
    "port": 5432,
    "database": "myapp_workspace",
    "user": "tdb_2abf90d3",
    "password": "tdb_d2bf66ed7898c448"
  },
  "connection_string": "postgresql://tdb_2abf90d3:[email protected]:5432/myapp_workspace?sslmode=require"
}
Response — Control Mode

Control mode workspaces omit the blueprint field. The workspace is a standalone managed database — no blueprints, versioning, or tenant deployment.

HTTP 201
{
  "success": true,
  "http_status": 201,
  "code": "created",
  "id": "controlplane",
  "mode": "control",
  "database": "PostgreSQL",
  "connection": {
    "host": "pg.tenantsdb.com",
    "port": 5432,
    "database": "controlplane_workspace",
    "user": "tdb_2abf90d3",
    "password": "tdb_d2bf66ed7898c448"
  },
  "connection_string": "postgresql://tdb_2abf90d3:[email protected]:5432/controlplane_workspace?sslmode=require"
}
Connection Strings by Database

TLS is enabled for all database types. The connection string format varies:

DatabaseTLS ParameterExample
PostgreSQL ?sslmode=require postgresql://user:pass@host:5432/db?sslmode=require
MySQL TLS required mysql://user:pass@host:3306/db
MongoDB ?tls=true mongodb://user:pass@host:27017/db?authMechanism=PLAIN&directConnection=true&tls=true
Redis rediss:// scheme rediss://workspace:pass@host:6379/0
All connections are encrypted via TLS. PostgreSQL uses sslmode=require, MySQL TLS is configured per driver (see Connections), MongoDB uses tls=true, and Redis uses the rediss:// URI scheme.
GET /workspaces/{id}
Get workspace details including schema and connection info.
Response
JSON
{
  "id": "ws_abc123",
  "name": "main",
  "mode": "tenant",
  "database": "postgresql",
  "schema": ["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL)"],
  "schema_error": null,
  "undeployed_changes": 2,
  "connection": {
    "host": "proxy.tenantsdb.com",
    "port": 5432,
    "database": "ws_abc123",
    "username": "ws_abc123",
    "password": "••••••••"
  },
  "connection_string": "postgresql://ws_abc123:••••••••@proxy.tenantsdb.com:5432/ws_abc123?sslmode=require",
  "settings": {
    "query_timeout_ms": 30000,
    "max_rows_per_query": 1000,
    "max_connections": 10
  },
  "version": 3
}
undeployed_changes and version are only present for tenant mode workspaces.
DELETE /workspaces/{id}
Delete a workspace. For tenant mode, returns 409 Conflict if tenants are deployed to its blueprint. Control mode workspaces can always be deleted.
Response — success
JSON
{
  "message": "workspace deleted",
  "details": {
    "workspace": "main",
    "mode": "tenant",
    "versions_deleted": 3,
    "deletion_type": "full",
    "physical_db": true
  }
}
Response — 409 Conflict (tenants deployed)
JSON
{
  "message": "cannot delete workspace with deployed tenants",
  "reason": "tenants_deployed",
  "deployed_to": ["acme", "globex"],
  "action_required": "Delete all tenants first or use a different workspace"
}
POST /workspaces/{id}/queries
Run a query in the workspace dev database. DDL statements (CREATE TABLE, ALTER TABLE, etc.) are allowed and automatically tracked as blueprint versions for deployment to tenants.
Request Body
FieldTypeDescription
query string required The SQL, MongoDB, or Redis query to execute.
Response
JSON
{
  "columns": ["id", "name", "email"],
  "rows": [
    [1, "Alice", "[email protected]"]
  ],
  "row_count": 1,
  "execution_time": "8ms"
}
POST /workspaces/{id}/schema
Import schema from template, JSON, external database, or URL.
Request Body
FieldTypeDescription
source string required json, database, template, or url
template string optional Template name: ecommerce, saas, blog, fintech
tables array optional Table definitions (for JSON source, SQL databases)
collections array optional Collection definitions (for JSON source, MongoDB)
connection object optional Database connection (for database source)
url string optional URL to JSON schema (for url source)
From Template
JSON
{
  "source": "template",
  "template": "fintech"
}
From Database
JSON
{
  "source": "database",
  "connection": {
    "type": "postgresql",
    "host": "db.example.com",
    "port": 5432,
    "database": "mydb",
    "user": "admin",
    "password": "secret"
  }
}
Response
HTTP 200
{
  "success": true,
  "http_status": 200,
  "code": "ok",
  "message": "Schema imported to workspace 'myapp'",
  "source": "template",
  "created": ["users", "accounts", "transactions"],
  "skipped": [],
  "errors": [],
  "details": {
    "workspace": "myapp",
    "database_type": "PostgreSQL",
    "statements_executed": 3,
    "statements_skipped": 0,
    "statements_failed": 0,
    "statements_total": 3
  }
}
GET /workspaces/{id}/schema
Get the current schema of a workspace (tables, columns, indexes).
Response
JSON
{
  "tables": [
    {
      "name": "users",
      "columns": [
        { "name": "id", "type": "SERIAL", "nullable": false },
        { "name": "name", "type": "TEXT", "nullable": false }
      ],
      "indexes": ["users_pkey"]
    }
  ]
}
GET /workspaces/{id}/diff
Get pending DDL changes ready for deployment.
Tenant mode only. Returns 400 Bad Request for control mode workspaces (no blueprint versioning).
DELETE /workspaces/{id}/diff/{ddl_id}
Remove a DDL change from the deploy queue. Returns 404 Not Found if the DDL does not exist or has already been deployed.
Response
HTTP 200
{
  "success": true,
  "http_status": 200,
  "code": "ok",
  "message": "DDL 42 removed from deploy queue"
}
POST /workspaces/{id}/diff/{ddl_id}/revert
Revert a DDL change in the workspace by executing the reverse statement. Only works for undeployed DDLs. Supports CREATE TABLE → DROP TABLE, CREATE INDEX → DROP INDEX, ALTER TABLE ADD COLUMN → DROP COLUMN, CREATE COLLECTION → DROP COLLECTION.
Response
HTTP 200
{
  "success": true,
  "http_status": 200,
  "code": "ok",
  "message": "Reverted: DROP TABLE IF EXISTS users;"
}
Cannot revert DDLs already deployed to tenants. Create a new migration instead.
GET /workspaces/{id}/settings
Get current settings for a workspace. Available fields depend on the database type.
Response
HTTP 200
{
  "success": true,
  "http_status": 200,
  "code": "ok",
  "database_type": "PostgreSQL",
  "settings": {
    "query_timeout_ms": 30000,
    "max_rows_per_query": 10000,
    "max_connections": 100
  },
  "available_fields": ["query_timeout_ms", "max_rows_per_query", "max_connections"]
}
PostgreSQL/MySQL/MongoDB: query_timeout_ms, max_rows_per_query, max_connections. Redis: default_ttl, max_keys, patterns.
POST /workspaces/{id}/settings
Update workspace settings. Supports partial updates — only provided fields are changed.
Request Body (PostgreSQL / MySQL / MongoDB)
JSON
{
  "query_timeout_ms": 30000,
  "max_rows_per_query": 10000,
  "max_connections": 100
}
Request Body (Redis)
JSON
{
  "default_ttl": 3600,
  "max_keys": 50000,
  "patterns": {
    "cache:*": { "ttl": 300 },
    "session:*": { "ttl": 86400 }
  }
}
Response
HTTP 200
{
  "success": true,
  "http_status": 200,
  "code": "ok",
  "settings": { ... },
  "message": "Settings saved. Deploy to apply to tenants.",
  "pending_deploy": true,
  "database_type": "PostgreSQL"
}
For tenant mode, the message is "Settings saved. Deploy to apply to tenants." with pending_deploy: true. For control mode, the message is "Settings saved." with no pending deploy.
Migrating from an existing database? The data import endpoints (import-data, import-data/analyze, import-full) are covered in the Onboarding Guide →

Blueprints
Blueprints are versioned schema snapshots created from workspaces. They define what gets deployed to tenant databases.
GET /blueprints
List all blueprints.
Response
JSON
{
  "blueprints": [
    {
      "name": "main",
      "database": "postgresql",
      "workspace_id": "ws_abc123",
      "current_version": 3,
      "tenant_count": 12
    }
  ],
  "count": 1
}
GET /blueprints/{name}
Get blueprint schema and deployment status.
Response
JSON
{
  "name": "main",
  "database": "postgresql",
  "workspace_id": "ws_abc123",
  "current_version": 3,
  "schema": [
    "CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL)"
  ],
  "tenant_count": 12
}
GET /blueprints/{name}/versions
List all versions of a blueprint.
Response
JSON
{
  "versions": [
    {
      "version": 3,
      "ddl_count": 2,
      "deployed_at": "2025-02-01T10:00:00Z"
    },
    {
      "version": 2,
      "ddl_count": 1,
      "deployed_at": "2025-01-20T14:30:00Z"
    }
  ],
  "count": 2
}

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:[email protected]: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).

Deployments
Deploy blueprint schema changes to tenant databases. Runs DDL migrations across all or selected tenants.
GET /deployments
List all deployment jobs.
Response
JSON
{
  "deployments": [
    {
      "id": "dep_abc123",
      "blueprint_name": "main",
      "version": 3,
      "status": "completed",
      "tenants_total": 12,
      "tenants_completed": 12,
      "created_at": "2025-02-01T10:00:00Z"
    }
  ],
  "count": 1
}
POST /deployments
Create a new deployment.
Request Body
FieldTypeDescription
blueprint_name string required Blueprint to deploy
version string optional Version to deploy
deploy_all boolean optional Deploy to all tenants
GET /deployments/{id}
Get deployment status and progress.
Response
JSON
{
  "id": "dep_abc123",
  "blueprint_name": "main",
  "version": 3,
  "status": "in_progress",
  "tenants_total": 12,
  "tenants_completed": 8,
  "tenants_failed": 0,
  "results": [
    { "tenant_id": "acme", "status": "completed", "duration": "120ms" },
    { "tenant_id": "globex", "status": "pending" }
  ],
  "created_at": "2025-02-01T10:00:00Z"
}

Admin Query
Run data queries directly on tenant databases. DDL statements are blocked. Schema changes must go through workspaces and blueprints.
POST /admin/query
Run a query directly on one or all tenant databases.
Request Body
FieldTypeDescription
blueprint string required Blueprint name. Used to resolve database type and route to tenant databases.
query string required The SQL/MongoDB/Redis query to execute. DDL statements are blocked.
tenant_id string optional* Target a specific tenant. Provide this or all_tenants.
all_tenants boolean optional* Run query on all tenants. Provide this or tenant_id.
Either tenant_id or all_tenants: true must be provided. Not available for control mode workspaces. Use workspace query instead.
Response — single tenant
JSON
{
  "tenant_id": "acme",
  "columns": ["id", "name", "email"],
  "rows": [
    [1, "Alice", "[email protected]"]
  ],
  "row_count": 1,
  "execution_time": "12ms"
}
Response — all tenants
JSON
{
  "results": [
    {
      "tenant_id": "acme",
      "columns": ["id", "name"],
      "rows": [[1, "Alice"]],
      "row_count": 1
    }
  ],
  "total_tenants": 1,
  "execution_time": "45ms"
}
Response — DDL blocked
HTTP 400
{
  "success": false,
  "http_status": 400,
  "code": "bad_request",
  "error": "DDL not allowed on tenant databases. Deploy schema through blueprints."
}
Schema changes (CREATE TABLE, ALTER TABLE, DROP TABLE, etc.) must go through your workspace. See Schema-First Architecture.