Skip to content

Subdomain Provisioning

Each tenant gets a dedicated subdomain per assigned app. Provisioning is handled exclusively by dot-portal-api.

Subdomain Pattern

{tenant-slug}.{app-base-domain}

Examples:
  acme-corp.floorix.net
  acme-corp.govnix.net

The Demo Tenant (dotevolve) uses the App Base Domain directly and is never provisioned.

Provisioning Flow

flowchart TD
    A[Tenant created / payment confirmed] --> B[provisionTenantApps]
    B --> C{For each app slug}
    C --> D[Vercel API\nRegister subdomain]
    C --> E[Cloudflare API\nCreate CNAME record]
    D --> F{Both succeed?}
    E --> F
    F -->|Yes| G[Add origin to app CORS\nSet domain_status: active]
    F -->|No| H[Set domain_status: pending\nReturn 207 Partial Content]
    G --> I[Create alert: subdomain_provisioned]
    H --> J[Create alert: subdomain_failed]

Retry

A pending subdomain can be retried from the Admin Portal:

POST /api/v1/tenants/:tenantId/apps/:appSlug/retry-domain

Deprovisioning

When a tenant is deleted, all subdomains are deprovisioned:

  • Remove subdomain from Vercel project
  • Delete Cloudflare CNAME record
  • Remove origin from app CORS policy
  • Failures are logged but do not abort the deletion
  • 404 responses from Vercel/Cloudflare are treated as already-removed

App Registry

The App Registry is an environment-level config in dot-portal-api:

{
  'floorix': {
    slug: 'floorix',
    baseDomain: 'floorix.net',
    vercelProjectId: process.env.FLOORIX_VERCEL_PROJECT_ID,
    corsEndpoint: process.env.FLOORIX_CORS_ENDPOINT,
    allowSelfSignup: true,
  },
  'govnix': {
    slug: 'govnix',
    baseDomain: 'govnix.net',
    vercelProjectId: process.env.DOT_COS_VERCEL_PROJECT_ID,
    corsEndpoint: process.env.DOT_COS_CORS_ENDPOINT,
    allowSelfSignup: true,
  },
}

CORS Origin Management

dot-portal-api exposes an internal endpoint that returns all active domains for an app:

GET /api/v1/internal/cors-origins/:appSlug
x-service-secret: <secret>

floorix-api fetches this list on startup and refreshes it periodically, replacing the old MongoDB GlobalConfig collection.