Contact: mail2ganesh.cse@gmail.com
GitHub: mail2ganeshcse/LOS-System-Opensource
Sahaay LOS is an India-first, open-source Loan Origination System (LOS) starter for loan officers, processors, underwriters, compliance teams, closers, and operations administrators. It provides a modern React and FastAPI foundation for digital lending workflows, customer onboarding, application processing, document management, underwriting, compliance, notifications, and AI-assisted review.
The current release provides a polished, responsive React workspace and a typed FastAPI service boundary. It uses deterministic demonstration data so the entire product can be explored without external credentials or a running database.
Sahaay LOS is designed as a modular, self-hostable LOS system for banks, NBFCs, fintech teams, and lending technology developers. The repository demonstrates an end-to-end loan origination workflow with independently structured APIs, optional third-party integrations, database migration guidance, user management, S3-compatible document storage, and configurable AI models. It can also serve as a reference implementation for teams evaluating open-source digital lending and underwriting software.
Important: This repository is an application starter, not legal, compliance, underwriting, or lending advice. RBI/NHB requirements, policy rules, disclosures, and integrations must be reviewed by qualified Indian lending and compliance professionals before production use.
- Responsive team login page
- Email and password validation
- Password visibility control
- Remember-device selection
- Searchable user directory with editable profiles, roles, branches, status, and authentication ownership
- Working sidebar My Profile entry with session-safe profile updates when the API is offline
- Dedicated Administrator dashboard with user search, role/status/authentication filters, access-governance queues, invitations, profile editing, suspension/reactivation, and recovery controls
- Invitation flow for LOS-managed and SSO-managed accounts
- Single-use, expiring reset links for LOS-managed accounts
- Provider-specific recovery notifications for Microsoft Entra ID, Google Workspace, and configured Okta accounts
- Email-first recovery with an independently configured SMS secondary channel
- Non-enumerating public forgot-password response and server-only SMTP/SMS credentials
- SSO entry point
- Pre-filled demonstration credentials
- Portfolio overview with pipeline, approval, SLA, and turn-time metrics
- Application search, stage filters, risk indicators, and pagination
- Interactive application workspace with Summary, Intelligence, Documents, and Activity tabs
- Optional live OpenAI assessment with a backend-only secret and environment-controlled model
- Live/demo AI status, schema-validated output, and deterministic fallback when AI is disabled
- AI-assisted sanction likelihood, file readiness, and predicted turn-time indicators
- Explainable decision factors, anomaly alerts, generated conditions, and next-best-action guidance
- Actionable My Tasks and team queues with create, start, reassign, complete, reopen, owner, priority, due-time, and linked-application controls
- Working document viewer with review status, preview, and demo download
- Optional private S3 uploads and downloads using short-lived signed requests
- Document inbox with classification and confidence indicators
- Named three-stage review workflow: loan officer condition review, underwriting decision, and compliance confirmation
- Clear in-app notifications after customer, task, document, review, profile, and recovery actions
- Borrower and co-borrower customer directory with working add-customer, profile, follow-up, messaging, and application-start actions
- RBI/NHB-oriented compliance control dashboard
- Portfolio analytics and product-mix visualisation
- Team workload and SLA performance directory
- Administrator user-management and access-governance dashboard
- Responsive desktop, tablet, and mobile navigation
- FastAPI application with generated OpenAPI documentation
- Validated Pydantic request and response schemas
- Application list, search, and detail endpoints
- Portfolio summary endpoint
- Audit-event ingestion boundary
- Safe AI status and application-assessment endpoints
- OpenAI Responses API adapter with strict structured output and minimized input
- S3 storage status, signed-upload, and scoped signed-download endpoints
- User directory, profile update, invitation, and administrative password-reset endpoints
- Public password-reset request and confirmation endpoints
- Async PostgreSQL engine configuration
- Local PostgreSQL service through Docker Compose
| Layer | Technology |
|---|---|
| Frontend | React, TypeScript, Vite |
| UI icons | Lucide React |
| Backend | FastAPI, Python, Pydantic |
| Database boundary | PostgreSQL, SQLAlchemy asyncio, asyncpg |
| Private documents | Amazon S3, Boto3, presigned POST/GET |
| Recovery notifications | SMTP email plus optional generic HTTP SMS gateway |
| Local infrastructure | Docker Compose |
Browser
└── React + TypeScript workspace
├── Login and session demo
├── Operations modules
├── Document review and upload UI
├── Deterministic UI fixtures
└── Optional live application intelligence
FastAPI service
├── /health
├── /api/v1/applications
├── /api/v1/portfolio/summary
├── /api/v1/audit-events
├── /api/v1/migration-manifests/validate
├── /api/v1/ai/status
├── /api/v1/ai/application-assessments
├── /api/v1/documents/storage/status
├── /api/v1/documents/uploads
├── /api/v1/documents/downloads
├── /api/v1/users
├── /api/v1/users/{user_id}/password-reset
├── /api/v1/auth/password-reset-requests
└── /api/v1/auth/password-reset-confirmations
├── PostgreSQL boundary
├── Optional private S3 bucket
├── Optional SMTP email and HTTP SMS delivery
├── SSO-provider recovery routing
└── Optional OpenAI Responses API adapter
The frontend uses local demonstration records for its primary workflow. Application Intelligence can optionally call the backend AI adapter; all model credentials and provider configuration remain server-side.
Every backend router is named for one use case and can be mounted without importing another API router. backend/app/api/v1/router.py only composes the independent modules:
applications.py application search and detail
audit.py audit-event ingestion
portfolio.py portfolio summary
migration_manifests.py legacy-mapping validation
ai.py application intelligence
documents.py private document storage
user_management.py directory, profiles, roles, status, invitations
password_recovery.py local resets and SSO-provider recovery notifications
User management and password recovery share only the UserRepository persistence contract; neither router nor service imports the other. Standalone router tests ensure every module starts and responds independently while the composed application preserves the existing /api/v1 URLs.
sahaay-los/
├── backend/
│ ├── alembic/ # Versioned PostgreSQL schema migrations
│ ├── app/
│ │ ├── api/v1/ # Independently mountable use-case routers
│ │ ├── core/ # Environment configuration
│ │ ├── db/ # Metadata, engine and transactions
│ │ ├── integrations/ # External provider adapters: AI, S3, email, and SMS
│ │ ├── legacy_migration/ # Manifest validation and migration planning
│ │ ├── models/ # SQLAlchemy persistence models
│ │ ├── repositories/ # Shared persistence contracts; no HTTP imports
│ │ ├── schemas/ # Pydantic contracts separated by use case
│ │ ├── services/ # Independent use-case orchestration
│ │ └── main.py # FastAPI application factory
│ ├── tests/
│ ├── alembic.ini
│ ├── requirements.txt
│ └── requirements-dev.txt
├── docs/
│ ├── architecture/ # Backend boundaries and folder rules
│ ├── integrations/ # Third-party provider onboarding
│ ├── migrations/ # Schema and legacy migration runbooks
│ └── sdlc/ # API/database delivery lifecycle
├── src/
│ ├── App.tsx # Login, shell, dashboard, and loan drawer
│ ├── api.ts # Typed frontend API client
│ ├── documents.ts # Typed demonstration document records
│ ├── operations.ts # Typed customer and task workflow records
│ ├── users.ts # Typed demonstration users and auth ownership
│ ├── WorkspacePages.tsx # Sidebar module pages
│ ├── data.ts # Typed demonstration loan records
│ ├── main.tsx # React entry point
│ └── styles.css # Responsive design system
├── docker-compose.yml # Local PostgreSQL service
├── index.html
├── package.json
├── tsconfig.json
└── README.md
- Node.js 20 or newer
- npm 10 or newer
- Python 3.11 or newer
- Docker Desktop or another Docker Compose-compatible runtime, if using PostgreSQL
PostgreSQL is optional for the current demonstration because the API returns fixture records until a persistence repository is added.
npm installnpm run devOpen http://localhost:5173.
Use the pre-filled credentials displayed on the login page:
Email: priya.sharma@sahaay.in
Password: Sahaay@2026
These credentials are for the local UI demonstration only. They are not sent to or verified by the FastAPI service.
In another terminal:
python3 -m venv .venv
source .venv/bin/activate
pip install -r backend/requirements.txt
uvicorn backend.app.main:app --reload --port 8000Windows PowerShell activation:
.venv\Scripts\Activate.ps1Available locally:
- API: http://localhost:8000
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
The application works in deterministic demo mode without a model key. To activate live application assessments, copy the environment template, inject a sandbox key into the ignored .env, and explicitly enable the feature:
AI_ENABLED=true
AI_PROVIDER=openai
AI_MODEL=gpt-5.6-luna
OPENAI_API_KEY=your-sandbox-keyRestart FastAPI, then verify GET /api/v1/ai/status. Keep the key on the backend—never create a VITE_ secret. See AI model integration and activation for the complete setup, security controls, API example, and model-switch process.
Document preview and review work in demonstration mode without S3. To enable real browser-to-S3 uploads and signed downloads, create an ignored .env file and configure:
DOCUMENT_STORAGE_ENABLED=true
S3_BUCKET_NAME=your-private-los-document-bucket
AWS_REGION=ap-south-1
S3_KEY_PREFIX=los-documents
S3_PRESIGNED_URL_EXPIRY_SECONDS=900
S3_MAX_UPLOAD_BYTES=20971520Credentials are backend-only and have two supported modes:
| Environment | Recommended configuration |
|---|---|
| ECS, EKS, or EC2 production workload | Attach a least-privilege IAM role and leave AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN empty. Boto3 uses its default credential provider chain. |
| Local sandbox | Set a scoped, temporary AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and, when issued, AWS_SESSION_TOKEN in the ignored .env. |
| Approved S3-compatible sandbox | Additionally set S3_ENDPOINT_URL; leave it empty for AWS S3. |
Never place AWS credentials in VITE_ variables, React code, source control, API requests, screenshots, or documentation. Never use an AWS account root access key.
The workload needs only object access under the configured prefix. Replace the bucket name in this example IAM policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "LOSPrivateDocumentObjects",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::your-private-los-document-bucket/los-documents/applications/*"
}
]
}Keep Block Public Access enabled. The bucket must allow the frontend origin to submit the signed POST and follow signed GET links. Example development CORS configuration:
[
{
"AllowedOrigins": ["http://localhost:5173"],
"AllowedMethods": ["GET", "POST"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 300
}
]Use only the deployed HTTPS frontend origin in production. Restart FastAPI and verify safe configuration metadata:
curl http://localhost:8000/api/v1/documents/storage/statusThe response reports the region, credential mode, size limit, and whether the bucket is configured. It never returns the bucket name, access key, secret key, session token, or signed URLs from previous requests.
Implementation references: Boto3 presigned URL guide, AWS credential provider chain, and AWS access-key configuration.
Profile management and simulated reset flows work without external notification credentials. Configure SMTP as the primary channel in the ignored .env file:
APP_ENVIRONMENT=production
APP_PUBLIC_URL=https://los.your-company.example
EMAIL_DELIVERY_ENABLED=true
SMTP_HOST=smtp.your-company.example
SMTP_PORT=587
SMTP_USERNAME=los-mailer
SMTP_PASSWORD=inject-from-your-secret-manager
SMTP_FROM_EMAIL=no-reply@your-company.example
SMTP_USE_TLS=true
PASSWORD_RESET_EXPIRY_MINUTES=30Enable SMS as an independent secondary channel:
SMS_DELIVERY_ENABLED=true
SMS_PROVIDER=generic_http
SMS_BASE_URL=https://sms-gateway.example.com/v1/messages
SMS_API_KEY=inject-from-your-secret-manager
SMS_SENDER_ID=LOSAPP
SMS_REQUEST_TIMEOUT_SECONDS=15
SMS_DLT_ENTITY_ID=registered-entity-id
SMS_DLT_TEMPLATE_ID=registered-template-idThe generic adapter sends a server-side authenticated JSON request containing recipient, message, sender_id, and the optional DLT identifiers. Adapt http.py if the selected gateway uses different field names or authentication. Registered mobile numbers must be stored in E.164 form, for example +919876543210; the masked demonstration values cannot be sent to a live gateway.
Set the identity-team-approved recovery location for each SSO provider:
ENTRA_PASSWORD_RESET_URL=https://passwordreset.microsoftonline.com/
GOOGLE_PASSWORD_RESET_URL=https://accounts.google.com/signin/recovery
OKTA_PASSWORD_RESET_URL=https://your-tenant.okta.com/signin/forgot-password| Account type | Reset behavior |
|---|---|
LOS-managed (auth_method=local) |
Generates one cryptographically random, single-use LOS link and sends it by email plus SMS when SMS is enabled. |
SSO-managed (auth_method=sso) |
Creates no LOS token. Email and optional SMS point to the configured Entra ID, Google Workspace, or Okta recovery URL. |
| Unknown public email | Returns the same generic response as a known account and sends nothing. |
The channels are independent: one failure is returned as partial while the successful channel remains valid; a reset token is revoked if every channel fails. When email is simulated, the administrator profile action returns a development preview link. The public endpoint never returns a token or account-existence information. Restart the API after configuration changes.
See Identity, user management, email, and SMS recovery for setup, provider switching, endpoint examples, security controls, and production persistence requirements.
docker compose up -d postgres
docker compose psDefault local connection:
postgresql+asyncpg://sahaay:sahaay@localhost:5432/sahaay
Override it with an environment variable when needed:
export DATABASE_URL='postgresql+asyncpg://user:password@host:5432/database'Stop the database with:
docker compose downAdd -v only when you intentionally want to delete the local database volume.
| Command | Purpose |
|---|---|
npm run dev |
Start the Vite development server |
npm run build |
Type-check and create a production frontend build |
npm test |
Run interaction tests for navigation, customers, tasks, staged reviews, documents, storage, profiles, and recovery |
npm run preview |
Preview the production build locally |
uvicorn backend.app.main:app --reload --port 8000 |
Start the API in development mode |
docker compose up -d postgres |
Start local PostgreSQL |
alembic -c backend/alembic.ini upgrade head |
Apply LOS schema migrations |
python -m pytest backend/tests |
Run backend API, standalone-router, integration, and migration tests |
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Service health check |
GET |
/api/v1/applications |
List applications; accepts an optional search query |
GET |
/api/v1/applications/{application_id} |
Fetch one application |
GET |
/api/v1/portfolio/summary |
Return portfolio-level metrics |
POST |
/api/v1/audit-events |
Validate and accept an audit event |
POST |
/api/v1/migration-manifests/validate |
Safely validate a legacy mapping manifest |
GET |
/api/v1/ai/status |
Return safe AI enablement metadata without secrets |
POST |
/api/v1/ai/application-assessments |
Generate a schema-validated advisory application assessment |
GET |
/api/v1/documents/storage/status |
Return safe S3 enablement metadata without credentials or bucket name |
POST |
/api/v1/documents/uploads |
Create a short-lived, size/type-restricted presigned S3 POST |
POST |
/api/v1/documents/downloads |
Create a short-lived signed GET scoped to one application's object prefix |
GET |
/api/v1/users |
Search or list user profiles and authentication ownership |
POST |
/api/v1/users |
Create an invited LOS-managed or SSO-managed user |
GET |
/api/v1/users/me |
Return the current demonstration profile |
PATCH |
/api/v1/users/me |
Update the current demonstration profile |
GET |
/api/v1/users/{user_id} |
Return one user profile |
PATCH |
/api/v1/users/{user_id} |
Update profile, role, branch, or status |
POST |
/api/v1/users/{user_id}/password-reset |
Send local or SSO recovery through email and optional SMS |
GET |
/api/v1/auth/password-recovery/status |
Return safe email/SMS channel configuration without credentials |
POST |
/api/v1/auth/password-reset-requests |
Accept forgot-password without revealing account existence |
POST |
/api/v1/auth/password-reset-confirmations |
Consume a local single-use token and set a strong password hash |
Example requests:
curl http://localhost:8000/health
curl 'http://localhost:8000/api/v1/applications?search=Aarav'
curl http://localhost:8000/api/v1/applications/HL-2026-0842Example audit event:
curl -X POST http://localhost:8000/api/v1/audit-events \
-H 'Content-Type: application/json' \
-d '{
"actor": "priya.sharma@sahaay.in",
"action": "underwriting_review_started",
"entity_id": "HL-2026-0842",
"metadata": {"source": "operations_workspace"}
}'Create an upload ticket:
curl -X POST http://localhost:8000/api/v1/documents/uploads \
-H 'Content-Type: application/json' \
-d '{
"application_id": "HL-2026-0842",
"file_name": "salary-statement.pdf",
"content_type": "application/pdf",
"size_bytes": 482113
}'The response contains upload_url, form_fields, object_key, and expiration metadata. Submit every returned field plus the file as multipart/form-data directly to upload_url. The application UI performs these two steps automatically. The signed POST enforces content type, AES-256 server-side encryption, the configured maximum size, a generated object key, and a short expiration.
Create a download ticket only for an object key previously recorded against the same application:
curl -X POST http://localhost:8000/api/v1/documents/downloads \
-H 'Content-Type: application/json' \
-d '{
"application_id": "HL-2026-0842",
"object_key": "los-documents/applications/HL-2026-0842/generated-id/salary-statement.pdf",
"download_name": "salary-statement.pdf"
}'Signed URLs are bearer capabilities until they expire. Return them only to an authenticated and authorised user, do not log them, and keep the expiry short. Before production, persist document metadata and application ownership, validate antivirus/content inspection results, audit access, enforce retention/legal holds, and require server-side authorisation before issuing any ticket.
Request a reset without revealing whether the account exists:
curl -X POST http://localhost:8000/api/v1/auth/password-reset-requests \
-H 'Content-Type: application/json' \
-d '{"email":"priya.sharma@sahaay.in"}'Administrators can select a profile in Team → User management and request recovery notifications. LOS-managed accounts receive one expiring application link; SSO-managed accounts receive their configured identity-provider recovery link. The response reports masked email/SMS destinations and per-channel status.
Integration credentials, provider selection, adapter contracts, consent requirements, and onboarding steps are documented separately:
- Third-party integration handbook
- Provider onboarding checklist
- AI model integration and activation
- Identity, user management, email, and SMS recovery
- Local environment template
The environment template contains placeholders only. Never commit real API keys, client secrets, passwords, private keys, bureau credentials, or Aadhaar-related licence material.
- Backend architecture and folder structure
- LOS schema migration process
- Legacy database migration runbook
- Example legacy mapping manifest
- API and database SDLC
Schema evolution and legacy-data movement are deliberately separate. Alembic owns the new PostgreSQL schema; the legacy migration process uses read-only sources, versioned mapping manifests, dry runs, checkpointed loads, reconciliation, and controlled cutover.
Build and type-check the frontend:
npm test
npm run buildThe frontend interaction suite verifies every sidebar module, the explicit Review application action, document opening/review, the disabled S3 uploader state, local profile editing/reset, SSO recovery routing, and the generic forgot-password response.
Run the backend test suite:
python -m pip install -r backend/requirements-dev.txt
python -m pytest backend/testsValidate the Alembic chain and example legacy mapping:
alembic -c backend/alembic.ini upgrade head --sql > /tmp/sahaay-schema.sql
python -m backend.app.legacy_migration.cli validate \
--manifest docs/migrations/examples/legacy-manifest.example.json- The login form accepts any syntactically valid email with a non-empty password.
- Team user profiles, password hashes, and reset tokens are in-memory demonstration records; they are lost when the API restarts.
- Profile editing, invitation creation, local reset confirmation, and SSO recovery routing call working API endpoints when the API is running.
- Disabled SMTP is simulated only in development and skipped in production; SMS is skipped until independently enabled. The administrator UI shows each channel status and exposes local preview links only in development.
- The demonstration login does not yet verify the password hash changed by the reset-confirmation API.
- Sidebar navigation is client-side state, not URL routing.
- Searches, filters, task completion, loan drawers, and notification feedback run locally.
- Application review buttons open the selected application workspace directly.
- Document rows open a working preview/review dialog; demonstration documents download as clearly labelled sample files.
- When S3 is enabled, the upload dialog obtains a signed ticket and sends the file directly to the private bucket without exposing AWS credentials.
- Refreshing the browser resets the demonstration session and local UI changes.
- SSO sign-in itself and exports remain demonstration actions until their external providers are connected.
The following work is intentionally outside the demonstration scope and should be completed before deployment.
- Replace demonstration authentication with OIDC/OAuth2 or an approved enterprise identity provider.
- Enforce server-side sessions, MFA, role-based access control, and branch/entity scoping.
- Persist users and hashed passwords in the database; move reset-token digests to a durable TTL store and revoke older tokens on each request.
- Protect all
/usersadministration endpoints with explicit administrator permissions and immutable audit events. - Add rate limiting, abuse detection, email/SMS delivery monitoring, token revocation, and session invalidation after local password changes.
- Keep the SSO password lifecycle with the identity provider and validate tenant-approved recovery URLs before deployment.
- Add PostgreSQL row-level security where multi-entity isolation is required.
- Connect the existing SQLAlchemy models and migrations to production repositories, transactions, backups, and restore drills.
- Store audit events in an append-only or WORM-backed system.
- Record model version, input fingerprint, explanation, recommendation, reviewer, and override reason for every AI-assisted decision.
- Encrypt sensitive data in transit and at rest and manage keys through a cloud KMS.
- CKYC registry
- PAN verification through an authorised provider
- Aadhaar e-KYC through an appropriately licensed KUA/sub-KUA workflow
- CIBIL, Experian, Equifax, and CRIF India credit bureau adapters
- RBI Account Aggregator consent and financial-information workflows
- Licensed e-sign/e-stamp providers
- India-resident OCR and document-processing infrastructure
- Email, SMS, WhatsApp, and push notification providers
Each adapter should implement consent capture, idempotency, retries, timeouts, error mapping, reconciliation, and immutable audit logging.
- AI recommendations must remain advisory until approved under the organisation's model-risk framework.
- Lending decisions require deterministic policy controls and authorised human review.
- Validate training data provenance, bias testing, explainability, drift monitoring, and retraining approval.
- Never send borrower PII to an AI or OCR provider without contractual, security, consent, and data-residency approval.
Validate current RBI directions, KYC requirements, Fair Practices Code obligations, Key Facts Statement formats, CIC reporting, floating-rate disclosures, grievance handling, and data-residency controls with legal counsel. Regulations and circulars change over time; do not treat repository text or demonstration labels as a final compliance checklist.
- Foundation: authentication, tenant model, RBAC, migrations, repositories, and immutable audit events.
- Origination: borrower application, co-borrowers, task workflow, document storage, and communications.
- Verification: KYC, bureau, account aggregator, appraisal, and property/legal integrations.
- Decisioning: versioned rules engine, pricing, exception workflow, and manual overrides.
- Closing: disclosures, e-sign, funding checklists, post-closing QC, and reporting.
- Intelligence: governed OCR, anomaly detection, explainable scoring, copilot, and predictive operations.
Start Vite or FastAPI on another port:
npm run dev -- --port 5174
uvicorn backend.app.main:app --reload --port 8001Stop and restart the Vite server, then perform a hard browser refresh.
Check the container and health state:
docker compose ps
docker compose logs postgresConfirm the virtual environment is active and reinstall dependencies:
pip install -r backend/requirements.txtThis repository is a functional product prototype and architecture starter. It is suitable for demonstrations, workflow validation, interface testing, and continued in-house development. It is not ready for handling real borrower data or making production lending decisions without the controls described above.