ZippCRM Overview
The purpose-built compliance CRM for Indian financial regulators
ZippCRM is a comprehensive compliance-management platform designed specifically for Fintech firms, NBFCs, insurance brokers, mutual fund distributors, and other regulated entities operating under Indian financial regulators — RBI, SEBI, IRDAI, MCA, PFRDA, and IFSCA.
The system handles the full lifecycle of regulatory engagements: client onboarding with KYC/AML checks, regulatory return filing tracking, show-cause notices, penalty management, eSign workflows, and retainer billing — all within a maker-checker governance framework.
Return Filing
Track every regulatory filing deadline across RBI, SEBI, IRDAI with auto-escalation on breach.
Penalty Engine
Auto-calculate exposure, track SCN / appeal status, and manage mitigation with maker-checker approval.
eSign / DSC
Aadhaar eSign, DSC, and OTP-based signing via Leegality, Digio, Signdesk, and NSDL.
Maker-Checker
Four-eyes approval on high-risk actions: KYC changes, penalty submissions, and key document updates.
Client Portal
Secure white-labelled portal for clients to upload documents, track status, and view invoices.
Auto Workflows
Event-driven workflow engine triggers emails, tasks, and escalations automatically.
System Requirements
Minimum and recommended hardware and software specifications
Hardware (Self-Hosted)
| Component | Minimum | Recommended (Production) |
|---|---|---|
| CPU | 2 vCPU | 4 vCPU (or 8 for >50 concurrent users) |
| RAM | 4 GB | 8 GB |
| Storage | 40 GB SSD | 200 GB SSD (NVMe preferred) |
| Network | 100 Mbps | 1 Gbps with static IP |
| OS | Ubuntu 22.04 LTS / Debian 12 / RHEL 9 | |
Software Dependencies
| Package | Version | Purpose |
|---|---|---|
Python | 3.12+ | Backend runtime |
PostgreSQL | 16+ | Primary database |
Docker Engine | 24+ | Containerised deployment |
Docker Compose | v2.20+ | Multi-container orchestration |
Nginx | 1.24+ | Reverse proxy / TLS termination |
Node.js | 18+ (build only) | Frontend asset bundling (optional) |
GENERATED ALWAYS AS IDENTITY columns and JSON path operators introduced in PostgreSQL 16.
Quick Start
Get ZippCRM running in under 10 minutes using Docker
Clone the repository
git clone https://github.com/your-org/zippcrm.git cd zippcrm
Start the stack
docker compose up -d
This starts zippcrm-backend, zippcrm-db, and zippcrm-nginx. On the very first boot, the backend detects no .setup_complete flag and waits for the Setup Wizard — no manual .env editing required.
Run the Setup Wizard
Open your browser and navigate to http://localhost:3002/setup.html. The 7-step wizard guides you through all configuration — database, SMTP, license, and admin user — and writes the .env file for you. The wizard self-destructs after completion and can never be accessed again.
Launch the application
After the wizard completes, click Launch ZippCRM or navigate to http://localhost:3002. Log in with the admin credentials you created in the wizard.
Optional: Manual .env setup (advanced)
If you prefer to configure ZippCRM without the wizard — for automated deployments or CI/CD — copy the example env file and set variables directly. The wizard is skipped when DATABASE_URL is already set and a valid .setup_complete flag file exists.
cp .env.example .env # Edit .env with DATABASE_URL, SECRET_KEY, SMTP_HOST, etc. touch backend/.setup_complete # skip wizard on next boot
First-Run Setup Wizard
Interactive 7-step installer that configures ZippCRM without touching any files manually
On first boot, ZippCRM detects the absence of a backend/.setup_complete flag file and automatically redirects any request to frontend/setup.html. This wizard handles all configuration end-to-end — no SSH, no text editor, no manual environment variable management.
The wizard communicates via a set of unauthenticated /api/setup/* routes that are intercepted before the database connection is established, allowing them to function even when no database is configured yet.
| Step | Name | What It Does | Skippable? |
|---|---|---|---|
| 1 | Pre-flight Check | Runs 7 system checks: Python ≥ 3.12, psycopg3 importable, disk ≥ 500 MB, directories writable, DATABASE_URL preset, SECRET_KEY length ≥ 32, SMTP host configured. Fails with actionable notes per check. | No (must pass critical checks) |
| 2 | Database | Supports 8 deployment types: Docker Compose, Same Server, Remote Server, AWS RDS, Google Cloud SQL, Azure PostgreSQL, Neon/Supabase, and raw Connection URL (DSN). Tests the connection live before saving. Writes DATABASE_URL to .env. | No |
| 3 | Application | Sets PORTAL_URL, auto-generates a cryptographically random SECRET_KEY (48-char), and ENVIRONMENT. | No |
| 4 | Email / SMTP | Configures SMTP host, port, username, password, from address. Tests TCP reachability before saving. | Yes — configurable later in Admin → Email Config |
| 5 | License Key | Validates and activates a ZCRM-XXXX-XXXX-XXXX-XXXX license key. Without a key the Starter plan (3 seats) is used. | Yes — activatable later in Admin → License |
| 6 | Admin User | Creates the first administrator account. Password strength is enforced. Cannot be skipped — at least one admin is required to complete setup. | No |
| 7 | Complete | Writes backend/.setup_complete flag and permanently deletes frontend/setup.html. All /api/setup/* routes return 410 Gone from this point forward. | — |
Self-Destruct Mechanism
After the admin user is created and the wizard's Complete step finalizes:
# backend/setup_wizard.py — finalize_setup() _SETUP_FLAG.touch() # creates backend/.setup_complete (FRONTEND_DIR / "setup.html").unlink() # permanently deletes setup.html
Nginx's try_files $uri /index.html handles the deleted setup.html gracefully — it falls back to index.html, which checks /api/setup/status, receives a 410 Gone, and boots the main app normally. The wizard can never be re-opened after this point, even if someone navigates to the URL.
Security Notes
All /api/setup/* routes require no authentication — by design, since no admin user exists yet. They are intercepted before the standard auth middleware in do_GET / do_POST. Once .setup_complete exists, every /api/setup/* request returns 410 Gone immediately, ensuring the unauthenticated surface disappears forever after first boot.
Database Deployment Types
Step 2 supports two input modes — Field Form (individual host / port / name / user / password / SSL mode fields) and Connection URL (paste a full postgresql:// DSN). The 8 preset types auto-fill sensible defaults, hints, and SSL requirements per provider:
| Type | Default Host Example | SSL Default | Notes |
|---|---|---|---|
| 🐳 Docker Compose | postgres or db | Disabled | Service name from docker-compose.yml |
| 💻 Same Server | localhost | Disabled | PostgreSQL on the same machine as ZippCRM |
| 🖥️ Remote Server | 10.0.1.50 | Require | Separate VM or bare-metal; port 5432 must be reachable |
| ☁️ AWS RDS | mydb.xxxx.ap-south-1.rds.amazonaws.com | Require | RDS/Aurora PostgreSQL endpoint from AWS console |
| 🌐 Google Cloud SQL | Public or private IP | Require | Works with Cloud SQL Auth Proxy or direct IP |
| 🔷 Azure Database | yourserver.postgres.database.azure.com | Require | Username must be in user@servername format |
| ⚡ Neon / Supabase | ep-xxxx.us-east-2.aws.neon.tech | Require | Serverless PostgreSQL; connection string available in dashboard |
| 🔗 Connection URL | — | Via DSN param | Paste full postgresql://user:pass@host:port/db?sslmode=require |
Docker Setup
Compose file structure and service configuration
ZippCRM ships as a three-service Docker Compose stack. The configuration below is the canonical docker-compose.yml — copy and adjust for your environment.
version: "3.9"
services:
db:
image: postgres:16-alpine
container_name: zippcrm-db
restart: unless-stopped
environment:
POSTGRES_DB: ${DB_NAME:-zippcrm}
POSTGRES_USER: ${DB_USER:-zippcrm}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-zippcrm}"]
interval: 10s
retries: 5
backend:
build: ./backend
container_name: zippcrm-backend
restart: unless-stopped
env_file: .env
depends_on:
db:
condition: service_healthy
ports:
- "5000:5000"
volumes:
- ./uploads:/app/uploads
nginx:
image: nginx:1.25-alpine
container_name: zippcrm-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./frontend:/usr/share/nginx/html:ro
- ./ssl:/etc/nginx/ssl:ro
volumes:
postgres_data:
/etc/nginx/ssl/ and use the bundled nginx/nginx-ssl.conf template. Let's Encrypt via Certbot is fully supported.
Environment Variables
All configurable runtime parameters for ZippCRM
Core
The Setup Wizard writes these automatically. When configuring manually, all variables are read from the .env file in the project root at startup.
| Variable | Required | Default | Description |
|---|---|---|---|
SECRET_KEY | Required | — | 32+ char random string for session token signing. The Setup Wizard auto-generates a cryptographically secure 48-char value. |
ENVIRONMENT | Optional | development | Set to production to enable prod guards: random seed passwords, stricter CORS headers, no debug output. |
SEED_ALLOW_DEMO_PASSWORDS | Optional | 0 | Set to 1 to allow predictable demo passwords in seed data even when ENVIRONMENT=production. Never use in real production. |
PORT | Optional | 5000 | Backend HTTP port |
LOG_LEVEL | Optional | INFO | Python log level: DEBUG / INFO / WARNING / ERROR |
Database
DATABASE_URL is the primary database configuration variable — set by the Setup Wizard. The legacy DB_HOST / DB_PORT / DB_NAME / DB_USER / DB_PASSWORD individual variables are still supported for backwards compatibility but DATABASE_URL takes precedence when set.
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | Required | Full PostgreSQL connection string: postgresql://user:pass@host:5432/dbname. Append ?sslmode=require for cloud-managed databases. Written by the Setup Wizard. |
DB_HOST | Legacy | PostgreSQL host — ignored when DATABASE_URL is set |
DB_PORT | Legacy | PostgreSQL port, default 5432 |
DB_NAME | Legacy | Database name |
DB_USER | Legacy | Database username |
DB_PASSWORD | Legacy | Database password |
DB_POOL_SIZE | Optional | Connection pool size, default 10 |
Email / SMTP
| Variable | Description |
|---|---|
SMTP_HOST | SMTP server hostname (e.g. smtp.gmail.com) |
SMTP_PORT | SMTP port — 587 (STARTTLS) or 465 (SSL) |
SMTP_USERNAME | SMTP auth username / email address |
SMTP_PASSWORD | SMTP auth password or app-specific password |
SMTP_FROM | From address, e.g. compliance@yourfirm.in |
Licensing
| Variable | Description |
|---|---|
ZIPPCRM_LICENSE_KEY | Your issued license key — activates the plan tier and seat count |
LICENSE_VALIDATION_URL | Override the license validation endpoint (for air-gapped deployments) |
# ── Core ────────────────────────────────────────────────────────── SECRET_KEY=Jx9kQm2nPvYwR7tUoL5sEhDcBfAzXiGp3NqWe6yKdCuVrTlMjFbOsHa4g8Z1 ENVIRONMENT=production # SEED_ALLOW_DEMO_PASSWORDS=1 # only for dev/staging, never production # ── Database (written by Setup Wizard — supports any PostgreSQL host) ── DATABASE_URL=postgresql://zippcrm:yourpassword@db:5432/zippcrm # For cloud managed databases, append sslmode: # DATABASE_URL=postgresql://user:pass@mydb.xxxx.ap-south-1.rds.amazonaws.com:5432/zippcrm?sslmode=require # ── SMTP ────────────────────────────────────────────────────────── SMTP_HOST=smtp.zoho.in SMTP_PORT=587 SMTP_USERNAME=alerts@yourfirm.in SMTP_PASSWORD=your-app-password SMTP_FROM=ZippCRM <alerts@yourfirm.in> SMTP_SECURITY=starttls # ── Licensing ───────────────────────────────────────────────────── ZIPPCRM_LICENSE_KEY=ZCRM-XXXX-XXXX-XXXX-XXXX
Database Setup
Schema auto-migration, backups, and manual operations
Auto-Migration
ZippCRM applies all migrations automatically on startup via the init_db() function in backend/app.py. There is no separate migration tool — all schema changes use CREATE TABLE IF NOT EXISTS and ALTER TABLE … ADD COLUMN IF NOT EXISTS so restarts are safe and idempotent.
Seeding
On a fresh database, the backend seeds regulatory workflows, compliance checklists, regulatory return templates, and an initial admin user. When ENVIRONMENT=production is set, seed user passwords are randomly generated and printed once to stdout — capture them immediately.
Backup Strategy
For production deployments we recommend daily pg_dump with off-site storage (S3, Backblaze B2). An example cron using the Docker exec pattern:
# /etc/cron.d/zippcrm-backup — runs at 2:00 AM daily 0 2 * * * root docker exec zippcrm-db \ pg_dump -U zippcrm -Fc zippcrm \ > /backups/zippcrm-$(date +\%Y\%m\%d).dump
Restore from Backup
# Stop backend, drop and recreate the database, restore docker compose stop backend docker exec -i zippcrm-db dropdb -U zippcrm zippcrm docker exec -i zippcrm-db createdb -U zippcrm zippcrm docker exec -i zippcrm-db pg_restore \ -U zippcrm -d zippcrm < /backups/zippcrm-20260409.dump docker compose start backend
Email / SMTP Configuration
Outbound email setup, retry logic, and outbox monitoring
ZippCRM sends emails for deadline alerts, SLA escalations, maker-checker approvals, eSign invitations, client portal invites, and payment receipts. Email can be configured either via environment variables or via the in-app Settings → Email screen.
Delivery Behaviour
Every outbound email is inserted into the email_outbox table. The system attempts SMTP delivery immediately. If delivery fails (e.g. transient SMTP error), the email stays in status = 'queued' and the background scheduler retries it every 15 minutes — up to 3 attempts total. After 3 failures the status changes to 'failed' and operators are alerted in the Email Outbox screen.
Supported Security Modes
| Mode | Port | Description |
|---|---|---|
starttls | 587 | Plain connection upgraded to TLS via STARTTLS — recommended for most providers |
ssl | 465 | TLS from the start (SMTP over SSL / SMTPS) |
none | 25 | Unencrypted — only for internal relay on a trusted network |
Email Retry Engine
Automatic queuing, retry, and failure handling for outbound email
ZippCRM queues all outbound emails in the email_outbox PostgreSQL table and retries failed sends automatically via the background scheduler. This ensures emails are never silently lost when the SMTP server is temporarily unreachable.
email_outbox Table
| Column | Type | Description |
|---|---|---|
id | SERIAL PK | Auto-increment primary key |
to_email | TEXT | Recipient email address |
subject | TEXT | Email subject line |
body | TEXT | Plain-text body |
html_body | TEXT | Optional HTML body |
status | TEXT | queued → sent or failed |
retry_count | INTEGER | Number of send attempts made so far (max 3) |
sent_at | TIMESTAMPTZ | Timestamp of the last send attempt (used for minimum 5-minute retry spacing) |
error_message | TEXT | SMTP error from the last failed attempt |
created_at | TIMESTAMPTZ | When the email was originally queued |
Retry Logic
The background scheduler calls _retry_queued_emails() every 5 minutes. It picks up any email with status = 'queued' whose last attempt was more than 5 minutes ago, attempts the SMTP send, and updates the record:
# Retry logic summary — backend/app.py MAX_RETRIES = 3 MIN_RETRY_AGE = timedelta(minutes=5) # Picks up: status='queued' AND (sent_at IS NULL OR sent_at < now() - 5min) # On success: status='sent' # On failure, retry_count < 3: retry_count += 1, sent_at = now() # On failure, retry_count >= 3: status='failed'
| Scenario | Behaviour |
|---|---|
| SMTP server temporarily down | Email retried up to 3 times, 5 minutes apart. Status becomes failed after 3 attempts. |
| Invalid recipient address | SMTP error captured in error_message. Retries will also fail — marks failed after 3 attempts. |
| SMTP not configured | Send is skipped (no-op). Email stays queued indefinitely until SMTP is configured. |
| Server restart | Queued emails survive restarts — they remain in PostgreSQL and are picked up on the next scheduler cycle. |
RBI Module
Reserve Bank of India compliance workflows
The RBI module covers NBFC (Non-Banking Financial Companies), payment aggregators, and prepaid payment instruments. It tracks licence validity, capital adequacy requirements, and all periodic returns mandated under the RBI Master Directions.
Supported RBI Return Types
| Return Code | Frequency | Description |
|---|---|---|
NBFC-NBS1 | Quarterly | Balance Sheet items (Deposits, Borrowings, Investments) |
NBFC-NBS2 | Quarterly | Statutory Liquid Assets computation |
NBFC-ALM | Half-Yearly | Asset-Liability Management returns |
NBFC-ANN | Annual | Audited Balance Sheet and P&L filing |
PA-VOL | Monthly | Payment Aggregator transaction volume report |
PPI-MONTHLY | Monthly | Prepaid Payment Instrument outstanding balances |
SEBI Module
Securities and Exchange Board of India workflows
Covers Investment Advisers (IA), Research Analysts (RA), Portfolio Managers (PMS), Alternative Investment Funds (AIF), and Stock Brokers. The module tracks SEBI SCORES complaint responses, BASL/AMFI disclosures, and annual compliance certificates.
| Entity Type | Licences Tracked | Key Returns |
|---|---|---|
| Investment Adviser | IA Registration | Half-yearly networth certificate, client audit |
| Research Analyst | RA Registration | Annual compliance report, SCORES responses |
| Portfolio Manager | PMS Certificate | Monthly NAV disclosure, quarterly PMLA report |
| AIF | Category I/II/III | Quarterly investor report, annual audit |
IRDAI Module
Insurance Regulatory and Development Authority workflows
Covers Insurance Brokers, Corporate Agents, Web Aggregators, and TPAs. Tracks license renewal cycles (3-year for brokers), IRDAI Bima Sugam portal filings, and grievance turnaround SLAs.
| Entity | Licence Cycle | Key Obligations |
|---|---|---|
| Insurance Broker | 3 years | Networth certificate, FIU compliance, broker exam renewal |
| Corporate Agent | 3 years | Specified Person training, product tie-up disclosure |
| Web Aggregator | 3 years | Website audit, comparative product accuracy |
| TPA | 3 years | Claims TAT report, hospital network update |
MCA / ROC Module
Ministry of Corporate Affairs and Registrar of Companies filings
Handles annual ROC filings (MGT-7, AOC-4), board resolution tracking, authorised signatory management with DSC validation, and NCLT / NCLAT matter tracking. All statutory filings are tracked against MCA21 deadlines with auto-breach detection.
API Overview
RESTful JSON API for all ZippCRM operations
ZippCRM exposes a RESTful API at /api/. All endpoints return JSON and accept JSON request bodies. The API is the same interface used by the ZippCRM frontend — there is no separate "public API" layer.
Base URL
https://your-domain.com/api
Response Format
Successful responses return HTTP 200 (or 201 for creation) with a JSON body. Errors return 4xx/5xx with a body of {"error": "…"}.
{
"id": 42,
"clientName": "HDFC AMC Ltd",
"gstin": "27AABCH1234F1Z5",
"status": "active",
"createdAt": "2026-01-15T10:30:00+05:30"
}
Authentication
Session token, TOTP, and rate limiting
ZippCRM uses HTTP-only session cookies. After login, the server issues a session_token cookie. All subsequent API requests are authenticated via this cookie. TOTP two-factor authentication is available for all users and required for admin accounts.
Login
POST /api/auth/login
Content-Type: application/json
{
"email": "admin@yourfirm.in",
"password": "your-password"
}
Rate Limits
| Endpoint Group | Limit | Window |
|---|---|---|
| Auth endpoints (login, TOTP, forgot-password) | 5 requests | 60 seconds per IP |
| All other API endpoints | 100 requests | 60 seconds per IP |
HTTP 429 Too Many Requests with a Retry-After header.
Clients & Projects API
Core entity endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/clients | List all clients (paginated, filterable by regulator/status) |
POST | /api/clients | Create a new client (validates GSTIN checksum, PAN, CIN) |
GET | /api/clients/{id} | Get a single client |
PATCH | /api/clients/{id} | Update client fields |
GET | /api/projects | List projects (filter by client, status, regulator) |
POST | /api/projects | Create a project with SLA and workflow assignment |
GET | /api/projects/{id} | Get project details with SLA timeline |
Compliance API
Return filings, penalties, SCN, and eSign
| Method | Path | Description |
|---|---|---|
GET | /api/return-filings | List all return filings with due/overdue status |
PATCH | /api/return-filings/{id} | Update filing status (filed, overdue, breached) |
GET | /api/penalty-register | Penalty register with exposure calculations |
POST | /api/penalty-register/recalculate | Force-refresh penalty exposure estimates |
POST | /api/documents/{id}/esign | Initiate an eSign request for a document |
POST | /api/esign/{id}/send-invitations | Send signing invitations to all signers |
POST | /api/esign/{id}/webhook | Receive status callback from eSign provider |
Webhooks
Inbound callbacks for eSign providers and automation engines
ZippCRM accepts inbound webhooks from eSign providers (Leegality, Digio, Signdesk, emudhra, NSDL) to update signature status. The endpoint is provider-agnostic — the provider sends a signed callback and ZippCRM maps it to the esign_requests record via provider_ref.
POST /api/esign/{id}/webhook
X-Provider-Signature: <hmac-sha256-of-body>
Content-Type: application/json
{
"status": "completed",
"providerRef": "LEG-20260409-1234",
"signedAt": "2026-04-09T14:22:00+05:30"
}
ZippCRM License Model
Tier-based SaaS licensing with feature and seat gating
ZippCRM is available in four tiers designed for the scale of your compliance practice — from boutique advisories handling a handful of clients to large firm-of-firms managing hundreds of regulated entities.
Each tier is an annual subscription. Pricing is denominated in INR and billed per seat (named operator user). Client Portal users do not consume seats.
- Up to 25 clients
- 2 regulators (choose any 2)
- Return filing tracker
- Basic penalty register
- Client portal (read-only)
- Email notifications
- Maker-Checker
- eSign integration
- Workflow engine
- API access
- Dedicated support
- Up to 100 clients
- 5 regulators
- Return filing tracker
- Full penalty engine
- Client portal (full)
- Email + WhatsApp alerts
- Maker-Checker (2-level)
- eSign (manual + OTP)
- Workflow engine
- API access
- Dedicated support
- Up to 500 clients
- All regulators (RBI, SEBI, IRDAI, MCA, PFRDA, IFSCA)
- Return filing tracker
- Full penalty engine + SCN
- Client portal + white-label
- Email + WhatsApp + SMS
- Maker-Checker (n-level)
- eSign (Aadhaar + DSC)
- Workflow engine
- REST API access
- Dedicated support
- Unlimited clients
- All regulators
- Everything in Professional
- COSMOS / XBRL integration
- On-premises / private cloud
- Custom workflows
- SSO (SAML 2.0 / OIDC)
- Dedicated support & SLA
- Custom data residency
- Annual compliance audit
License Keys
Activation, validation, and key management
Key Format
ZippCRM license keys follow the format ZCRM-XXXX-XXXX-XXXX-XXXX where each segment is a Base-36 encoded cluster of plan metadata (tier, seat count, expiry epoch, and HMAC checksum).
Activation
Set the ZIPPCRM_LICENSE_KEY environment variable before starting the backend, or enter it in Settings → License → Activate Key. The backend validates the key cryptographically on startup and every 24 hours thereafter.
Offline / Air-Gapped Validation
For Enterprise deployments without internet access, ZippCRM supports offline activation. Contact support to receive an offline license bundle (.zlic file) that encodes the full license claims without calling the validation endpoint.
# Example: reading license claims from environment at startup from licensing import validate_license, LicenseClaims claims: LicenseClaims = validate_license( key=os.environ["ZIPPCRM_LICENSE_KEY"], product="zippcrm", ) # claims.tier → "professional" # claims.max_seats → 30 # claims.valid_until → datetime(2027, 4, 9) # claims.features → {"maker_checker", "esign_aadhaar", "workflow_engine", ...}
Grace Period
If the validation endpoint is unreachable (network issues), ZippCRM operates in a 7-day grace period. After 7 days without a successful validation, the system enters read-only mode until connectivity is restored or an offline bundle is applied.
Feature Gating
How plan tiers control access to modules and capabilities
Every API endpoint and frontend module checks the active license claims before rendering or executing. Gating is enforced at three layers:
Backend middleware
The require_feature("esign_aadhaar") decorator on API routes returns HTTP 402 Payment Required with a clear upgrade message if the active plan does not include the feature.
Frontend plan guard
The JS layer calls GET /api/license/claims on boot and stores the feature set in memory. UI buttons for locked features show a lock icon and open an upgrade modal instead of executing the action.
Seat count enforcement
When creating a new operator user, the backend checks current active user count against claims.max_seats. Exceeding the seat limit returns HTTP 402. Client portal users are never counted as seats.
| Feature Flag | Starter | Growth | Pro | Enterprise |
|---|---|---|---|---|
maker_checker | — | ✓ (2-level) | ✓ (n-level) | ✓ |
esign_aadhaar | — | — | ✓ | ✓ |
esign_dsc | — | — | ✓ | ✓ |
workflow_engine | — | — | ✓ | ✓ |
api_access | — | — | ✓ | ✓ |
sso_saml | — | — | — | ✓ |
cosmos_xbrl | — | — | — | ✓ |
white_label_portal | — | — | ✓ | ✓ |
whatsapp_alerts | — | ✓ | ✓ | ✓ |