Skip to main content

Platform & Tenant Administration (PLT)

Module code PLT · Release R0 (registry, provisioning, plans, flags), R1 (billing, statutory packs), R2 (dedicated-cell automation starts), R4 (full automation) · Packaging Core, operated by Spyrl staff · Requirements FR-PLT-*, BR-PLT-* in the PRD and BRD · Architecture architecture sections 3, 4, 9, 14 and 15

1. Overview and scope​

PLT is the control plane: a separate NestJS service with its own PostgreSQL database that is the system of record for who the tenants are, what they bought and where they run. Tenant users never see it directly. Spyrl staff use it through the platform admin console. The application plane calls it (through a cached SDK) to resolve tenants, check entitlements and evaluate feature flags.

In scope: tenant registry and lifecycle; self-serve trial sign-up; idempotent provisioning; cell registry and placement; plans, add-ons, entitlements and overrides; feature flags; metering and Razorpay billing; version rollout waves; statutory reference packs; break-glass support access; tenant export and deletion; health scores.

Out of scope: anything inside a tenant's HR data (owned by the app-plane modules).

2. Learned from the reference product​

Source: Spryple analysis, §3.3, §20 and §21.1.

  • [Observed] Every login asks for a Company Code, on web and in all apps (SP-X-10).
  • [Inferred] This, plus hash-routed URLs on shared raw IPs in screenshots, suggests a single shared multi-tenant deployment with no per-tenant addressing (§21.1).
  • [Observed] Spryple's "Super Admin" is a tenant role (the company owner), not a platform operator. No platform console is documented (§3.2).
  • [Observed] A self-serve trial exists at a separate sign-up URL (§21.1).
  • [Observed] An implementation team supplies templates (e.g. the attendance Excel) during onboarding (§3.2), so onboarding is partly vendor-driven.
  • [Inferred] Pricing tiers and per-module licensing could not be analysed from public docs (§20, question 7).

3. Our design decisions​

IDDecision [Proposed]Reason
D-PLT-01Separate control-plane service and database.Tenancy, billing and rollout must survive any single cell's failure; least privilege.
D-PLT-02Hybrid cells: pooled by default, dedicated on demand; same images and migrations.Low cost for SMEs, isolation for enterprise, one release train.
D-PLT-03Tenants are addressed by an immutable slug (acme.spyrl.app) plus a human-friendly company code for mobile.Clear routing; the company code is familiar to reference-product users.
D-PLT-04Entitlements (plan + add-ons + overrides) are separate from feature flags (rollout and kill switches).Commercial rules and engineering rollout change for different reasons.
D-PLT-05Statutory rates are published centrally as versioned statutory packs with effective dates; tenants inherit them or accept them explicitly.One team keeps EPF, ESI, PT, LWF and TDS tables correct for every tenant.
D-PLT-06Support access is break-glass only: time-boxed, reasoned, consented where the plan requires it, read-only by default and visible to the tenant.Trust and DPDP accountability.
D-PLT-07Billing never blocks payroll processing inside the payroll window without senior approval.A missed salary is worse than a late invoice.

4. Domain model​

Control-plane tables have no module prefix.

5. State machines​

5.1 Tenant lifecycle​

Suspended is read-only for users; payroll exports remain available. Churned keeps a data-export window (30 or 90 days per contract).

5.2 Support access grant​

6. Key business logic​

6.1 Provisioning (idempotent steps)​

provision(tenantRequest):
step 1 validate slug and company code (reserved words, uniqueness)
step 2 choose cell = placeCell(plan, region)
step 3 insert tenant row in the control plane (status Provisioning)
step 4 in the cell: create tenant root rows, seed reference data
(roles from templates, leave-type templates, components and a default
structure, holiday template, statutory pack acceptance = INHERIT)
step 5 create owner user and send an invite link (72 h, single use)
step 6 register domain or confirm wildcard; custom domain stays Pending
step 7 mark Trial, start metering, emit plt.tenant.provisioned
each step records completion in provisioning_job.step_no; a retry resumes there

6.2 Cell placement​

placeCell(plan, region):
if plan requires a dedicated cell: create one (Terraform module) or use the contracted cell
candidates = pooled cells in region with status Open
exclude cells over any threshold: tenants > 300, active employees > 30000,
DB CPU at monthly peak > 60%, DB size > 500 GB
return the candidate with the lowest active_employee_count
if none: raise capacity alert and open a new pooled cell

6.3 Entitlement check (app plane SDK)​

entitled(tenant, feature_key):
e = cache.get(tenant) # TTL 60 s, invalidated by plt.entitlements.changed
value = override(feature_key) or addon(feature_key) or plan(feature_key)
return value # boolean or a numeric limit
limit check for employees:
warn at 100% of employee_limit, soft block new hires at 105%, never block payroll runs

6.4 PEPM billing​

billable_employees(month) = max over days in month of usage_daily(metric = ACTIVE_EMPLOYEES)
amount = max(plan.min_monthly_fee, billable_employees * plan.pepm_price)
+ sum(addon quantity * addon price) + metered usage (SMS, WhatsApp, tracked users, storage)
GST is added on the invoice.

7. Configuration​

