>
Module 02
Fintech AI

Credit and Underwriting

Predicting whether someone will repay, and being able to explain the answer. This module covers the decisioning pipeline, alternative data for borrowers the bureau cannot see, model choice under explainability constraints, deterministic adverse action reason codes, and the failure modes that decide whether a lending book performs.

Verified May 2026Free · No signupOfficial sources only
BeginnerStart here. No prior knowledge assumed.
Product guide

Building BNPL at checkout? The eight steps, the options at each stage, and what RBI actually permits: BNPL Checkout: How to Build It →

Product guide

Getting customer bank data with consent? The eight steps, and the module most estimates forget: Account Aggregator: How to Build It →

Product guide

Lending alongside a bank? The eight steps, and what the 2025 Directions changed: Co-Lending: How to Build It →

Product guide

Financing receivables rather than borrowers? The eight steps, and what the 2026 TReDS Directions changed: Invoice Discounting: How to Build It →

Product guide

Scoring a borrower the bureau cannot rank? The eight steps, what data you may lawfully use, and why every decline needs a stored reason: Alternative Credit Scoring: How to Build It →

Product guide

Contacting borrowers who have missed a payment? The eight steps, the contact rules as code, and what the August 2026 Directions change from 1 January 2027: Collections and Recovery: How to Build It →

Build sheet

Every tool for this module, how to use each one, what it costs, the best combinations and three recommended builds: Credit Build Sheet →

What underwriting actually decides

Underwriting answers one question: will this person pay us back? Everything else — the score, the model, the data — exists to estimate that.

A lender is really making three separate judgements:

  1. Capacity — can they afford the repayment out of their income?
  2. Willingness — have they repaid obligations before when they could have chosen not to?
  3. Stability — is their situation likely to hold for the life of the loan?

A credit bureau score compresses mostly the second into one number. That works well for people with a borrowing history. It fails completely for people without one ’ and in India that is most of the working-age population.

The thin-file problem

Roughly 50 crore working-age Indians have no credit bureau score. Not because they are risky, but because they have never taken formal credit. The bureau has nothing to report.

There is an important distinction people collapse:

SituationWhat the bureau showsWhat it means
Thin file / new-to-creditNo history, or "NH" / "-1"An unknown borrower, not a bad one. The data is missing, not negative.
Low scoreScore below roughly 600A known borrower with a poor record — missed payments, defaults, settlements.

Treating the first like the second is the single largest source of unnecessary rejection in Indian lending, and it is what alternative data exists to fix.

Note

This is also where the commercial opportunity sits. Everybody can lend to a prime salaried borrower with a 780 CIBIL score, and the margin reflects that. The lenders making money in India are the ones who learned to underwrite the people the bureau cannot see.

What AI can and cannot do here

TaskHow well AI does it
Predict default probability from structured historyVery well. Gradient-boosted models beat logistic regression consistently.
Extract income and obligations from a bank statementVery well. Classification of recurring inflows and outflows is largely solved.
Find signal in transaction patternsWell. This is where alternative data pays.
Score a borrower with genuinely no dataNot at all. No model creates information that does not exist.
Explain why it rejected someonePartially, and this is the hard part. Post-hoc attribution gives an approximation, not the decision process.
Prove it does not discriminateNo. It can be tested for disparate outcomes; it cannot certify fairness.
Decide the credit policyIt should not. Risk appetite is a business decision, not a model output.

The constraint that shapes everything

If you decline someone, you must tell them why ’ specifically. Not "did not meet our criteria". A real, accurate, specific reason.

In India this flows from RBI expectations on digital lending and fair practices. In the US it is codified in ECOA and Regulation B, and the CFPB has stated plainly that the obligation applies fully when the decision comes from a complex model, and that generic reason codes are insufficient.

This is not a reporting feature you add before launch. It is an architectural constraint that decides which models you may use.

Watch out

Read this carefully, because it is the most commonly misunderstood point in AI lending: regulators distinguish between a post-hoc explanation and the reasons that actually drove the decision. If your documented process says a linear scorecard decides, but a neural network is actually making the call and SHAP is describing it afterwards, that discrepancy is itself a compliance failure. The explanation must describe the real decision path.

