Tenant Subdomain Provisioning
When a new tenant is provisioned, the API automatically registers a custom subdomain for them — both on Vercel (to route traffic) and on Cloudflare (to create the DNS CNAME record). This happens concurrently, outside the MongoDB transaction, so a DNS failure never blocks tenant creation.
Full Provisioning Flow
sequenceDiagram
participant Admin as Super Admin (Dashboard)
participant API as dot-foot-factory-api
participant DB as MongoDB Atlas
participant DP as domainProvisioning.js
participant Vercel as Vercel API
participant CF as Cloudflare DNS API
Admin->>API: POST /api/v1/tenant
API->>DB: Start MongoDB transaction
DB-->>API: Session open
API->>DB: Create Tenant document
API->>DB: Create admin User document
API->>DB: Set Tenant.owner = newUser._id
API->>DB: Commit transaction
DB-->>API: Transaction committed
API->>DP: provisionSubdomain(slug)
DP->>Vercel: addDomain(subdomain) [10s timeout]
DP->>CF: createCnameRecord(subdomain, cnameTarget) [10s timeout]
Vercel-->>DP: 200 OK / 409 Already exists
CF-->>DP: 200 OK / 409 Already exists
alt Both services succeeded
DP-->>API: { vercel: success, cloudflare: success }
API->>DB: tenant.domainStatus = "active"
API-->>Admin: 201 Created
else One or both services failed
DP-->>API: { vercel: failed/success, cloudflare: failed/success }
API->>DB: tenant.domainStatus = "pending"
API-->>Admin: 207 Multi-Status (partial)
end
201 vs 207 Response Decision
flowchart TD
A[provisionSubdomain resolves] --> B{vercel.status === success?}
B -->|No| D[domainStatus = pending]
B -->|Yes| C{cloudflare.status === success?}
C -->|No| D
C -->|Yes| E[domainStatus = active]
D --> F[HTTP 207 — partial success]
E --> G[HTTP 201 — full success]
A 207 response means the tenant was created successfully in the database, but one or both DNS services failed. The tenant is fully operational — only the custom subdomain is pending.
Retry Flow
When a tenant has domainStatus: "pending", the super admin can trigger a retry from the dashboard.
sequenceDiagram
participant Admin as Super Admin (Dashboard)
participant API as dot-foot-factory-api
participant DB as MongoDB Atlas
participant DP as domainProvisioning.js
participant Vercel as Vercel API
participant CF as Cloudflare DNS API
Admin->>API: POST /api/v1/tenant/:id/retry-domain
API->>DB: Tenant.findById(id)
DB-->>API: Tenant document
alt domainStatus === "active"
API-->>Admin: 400 — Domain is already active
else domainStatus === "pending"
API->>DP: retrySubdomain(slug)
DP->>Vercel: addDomain(subdomain) [10s timeout]
DP->>CF: createCnameRecord(subdomain, cnameTarget) [10s timeout]
Vercel-->>DP: 200 OK / 409 Already exists
CF-->>DP: 200 OK / 409 Already exists
DP-->>API: ProvisioningResult
API->>DB: Update tenant.domainStatus
API-->>Admin: 200 OK with updated tenant + provisioningResult
end
Both addDomain and createCnameRecord treat 409 Already exists as success — retries are idempotent.
Architecture: Domain Provisioning Utilities
graph TD
TC[tenantController.js] -->|provisionSubdomain / retrySubdomain| DP[domainProvisioning.js]
DP -->|Promise.allSettled + 10s timeout| VC[vercelClient.js]
DP -->|Promise.allSettled + 10s timeout| CC[cloudflareClient.js]
VC -->|POST /v10/projects/:id/domains| VAPI[Vercel REST API]
CC -->|POST /zones/:id/dns_records| CFAPI[Cloudflare DNS API]
CC -->|CLOUDFLARE_ENABLED=false| NOOP[No-op mode]
Module Responsibilities
| Module | Responsibility |
|---|---|
domainProvisioning.js |
Orchestrator — runs Vercel + Cloudflare concurrently via Promise.allSettled, enforces 10s timeouts, logs failures |
vercelClient.js |
Calls Vercel REST API to add/remove domains from the project. Throws ConfigError if env vars missing |
cloudflareClient.js |
Calls Cloudflare DNS API to create/delete CNAME records. Supports no-op mode via CLOUDFLARE_ENABLED=false |
Error Handling Strategy
| Scenario | Behaviour |
|---|---|
| Vercel API timeout (>10s) | Logged as error; vercel.status = "failed"; tenant saved with domainStatus: "pending" |
| Cloudflare API timeout (>10s) | Logged as error; cloudflare.status = "failed"; tenant saved with domainStatus: "pending" |
| Vercel 409 (domain already exists) | Treated as success — idempotent |
| Cloudflare 409 (record already exists) | Treated as success — idempotent |
Missing VERCEL_API_TOKEN / VERCEL_PROJECT_ID |
ConfigError thrown; provisioning fails; tenant saved with domainStatus: "pending" |
Missing CLOUDFLARE_API_TOKEN / CLOUDFLARE_ZONE_ID |
ConfigError thrown (unless CLOUDFLARE_ENABLED=false) |
CLOUDFLARE_ENABLED=false |
Cloudflare calls are skipped entirely (no-op); only Vercel is called |
| MongoDB transaction failure | Transaction rolled back; no tenant created; domain provisioning never runs |
Failures in domain provisioning never cause the tenant creation to fail — the tenant is always persisted. The domainStatus field reflects the current state.
Admin UI: Domain Status
The DomainStatusBadge component in dot-foot-factory-admin renders the domain state inline on the Tenants page:
active— green pill showing the subdomain URLpending— amber warning banner with two actions:- Retry — calls
POST /api/v1/tenant/:id/retry-domain - Manual Setup — opens
DomainManualSetupModalwith step-by-step Vercel + Cloudflare instructions
Environment Variables
| Variable | Required | Description |
|---|---|---|
SUBDOMAIN_BASE_DOMAIN |
Yes | Base domain suffix appended to slug (e.g. .dotevolve.net) |
VERCEL_API_TOKEN |
Yes | Vercel API token with domain management permissions |
VERCEL_PROJECT_ID |
Yes | Vercel project ID to register domains against |
VERCEL_CNAME_TARGET |
Yes | CNAME target for DNS records (e.g. 6ede78a9c15a1c22.vercel-dns-017.com) |
CLOUDFLARE_API_TOKEN |
Yes* | Cloudflare API token with DNS edit permissions |
CLOUDFLARE_ZONE_ID |
Yes* | Cloudflare zone ID for the base domain |
CLOUDFLARE_ENABLED |
No | Set to false to disable Cloudflare integration (no-op mode) |
*Not required when CLOUDFLARE_ENABLED=false.
Subdomain Format
Given a tenant with slug: "acme-corp" and SUBDOMAIN_BASE_DOMAIN=".dotevolve.net":
- Subdomain:
acme-corp.dotevolve.net - Vercel: domain added to the project → traffic routed to the factory floor app
- Cloudflare: proxied CNAME record
acme-corp.dotevolve.net → cname.vercel-dns.com