Skip to content

4. Multi-Tenancy & Configuration

How tenants are isolated, provisioned, configured, and retired — the multi-tenancy model every entity inherits.

4.1 Overview

The platform is multi-tenant by design. Every resource — user, device, consent record, audit event, OAuth client, KYC session — belongs to exactly one tenant. This section defines the isolation model, tenant lifecycle, and cross-tenant rules. Entity schemas (tenant, user, device, …) are defined in §13 Data Model.

4.2 Data Isolation Model

Isolation Requirements:

Requirement Description
Tenant-scoped data Every data entity includes a tenant_id field. All queries are scoped to the requesting tenant.
No cross-tenant data access A tenant's API credentials can never read, modify, or search data belonging to another tenant. This includes palm identification — 1:N search scopes to the tenant's enrolled templates only.
Tenant-scoped authentication OAuth client credentials are bound to exactly one tenant. The tenant_id is embedded in the access token and validated on every request.
Tenant-scoped devices All devices (personal scanner, pos, gate, kiosk) are registered to a specific tenant. The device certificate's SAN URI identifies both the device_id and tenant_id.
Platform-level visibility Only Platform Admins can view data across multiple tenants (for operational monitoring, not business operations). All such access is audit-logged.

Enforcement Points:

Layer Mechanism
API Gateway tenant_id extracted from token/cert, injected into request context
Middleware tenant_middleware.py validates tenant_id on every request, rejects if missing or mismatched
Domain Services All repository queries include tenant_id as a mandatory filter
Database Row-level filtering on tenant_id (implementation may use RLS or application-level enforcement)

4.3 Tenant Lifecycle

Tenant States:

State Description Data Access API Access
provisioning Being set up, not yet active Write-only (setup) Disabled
active Fully operational Full Enabled
suspended Temporarily disabled (non-payment, security incident, policy violation) Read-only Disabled (returns HTTP 403, tenant_suspended)
deactivating Marked for deletion, grace period active Read-only Disabled
deleted All data permanently removed None None

Lifecycle Flow:

stateDiagram-v2
    [*] --> provisioning
    provisioning --> active
    active --> suspended
    suspended --> active
    active --> deactivating
    deactivating --> deleted
    deleted --> [*]

Suspension (Platform Admin — non-payment, security incident, policy violation): API calls return HTTP 403 tenant_suspended; data stays intact and readable (the Tenant Admin console is read-only). Reactivation restores full access.

Deletion (Platform Admin): a configurable grace period (default 30 days) holds the tenant in deactivating (read-only, API disabled); afterward all user data, palm templates (via the vendor delete API), OAuth clients, device certificates, audit logs, and consent records are permanently removed. Deletion is irreversible; the deletion audit record itself is retained at the platform level for compliance.

4.4 Tenant Provisioning

Diagram — Tenant provisioning

sequenceDiagram
    autonumber
    participant Admin as Platform Admin
    participant Console as Web Console
    participant Identity as Identity Platform
    participant DB as PostgreSQL
    Admin->>Console: Create tenant
    Console->>Identity: POST /v1/admin/tenants
    Identity->>Identity: Generate tenant_id (slug, e.g. link-wallet)
    Identity->>DB: Insert tenant (status provisioning)
    Admin->>Console: Configure settings (auth, KYC, palm vendor — see §4.5)
    Console->>Identity: PUT /v1/admin/tenants/{id}/settings
    Identity->>DB: Persist settings
    Identity->>DB: Create first Tenant Admin (email + temp password)
    Identity->>Identity: Tenant status to active
    Identity-->>Console: Tenant active

Tenant entity schema: see §13.2.

4.5 Tenant Settings

Each tenant configures capabilities individually. All settings have sensible defaults. There are no preset tenant types — each tenant is configured individually.

User ID handling is per-request: POST /v1/users accepts an optional user_id — if provided, the platform maps it; if omitted, the platform generates one. Auth signup flows (OTP, password, social) always generate platform-managed IDs.

Setting Description Default
auth_methods Enabled auth methods ["otp", "password", "google", "apple"]
kyc_required Require KYC false
kyc_provider KYC provider null
kyc_level Required KYC level null
kyc_required_for_enrollment KYC before palm false
kyc_required_for_transactions Require KYC for certain transactions / limits — [POST-MVP], §6 false
palm_provider Palm verification vendor biowave
palm_match_policy How vendor scores map to match decision (all_thresholds / majority / any) — see Section 7.4 all_thresholds
palm_duplicate_check_enabled Run pre-enrollment similarity check to detect duplicate biometrics — see Section 7.10 false
palm_duplicate_action Action on duplicate detection (reject / flag) reject
require_email_verified Require email verification before palm enrollment false
require_mobile_verified Require mobile verification before palm enrollment false
consent_required Require consent false
data_subject_rights_enabled Enable DSR false
audit_enabled Enable audit logging true

4.6 Example Configurations

Tenant Auth KYC KYC Provider Palm Vendor
Link Holdings (one tenant — Wallet, Access + sub-products are products within it) All Per-product (Wallet: full / Nafath; Access: none) Nafath X-Telcom BioWave Pass
Link Social All Optional (basic) Onfido X-Telcom BioWave Pass
InvestGlass Not enabled Not enabled - X-Telcom BioWave Pass
PartnerBank OTP Required (full) Nafath X-Telcom BioWave Pass
HealthApp All Required (full) Onfido X-Telcom BioWave Pass

4.7 Cross-Tenant Rules

Specific rules enforcing the §4.2 isolation model:

  1. No cross-tenant queries. API calls cannot specify a different tenant_id than the one in their credential. There is no "impersonation" or "on behalf of" mechanism.
  2. No cross-tenant user sharing. A user belongs to exactly one tenant; distinct integrators are distinct tenants. (Link's own first-party products share one Link Holdings tenant — §4.6.)
  3. No cross-tenant palm matching. 1:N identification searches only within the requesting tenant's enrolled templates — a palm enrolled in Tenant A will not match in Tenant B.

Tenant isolation on X-Telcom BioWave Pass. The vendor API has a single global user_id namespace, so the platform prefixes every user_id sent to the vendor with <tenant_slug>__ (e.g. link-holdings__user_456) and strips it on response. Within-tenant sharing (Wallet ↔ Access) uses the same namespace. 4. Platform Admin visibility. The sole cross-tenant exception, audit-logged (§4.2). 5. Webhook isolation. Each tenant configures its own webhook endpoints. Events from one tenant are never delivered to another tenant's endpoints.