IntermediateBuild it. Pipelines, tools and working code.

The decisioning pipeline

Eight stages. The two most teams under-build are the policy layer and reason generation — which are also the two a regulator will look at first.

Decisioning pipeline
APPLICATION
  |
  1. ELIGIBILITY GATE    age, geography, product rules, KYC status
  |                      -> hard rules, no model. Cheap rejects first.
  |
  2. DATA ASSEMBLY       bureau pull(s) + AA bank statements + GST/EPFO
  |                      + device & application signals
  |                      -> each source has a cost. Order them cheapest-first
  |                         and stop early where a decision is already certain.
  |
  3. FEATURE ENGINEERING derive ~50-300 features from raw data
  |                      (this is where most of the model quality lives)
  |
  4. SCORING             PD model -> probability of default
  |
  5. POLICY LAYER        score + hard rules + affordability -> decision
  |                      -> the model gives probability; POLICY gives the answer
  |
  6. LIMIT & PRICING     how much, at what rate, over what tenure
  |
  7. REASON GENERATION   if declined: specific, accurate, deterministic reasons
  |
  8. DECISION LOG        immutable record: inputs, model version, policy
  |                      version, score, reasons, any human override
  |
OUTCOME  approve / decline / refer to human / counter-offer

Order your data pulls by cost

Every bureau pull and every AA fetch costs money. A meaningful share of applications can be declined on hard eligibility rules before you spend anything ’ wrong geography, under-age, product mismatch, existing default with you.

A sensible sequence: free internal rules, then the cheapest external signal, then bureau, then AA data only for applicants still in contention. Teams that pull everything for every applicant are paying several times more than necessary for the same decision.

Where the data comes from in India

Six underwriting paths that Indian lenders actually run, roughly in order of cost and depth:

PathSignalBest for
Bureau pullScore, tradelines, DPD history, enquiriesAnyone with a credit history. Fast, cheap, decisive.
Bank statement analysis via Account Aggregator12 months of inflows, EMIs, surplus, bounce rateThe thin-file lender’s primary signal.
GST returns (GSTR-1 / GSTR-3B)Turnover, growth, filing regularity, tax paidMSME credit. The unlock for business lending.
EPFO contributionsEmployer-confirmed income, tenure, continuitySalaried borrowers. Employer-verified, hard to fake.
UPI transaction patternsVelocity, merchant category spread, consistencySelf-employed, gig workers, small merchants.
Utility and telecom paymentsPayment regularity on recurring billsSupplementary signal for new-to-credit.
Note

EPFO and GST are underrated. They are employer- and government-confirmed, which makes them far harder to manipulate than self-declared income or uploaded documents. For salaried and MSME segments respectively they often carry more signal per rupee of data cost than anything else available.

Access is licensed, again

The four Indian credit bureaus — TransUnion CIBIL, Experian, Equifax and CRIF High Mark — require membership, which requires being a regulated lender or going through one. Account Aggregator data requires being a registered Financial Information User. Neither is available to an unlicensed startup directly.

This is why most new lenders start as a technology partner to an NBFC, or buy through an aggregation platform that holds the licences.

Feature engineering — where the model quality actually is

Model choice matters less than people expect. Feature quality matters more than people expect. Two teams using the same gradient-boosted library on the same population will differ mostly because one derived better features.

Python — features from Account Aggregator bank statement data
from collections import defaultdict
from statistics import mean, pstdev