SettingTypeDefaultDescription
trial.daysinteger14Trial length (14 to 30)
trial.sample_databooleantrueSeed a demo company on sign-up
billing.grace_daysinteger7Past-due grace before suspension
billing.export_window_daysinteger30Days after churn to export data
entitlement.soft_limit_percentinteger105Employee count before new hires are blocked
support.consent_requiredbooleanplan-basedEnterprise and Professional require owner consent
support.max_grant_hoursinteger8Maximum duration of a grant
rollout.wave_soak_hoursarray of integer[24, 48]SLO-green time before the next wave
statutory_pack.modeenumINHERITINHERIT or ACCEPT_EXPLICITLY per tenant
cell.thresholdsobjectsee 6.2Capacity limits for pooled cells

8. Screens by persona​

PersonaScreens
platform_adminTenant list and detail (plan, usage, cell, health score), suspend and reactivate, trial extension, export and deletion, cells and capacity, feature flags, statutory pack publishing
billing_adminPlans and prices, subscriptions, invoices, dunning queue, credit notes
release_managerCell versions, rollout waves, maintenance windows, release notes
support_engineerRequest access, active grants, tenant-visible access log
org_owner (tenant side)Subscription page (plan, usage, invoices, add-ons), support-access consent prompt and log, data export request
ProspectSelf-serve sign-up (company, owner, slug, plan, mobile OTP)

9. Events​

EventConsumers
plt.tenant.provisionedApp cell seeding confirmation, NTF (welcome), CRM of Spyrl sales
plt.tenant.activatedBilling, NTF
plt.tenant.suspendedApp plane (read-only mode), NTF
plt.tenant.reactivatedApp plane
plt.tenant.churnedExport job, NTF
plt.tenant.deletedCell purge job, deletion certificate
plt.entitlements.changedApp-plane SDK cache invalidation, MOB navigation refresh
plt.flag.changedSDK cache invalidation
plt.statutory_pack.publishedPAY (new rate versions), NTF (payroll managers)
plt.support_access.grantedIAM (temporary session), tenant audit log
plt.cell.version_changedRelease notes, monitoring

Consumed from cells: daily usage snapshots (emp.employee.joined, ext.lwd.reached aggregates), pay.run.locked (health score), error-rate metrics.

10. API surface​

Control-plane APIs are internal; public resolution is the only anonymous endpoint. Detailed in API endpoints.

ResourceMain operations
/public/tenants/resolve?code=GET company code to API base URL and branding (mobile)
/public/signupPOST self-serve trial
/cp/v1/tenantsCRUD, POST /:id/suspend, /:id/reactivate, /:id/export, /:id/delete
/cp/v1/cellsCRUD, capacity, versions
/cp/v1/plans, /cp/v1/tenants/:id/addons, /cp/v1/tenants/:id/overridesCRUD
/cp/v1/flagsCRUD, rules
/cp/v1/statutory-packsPOST draft, POST /:id/publish
/cp/v1/support-accessPOST request, POST /:id/consent, POST /:id/revoke
/v1/plt/subscription (tenant side)GET plan, usage, invoices; POST change plan or add-ons

11. Permissions​

Platform roles are separate from tenant roles and use the control-plane identity provider with mandatory MFA.

PermissionRoles
plt.tenant.read, plt.tenant.manageplatform_admin
plt.tenant.suspendplatform_admin, billing_admin
plt.billing.managebilling_admin
plt.cell.manage, plt.rollout.managerelease_manager, platform_admin
plt.flag.manageplatform_admin, release_manager
plt.statutory_pack.publishplatform_admin (two-person review)
plt.support_access.requestsupport_engineer
plt.support_access.consent@ORGorg_owner (tenant side)
plt.subscription.manage@ORGorg_owner (tenant side)

12. Reports​

ReportContents
Tenant healthSetup completion (go-live readiness), DAU/MAU, punches per day, last payroll run status, open tickets
Revenue and usageMRR, PEPM by plan, add-on attach rate, metered usage
Trial funnelSign-ups, activated, converted, by source
Cell capacityTenants, employees, DB CPU and size, p95 latency per cell
Support access logEvery grant with reason and actions (also visible to the tenant)

13. Release and priority​

CapabilityReleaseMoSCoW
Tenant registry, slug and company code, provisioning, one pooled cellR0Must
Plans, entitlements SDK, feature flags, admin console v1R0Must
Support access with auditR0Must
Razorpay billing, metering, dunning, GST invoicesR1Must
Statutory packs with publishing and acceptanceR1Must
Tenant export and deletion certificateR1Must
Custom domainsR2Should
Dedicated-cell provisioning via TerraformR2Should
Wave rollout orchestratorR2Should
Health scores and customer-success viewsR2Could
Cell mobility tool (move a tenant between cells)R4Should

14. Open questions​

  1. Final plan prices and the Lite plan limits need commercial validation (ADR-013 is Proposed).
  2. Do we let channel partners (CA firms, payroll bureaus) manage several tenants from one login? This would need a partner role in PLT.
  3. Should statutory packs be accepted per legal entity or per tenant? The default is per tenant.
  4. How long do we keep a deleted tenant's backups (they age out of PITR and snapshots)? This must be stated in the deletion certificate.