Skip to main content

Tenancy Conventions

PensionPortal.ai is a multi-tenant SaaS platform. Each tenant is a broker firm that manages one or more pension schemes on behalf of employer clients and their members. All data is strictly isolated between tenants.

Tenant Model

Tenant (Broker Firm)
  └── Employer(s)
        └── Scheme(s)
              └── Member(s)
                    └── Contribution Periods
                          └── Contribution Files / Entries
  • A brokerId identifies the top-level tenant.
  • All resources (schemes, members, contributions, documents) are owned by a broker.
  • Employers are clients of a broker. A single employer can have multiple schemes.

Tenant Resolution

The active tenant is resolved server-side from the authenticated session. There is no X-Tenant-ID header or query parameter — the session token carries the tenant context implicitly.
// Server-side (simplified):
const session = await getServerSession();
const actorContext = buildActorContext(session); // includes brokerId
// All DB queries are automatically scoped to actorContext.brokerId
This means:
  • You cannot access another tenant’s data by passing a different brokerId in the request body.
  • Attempts to do so return 403 Forbidden.
  • IDs (scheme IDs, member IDs, etc.) are globally unique UUIDs — but ownership is verified on every request.

Resource Ownership Checks

Every API handler that accepts a resource ID performs an ownership check before returning or mutating data:
GET /api/schemes/{id}
→ Server verifies scheme.brokerId === session.brokerId
→ 403 if mismatch
→ 200 + data if match
The same applies to nested resources — accessing GET /api/schemes/{schemeId}/kfh verifies the scheme belongs to the calling tenant before returning KFH records.

Cross-Tenant Isolation Guarantees

LayerMechanism
DatabaseAll queries include a brokerId WHERE clause; no raw SQL
SessionbrokerId is embedded in the JWT/session at sign-in; cannot be spoofed
APIrequireTenant() enforces ownership on all resource reads/writes
File StorageVercel Blob paths are namespaced: /{brokerId}/{schemeId}/...
Audit LogEvery action records actorId, brokerId, schemeId for forensic tracing

Employer and Scheme Scoping

Many endpoints accept employerId or schemeId as query parameters for filtering:
GET /api/members?schemeId=<uuid>&employerId=<uuid>
GET /api/contributions?schemeId=<uuid>
GET /api/documents?scheme_id=<uuid>&type=trust_deed
If you pass an ID that belongs to a different tenant, the request returns an empty result set (or 404) — not an error — to avoid leaking the existence of other tenants’ resources.

Tenant Provisioning

New tenants are provisioned by a SuperAdmin. The provisioning process:
  1. Creates the brokers record
  2. Creates the initial BrokerAdmin user account
  3. Seeds default configuration (email templates, compliance calendar defaults)
  4. Sends a welcome email via Resend
See the Tenant Provisioning Runbook for the full procedure.

Tenant Offboarding

Tenant offboarding follows a data retention policy (minimum 7 years for pension records under Irish law). See Tenant Offboarding for the full procedure including data export and deletion schedules.