def cashflow_features(txns, months=12):
    """txns: list of {date, amount, narration, type: 'credit'|'debit'}
    Amounts positive. This is the thin-file lender's primary signal set."""
    f = {}
    by_month = defaultdict(lambda: {"in": 0.0, "out": 0.0})
    for t in txns:
        m = t["date"][:7]                      # YYYY-MM
        by_month[m]["in" if t["type"] == "credit" else "out"] += t["amount"]

    ins  = [v["in"]  for v in by_month.values()]
    outs = [v["out"] for v in by_month.values()]
    if not ins:
        return {"insufficient_data": True}

    # --- Income: level and, more importantly, STABILITY ---
    f["avg_monthly_inflow"] = mean(ins)
    f["inflow_volatility"]  = pstdev(ins) / mean(ins) if mean(ins) else 0
    f["months_observed"]    = len(by_month)

    # Salary credits: same-ish amount, same-ish day each month
    sal = [t for t in txns if t["type"] == "credit"
           and any(k in t["narration"].upper()
                   for k in ("SALARY", "SAL ", "NEFT-SAL", "PAYROLL"))]
    f["salary_months"] = len({t["date"][:7] for t in sal})
    f["is_salaried"]   = f["salary_months"] >= max(3, len(by_month) * 0.6)

    # --- Obligations: existing EMIs are the affordability denominator ---
    emi = [t for t in txns if t["type"] == "debit"
           and any(k in t["narration"].upper()
                   for k in ("EMI", "ACH-D", "NACH", "LOAN", "ECS"))]
    emi_by_month = defaultdict(float)
    for t in emi:
        emi_by_month[t["date"][:7]] += t["amount"]
    f["avg_monthly_emi"] = mean(emi_by_month.values()) if emi_by_month else 0.0
    f["foir"] = (f["avg_monthly_emi"] / f["avg_monthly_inflow"]
                 if f["avg_monthly_inflow"] else 1.0)   # fixed-obligation ratio

    # --- Distress signals: the highest-value features in this set ---
    bounces = [t for t in txns if any(k in t["narration"].upper()
               for k in ("RETURN", "BOUNCE", "INSUFFICIENT", "ECS RTN", "CHQ RTN"))]
    f["bounce_count_12m"]  = len(bounces)
    f["bounce_months"]     = len({t["date"][:7] for t in bounces})
    f["has_recent_bounce"] = any(t["date"][:7] in sorted(by_month)[-3:] for t in bounces)

    # --- Buffer: does money survive the month? ---
    f["avg_monthly_surplus"] = mean(ins) - mean(outs)
    f["surplus_ratio"] = f["avg_monthly_surplus"] / mean(ins) if mean(ins) else 0

    return f

# Note what is NOT here: no device model, no contact list, no location,
# no app-usage. Those are either prohibited under the RBI digital lending
# rules or carry serious proxy-discrimination risk. See the Advanced lane.

The features that carry the most weight

Consistently, across Indian lending portfolios:

  1. Bounce and return history — the strongest single alternative-data signal. Someone whose payments have been returned in the last three months is a different risk from someone whose have not.
  2. Fixed obligation to income ratio — affordability, not willingness. Catches over-leveraged borrowers the bureau has not caught up with.
  3. Income stability rather than income level. A steady ₹30,000 is safer than a volatile ₹60,000.
  4. Surplus ratio — whether money survives the month at all.
  5. Recent credit-seeking — a burst of enquiries across lenders is a well-established distress signal.

Choosing a model, with the explainability constraint

ModelAccuracyExplainabilityVerdict for credit
Logistic regression / scorecardLowerNative — coefficients are the reasonsStill the default at banks. Defensible, stable, boring, and boring is a feature here.
Gradient boosting (XGBoost, LightGBM)HigherPost-hoc via SHAPThe practical standard for fintech lenders. Needs a disciplined reason-code layer.
Neural networksMarginal gain on tabular dataHardRarely worth the regulatory cost on structured credit data.
LLMs as the decision-makerPoorVery hardDo not. Non-deterministic, unstable, unexplainable in the required sense.
Hybrid — model score as one input to a rules policyGoodGoodWhat most defensible production systems actually are.
Watch out

The last row is the practical answer. Let the model produce a probability. Let an explicit, versioned policy layer turn that probability plus hard rules plus affordability into a decision. You can then explain the decision as policy, with the score as one documented input — which is a far easier thing to defend than "the model said no".

Generating adverse action reasons

This is the part that decides whether the system is shippable.

The established technique groups SHAP contributions by reason category, sums within group, ranks, and returns the top few that pushed toward decline. Grouping matters — five correlated features about repayment history should surface as one reason, not crowd out everything else.

Python — SHAP to deterministic adverse action reason codes
import numpy as np

