Skip to main content

Tenant Manager

The tenant manager is a console for the people who run the platform — not for workspace users. It lists every workspace (tenant) with its plan, limits and usage, lets you change a workspace's plan and limits, suspend, reactivate or delete it, and shows the background job queues across all workspaces. Before it existed, workspaces could only be managed with SQL on the database server.

Open it at /platform/login on the web app (for example https://crm.example.com/platform/login).

Platform admins​

Platform admins are not CRM users. They live in master.platform_admins (master migration m002_platform_admins), outside every workspace, and there is no sign-up for them. Create and manage them from the server with the CLI:

TaskCommand (from apps/api)
Create an adminnpm run platform:admin -- create --email you@company.com --name "Your Name"
Reset the passwordnpm run platform:admin -- reset-password --email you@company.com
Reset the authenticator (re-enrol at next sign-in)npm run platform:admin -- reset-mfa --email you@company.com
Disable / enable an adminnpm run platform:admin -- disable --email … / enable --email …
List adminsnpm run platform:admin -- list

In production use npm run platform:admin:prod inside the api container — see Production Topology → Operational commands. create and reset-password print a generated password once; it is never stored in plain text. Every CLI action is written to the platform activity log.

Master migration

m002_platform_admins lives in apps/api/src/scripts/master-migrations.ts. In production the app's database user can't create tables in the master schema, so it must be applied as postgres — see Master-schema migrations.

Signing in​

  1. Enter the email and password.
  2. First sign-in: scan the QR code with an authenticator app (Google Authenticator, Microsoft Authenticator, 1Password…) and enter the 6-digit code. Afterwards: enter the code from the app.

Details:

  • Codes are standard TOTP (RFC 6238). A code that was already used can't be used again.
  • A session lasts 4 hours with no refresh; sign in again after that.
  • Platform tokens are signed with their own key (PLATFORM_JWT_SECRET, or a key derived from JWT_SECRET) and carry their own token type, so a workspace token never opens a platform route and a platform token never opens a workspace route.
  • In the browser the platform session is kept per tab and never touches the workspace session, so you can be signed in to a workspace and to the tenant manager at the same time.
  • Sign-in endpoints are rate limited. Every sign-in, failed sign-in and wrong code is recorded in the activity log.

Workspaces​

Workspaces lists every workspace with its status, plan and usage against its limits (users, records, storage, API calls today). Search by name or slug. Click a workspace to open it.

The workspace page shows:

  • Plan and limits — choose a plan; for each limit (users, records, storage MB, API calls per day, import rows) the plan's value, an optional override and the effective value. Leave an override blank to use the plan's value. Limits are measured, not enforced yet.
  • Daily usage (last 30 days) — from the nightly usage snapshot (00:10 UTC).
  • Activity — the tenant-manager actions taken on this workspace.
  • Suspend / Reactivate workspace. Suspending signs every user out at their next request, blocks sign-in and makes scheduled jobs skip the workspace. Its data is kept.
  • Delete workspace — only for a suspended workspace, after typing its slug to confirm. The workspace row and its database schema are removed in one transaction; stored files can optionally be removed too. This can't be undone.

Background jobs​

Background jobs (/platform/jobs) shows the queued work across all workspaces and refreshes every 15 seconds. There is a card per queue with the number of live workers and the jobs waiting, running, retrying and failed:

QueueHoldsRetry from here
Scheduled jobs (tenant-jobs)Per-workspace scheduled work: SLA scans, reminders, mailbox and calendar sync, reports…Yes
Notification delivery (notification-delivery)Notification emails, SMS, WhatsApp and chat messagesYes
Workflow runs (workflow-runs)Workflow executionsNo — retry from the failed step on the workflow's Runs tab in the workspace
Lead / account / opportunity / product importsImport filesNo — run the import again in the workspace
Account enrichmentCompany data lookupsNo — run enrichment again on the account

Open a queue to list its jobs by state, optionally for one workspace. For failed jobs you can retry one or retry all failed (where retry is allowed) and discard a job so it never runs again. Workflow runs and imports can't be retried here because repeating them blindly could create duplicate records.

Job payloads are never shown — they can hold message bodies and personal data. Only the workspace, job type, attempts, timings and error are listed. Every retry and discard is recorded in the activity log.

When no worker is running, jobs pile up under waiting; see Process roles.

Activity​

Activity is the platform audit log: sign-ins and failures, plan / limit / status changes (with the before and after values), deletions, CLI actions on platform admins, and job retries and discards. It is append-only for the application's database user.

API​

All routes are under /api/platform and require a platform token (PlatformAuthGuard), except sign-in.

MethodPathPurpose
POST/platform/auth/loginEmail + password → { step: 'enroll', otpauthUrl, mfaToken } the first time, then { step: 'verify', mfaToken }
POST/platform/auth/mfamfaToken + 6-digit code → { accessToken }
GET/platform/meThe signed-in platform admin
GET/platform/plansPlans and their limits
GET/platform/tenants · /platform/tenants/:idWorkspaces with plan, limits and usage; one workspace with daily usage and activity
PATCH/platform/tenants/:id{ plan?, status?: 'active' | 'suspended', limitsOverride? } — a null override removes it
DELETE/platform/tenants/:id{ confirmSlug, deleteFiles? } — suspended workspaces only
GET/platform/jobs · /platform/jobs/:queueQueue overview; jobs in one state (?state=failed&workspace=&limit=50 — states waiting, active, delayed, failed, completed)
POST/platform/jobs/:queue/retry-failed · /platform/jobs/:queue/:id/retryRetry failed jobs (scheduled jobs and notification delivery only)
DELETE/platform/jobs/:queue/:idDiscard a failed job
GET/platform/auditActivity log

Code: apps/api/src/modules/platform/ (API) and apps/web/src/features/platform/ (screens). Suspension is enforced in JwtStrategy, which now also requires the workspace to be active — this covers deleted workspaces too.