# Map each raw feature to a REGULATOR-FACING reason group.
# The group is what the applicant sees. Grouping matters: five features
# about repayment history should surface as one reason, not five.
FEATURE_TO_REASON = {
    "bounce_count_12m":    "RECENT_PAYMENT_RETURNS",
    "bounce_months":       "RECENT_PAYMENT_RETURNS",
    "has_recent_bounce":   "RECENT_PAYMENT_RETURNS",
    "foir":                "EXISTING_OBLIGATIONS_HIGH",
    "avg_monthly_emi":     "EXISTING_OBLIGATIONS_HIGH",
    "avg_monthly_inflow":  "INCOME_BELOW_REQUIREMENT",
    "surplus_ratio":       "INSUFFICIENT_SURPLUS",
    "inflow_volatility":   "INCOME_NOT_STABLE",
    "months_observed":     "INSUFFICIENT_HISTORY",
    "bureau_score":        "CREDIT_BUREAU_HISTORY",
    "bureau_dpd_max":      "CREDIT_BUREAU_HISTORY",
    "enquiries_6m":        "RECENT_CREDIT_SEEKING",
}

REASON_TEXT = {
    "RECENT_PAYMENT_RETURNS":    "Recent returned or bounced payments on your bank account",
    "EXISTING_OBLIGATIONS_HIGH": "Existing loan repayments are high relative to your income",
    "INCOME_BELOW_REQUIREMENT":  "Income is below the level required for this product",
    "INSUFFICIENT_SURPLUS":      "Limited surplus remaining after monthly expenses",
    "INCOME_NOT_STABLE":         "Income received varies significantly month to month",
    "INSUFFICIENT_HISTORY":      "Not enough account history available to assess",
    "CREDIT_BUREAU_HISTORY":     "Repayment history reported by the credit bureau",
    "RECENT_CREDIT_SEEKING":     "Several recent applications for credit",
}

def adverse_action_reasons(shap_values, feature_names, top_n=3):
    """Group SHAP contributions by reason, keep only those pushing
    TOWARD decline, rank, and return the top N.

    DETERMINISM IS A HARD REQUIREMENT. The same applicant with the same
    data must produce the same reasons in the same order, every time.
    Ties are broken by reason code alphabetically, never by dict order."""
    grouped = {}
    for name, sv in zip(feature_names, shap_values):
        reason = FEATURE_TO_REASON.get(name)
        if reason is None:
            continue                      # unmapped feature -> see assertion below
        grouped[reason] = grouped.get(reason, 0.0) + float(sv)

    # Sign convention: positive SHAP = pushes toward higher default risk
    adverse = {r: v for r, v in grouped.items() if v > 0}

    ranked = sorted(adverse.items(), key=lambda kv: (-round(kv[1], 10), kv[0]))
    return [{"code": r, "text": REASON_TEXT[r], "contribution": round(v, 6)}
            for r, v in ranked[:top_n]]

def assert_all_features_mapped(feature_names):
    """Run in CI. An unmapped feature means a decision could be driven by
    something you cannot explain to an applicant or a regulator."""
    missing = [f for f in feature_names if f not in FEATURE_TO_REASON]
    assert not missing, f"Unmapped features cannot be used in a credit model: {missing}"

Determinism is non-negotiable

If the same applicant reapplies with identical data and receives a different reason ranking, the audit trail fails immediately under examination. Sort keys must be fully specified, ties broken deterministically, and floating-point rounding fixed ’ not left to dictionary iteration order.

Note

The CI assertion in the code above is worth copying. If a feature enters the model without a mapped reason code, a decision could be driven by something you cannot articulate to the applicant. Failing the build is the right response.

The registry

India — credit data sources

Verified May 2026
TransUnion CIBILindirect
Largest Indian bureau. Membership requires being a regulated lender or going through one.
Experian Indiaindirect
Bureau. Same access model.
Equifax Indiaindirect
Bureau. Same access model.
CRIF High Markindirect
Bureau, historically strong in microfinance coverage.
Account Aggregator networkindirect
Consented bank statement data. Requires FIU registration; reached through AA providers and TSPs.
GSTN (GST returns)indirect
GSTR-1 / GSTR-3B filings for MSME income assessment. Consent-based, via authorised providers.
EPFOindirect
Employer-confirmed salary and tenure for salaried borrowers.
NPCI / UPI dataindirect
Transaction velocity and merchant category signals; reached via PSP or aggregator relationships.

Decisioning and data platforms

Verified May 2026
Perfiosdirect
Bank statement analysis plus broader financial data. One of the most widely used in Indian lending.
FinBoxdirect
Alt-data underwriting, device and bureau integration, decisioning layer.
Lentradirect
Full-stack lending infrastructure — origination through decisioning.
Finfluxdirect
Loan origination and management system with decisioning.
Karzadirect
Verification and financial intelligence, bank statement analysis, fraud signals.
Setudirect
Account Aggregator and data infrastructure APIs.
Digitap / Deepvuedirect
Alt-data and verification API aggregators covering multiple underwriting paths.

Modelling and explainability

Verified May 2026
XGBoost / LightGBM / CatBoostoss
The practical standard for tabular credit models. Apache 2.0 / MIT.
SHAPoss
Shapley-value attribution. The dominant method for reason codes on tree ensembles. MIT.
scikit-learnoss
Logistic regression, calibration, metrics. BSD.
Optbinningoss
Weight-of-evidence binning and scorecard construction — the traditional credit toolkit.
Evidently / NannyMLoss
Drift and performance monitoring in production.
Fairlearn / AIF360oss
Group fairness metrics and bias testing.
Watch out

Registry reflects what was publicly visible in May 2026. Bureau and AA access terms in particular change; verify current requirements with the provider and with your compliance function before designing around them.

A prompt for designing your credit policy

Prompt — paste into any AI
You are a credit risk head who has built underwriting policy for
Indian NBFCs and fintech lenders.

My product:
- Loan type and ticket size: [e.g. unsecured personal, Rs 25k-2L]
- Target segment: [e.g. salaried tier-2, self-employed, MSME, gig]
- Tenure: [e.g. 6-24 months]
- Expected volume: [applications per month]
- Data I can access: [bureau / AA / GST / EPFO / UPI / none yet]
- Licence position: [own NBFC / partner with an NBFC / unlicensed]

Give me:

1. An underwriting waterfall - which checks in which order, and why
   that order minimises data cost per decision.
2. The 15-20 features I should build first, and which data source
   each comes from.
3. Hard-rule declines that should happen BEFORE any model runs.
4. A starting policy grid: score band x FOIR band -> decision and limit.
5. The adverse action reason codes I will need, mapped to features.
6. What my approval rate and early-delinquency rate should roughly
   look like in months 1-6 if the policy is calibrated sensibly, and
   what it means if they diverge.
7. The three ways this policy will most likely be gamed.

Be specific to the Indian context. Flag where you are uncertain or
where current regulation should be checked with a qualified adviser.
AdvancedShip it. Failure modes, thresholds and evidence.

The reject inference problem

This is the deepest methodological problem in credit modelling and most teams meet it late.

You only observe repayment behaviour for applicants you approved. The ones you declined have no outcome. So your training data is not a sample of applicants ’ it is a sample of applicants your existing policy already liked.

Train naively on that and the model learns to reproduce your current policy, including its mistakes, while appearing to perform well because it is evaluated on the same biased sample.

What to do about it

  • Hold out a random approval slice. Approve a small random percentage of applicants who would otherwise be declined, at reduced limits. Expensive, and the only method that produces genuinely unbiased data. Budget for the losses as a data acquisition cost.
  • Reject inference techniques — parcelling, augmentation, fuzzy assignment — infer likely outcomes for declined applicants. Standard practice, and all of them rest on assumptions that can be wrong.
  • Use bureau performance on declines. If a declined applicant borrowed elsewhere, the bureau will eventually show how that performed. Cheap, partial, and genuinely useful.
Note

The random approval slice is the one that separates lenders who understand their risk from lenders who are extrapolating. It feels wasteful in a board meeting and it is the only way to find out whether the population just outside your cut-off is actually bad.

Proxy discrimination, tested properly

You are not permitted to use caste, religion, gender or similar characteristics. The harder question is whether your features stand in for them.

Pincode correlates with community in India. Device price correlates with income and with much else. App usage, language settings, and name-derived features all carry demographic signal.

Python — disparate impact testing
from collections import defaultdict

def disparate_impact(decisions, groups):
    """decisions: list of 'approve'|'decline'
       groups:    list of protected-attribute labels, SAME ORDER

    The four-fifths rule: if the approval rate for any group falls below
    80% of the highest group's rate, that is a flag requiring investigation.
    It is a screening heuristic, not a legal test, and it applies to
    outcomes - not to whether the attribute was an input."""
    tot, appr = defaultdict(int), defaultdict(int)
    for d, g in zip(decisions, groups):
        tot[g] += 1
        if d == "approve":
            appr[g] += 1

    rates = {g: appr[g] / tot[g] for g in tot if tot[g] >= 100}
    if not rates:
        return {"error": "insufficient volume per group"}

    best = max(rates.values())
    return {
        "approval_rates": rates,
        "impact_ratios": {g: (r / best) for g, r in rates.items()},
        "flags": [g for g, r in rates.items() if best and (r / best) < 0.80],
    }

def proxy_strength(feature_values, protected_labels):
    """How well does a single feature predict the protected attribute?
    High AUC here means the feature is carrying demographic signal,
    whether or not you intended it to."""
    from sklearn.linear_model import LogisticRegression
    from sklearn.model_selection import cross_val_score
    import numpy as np
    X = np.array(feature_values).reshape(-1, 1)
    y = np.array(protected_labels)
    auc = cross_val_score(LogisticRegression(max_iter=1000), X, y,
                          cv=5, scoring="roc_auc").mean()
    return {"proxy_auc": auc,
            "assessment": "strong proxy" if auc > 0.70
                          else "moderate proxy" if auc > 0.60
                          else "weak"}
Watch out

To test for disparate impact you generally need the protected characteristic. To comply with data minimisation you should not be holding it. Jurisdictions resolve this differently and some permit collection specifically for fairness testing. Resolve it with counsel rather than by assumption — and note that not testing is not a defence.

India does not have a US-style disparate impact doctrine in credit. The supervisory and reputational exposure is nonetheless real, and the RBI digital lending rules were themselves a response to conduct that looked defensible to the firms doing it.

SHAP is less stable than it looks

A point that rarely appears in vendor material: SHAP explanations are not perfectly stable. Retrain on slightly different data, or perturb an input marginally, and the ranking of contributions can shift ’ particularly among correlated features.

For model understanding that is tolerable. For a regulated adverse action notice that must be reproducible, it is a problem.

Mitigations that work

  • Group aggressively. Individual feature attributions move; grouped reason categories are far more stable because correlated features sum within the same group.
  • Pin the model version to the decision. The reason is reproducible against the model that made the decision, not against the current model.
  • Use TreeSHAP exact rather than sampling approximations for the production path.
  • Test reproducibility in CI. Same input, same model version, identical reason ranking — assert it.
  • Prune correlated features. Two features measuring the same thing split attribution between them and destabilise the ranking.
Note

This is also an argument for the hybrid architecture. If the policy layer is doing the deciding and the score is one input, your explanation rests on explicit policy rules rather than on the stability of a post-hoc attribution method.

Monitoring: models decay, and quietly

A credit model degrades for reasons unrelated to code. The population shifts, the economy shifts, your marketing changes who applies, and competitors change who is left for you.

What to watchSignalTypical cadence
Population Stability IndexHas the applicant mix shifted? PSI above ~0.25 on key features warrants investigationWeekly
Score distributionSudden shift usually means a data pipeline problem, not a population changeDaily
Approval rate by segmentDrift here precedes both risk and fairness problemsWeekly
Early delinquency (first EMI, 30+ DPD)The earliest real outcome signal you getMonthly by cohort
Vintage curvesCompare each origination cohort at the same age — the only fair comparisonMonthly
Feature null ratesA source silently returning nulls is a common and expensive failureDaily
Watch out

Watch feature null rates closely. When an upstream provider degrades and starts returning nulls, a tree model does not error — it routes down the default branch and keeps scoring confidently. Portfolios have been damaged by a data outage nobody noticed for weeks because nothing looked broken.

Unit economics, honestly

The arithmetic that decides whether a lending product works, before any model quality is considered:

Unit economics per approved loan
Revenue
  interest earned over expected life
  + processing fee
  - cost of funds

Costs
  - acquisition cost / approval rate        <- declined applicants cost you too
  - data cost per application / approval rate
  - expected credit loss (PD x LGD x EAD)
  - servicing and collections cost
  = contribution per loan

The term everyone underestimates:
  data cost per APPROVAL, not per application.

  At a 20% approval rate, Rs 60 of data pulls per application
  becomes Rs 300 per approved loan. On a Rs 25,000 ticket at
  thin margins, that is not a rounding error.

The lever most teams miss:
  Declining cheaply is worth as much as approving accurately.
  Every rupee of data spent on an applicant you were always
  going to decline is pure loss.

Two consequences follow. First, the eligibility gate and pull ordering in the Intermediate lane are commercial decisions, not engineering tidiness. Second, a small improvement in approval rate at constant risk improves unit economics more than a large improvement in model AUC.

The decision log, and where this module ends

JSON — minimum credit decision record
{
  "decision_id": "dec_01HXZ...",
  "application_id": "app_7741",
  "timestamp": "2026-05-22T10:02:11Z",

  "data_sources": [
    {"source": "cibil", "pulled_at": "...", "ref": "enq_9912", "cost_paise": 4500},
    {"source": "aa_bank_statement", "consent_ref": "cons_441a",
     "months": 12, "accounts": 1}
  ],

  "features_snapshot": { "...": "full feature vector as scored" },
  "feature_null_count": 0,

  "model": {
    "name": "pd_unsecured_v3",
    "version": "3.4.2",
    "trained_on": "2026-02-18",
    "score": 0.0731,
    "score_band": "B2"
  },

  "policy": {
    "version": "credit-policy-v11",
    "hard_rules_passed": ["age", "geo", "kyc_complete"],
    "hard_rules_failed": [],
    "foir_computed": 0.41,
    "foir_cap": 0.50
  },

  "decision": {
    "outcome": "approve",
    "limit": 120000, "tenure_months": 18, "apr": 0.24,
    "adverse_reasons": [],
    "human_override": null
  },

  "explainability": {
    "method": "TreeSHAP-exact",
    "reason_codes_computed": true,
    "determinism_hash": "sha256:7c1f..."
  }
}

The determinism_hash is worth adopting. Hash the sorted reason codes and contributions at decision time. If the same applicant is re-scored later against the same model version, the hash must match. It turns reproducibility from a claim into a test.

Where this module ends

  • Identity verification happens before any of this — Module 01.
  • Fraud at application — synthetic identities, first-payment default rings, application farming — is Fraud & Risk. Credit models assume the applicant is real; fraud models do not.
  • Collections and recovery after delinquency is Customer Operations, and recovery conduct is tightly regulated.
  • Model risk management — validation, documentation, challenger models, independent review — is Governance.
  • What you may collect and how automated the decision may be sits in the India and global regulatory spine pages.
Watch out

Illustrative code, as throughout. A credit decisioning system affecting real borrowers needs independent model validation, legal review of the adverse action path, and sign-off from whoever is accountable for credit risk at your firm or your partner lender.

Sources

Every figure, rule and date on this page, and where to check it. Entries are typed so you can see which are primary-sourced and which are industry reporting.

  1. officialRBI Digital Lending Directions — the RE/LSP split, disclosure obligations and the cooling-off requirement. www.rbi.org.in
  2. officialRBI Fair Practices Code — the obligation to give reasons for a rejection. www.rbi.org.in
  3. officialAccount Aggregator framework — the consent architecture for financial data. sahamati.org.in
  4. officialCIC (Regulation) Act and RBI directions — bureau reporting, dispute handling and the 30-day resolution clock. www.rbi.org.in

Tooling, pricing and cost sources for this module are on its build sheet: sources →

Checked September 2026. Regulation in this area is actively developing; the date is part of the claim.

Ask an AI about this page

Opens your assistant with this page as the source, and a question rather than a summary. It will ask what you are building before it answers.

Nothing is sent from here. The link carries only this page’s title and address.