Intent Spec — finance.apply_personal_loan
FULL ID: finance.apply_personal_loan
VERSION: v1.0.0
STATUS: draft
LAST UPDATED: 2026-05-13
DOMAIN: finance
PRIMARY AGENT: FinanceAgent
TTBS WEIGHTS: time=0.25 taste=0.10 budget=0.40 safety=0.25
User applies for an unsecured personal loan from a bank or NBFC. Covers eligibility check (soft-pull credit bureau), offer discovery across lenders, KYC, e-mandate setup, instant or T+1 disbursement. Distinct from secured loans (finance.apply_home_loan, vehicle loans v1.1+) and credit cards (finance.apply_credit_card). Pre-approved offers (PA-flagged) and standard applications both route here.
Partner exemplars: BankBazaar, Paisabazaar, Loan-Tap, Bajaj Finserv, HDFC Bank, ICICI Bank, Axis Bank, IDFC First, Kotak Mahindra, Fibe (EarlySalary), KreditBee, LazyPay, MoneyTap.
SECTION 1 — INTENT IDENTITY
User wants an unsecured personal loan. Distinct from:
finance.apply_home_loan— secured against propertyfinance.apply_credit_card— revolving credit, not term loan- Vehicle loans — v1.1+
finance.apply_vehicle_loan - Business / MSME loans — v1.1+
finance.apply_business_loan - Gold loan — v1.1+
finance.apply_gold_loan - Loan against securities — v1.1+
- Buy-now-pay-later — v1.1+
finance.apply_bnpl - Education loan — v1.1+
finance.apply_education_loan
Single intent per loan application. Multi-lender shopping is internal to §4 (TOMO fans out to all eligible partners).
SECTION 2 — NATURAL LANGUAGE COVERAGE
CLASSIFIES IN
- "Personal loan ₹5 lakh, 3 years"
- "Best instant personal loan for salaried"
- "Pre-approved personal loan offers"
- "Cheapest personal loan interest rate"
- "₹2L emergency loan, need today"
- "Personal loan for medical emergency"
- "Personal loan for wedding"
- "Top up my existing personal loan"
- "Personal loan eligibility check"
- "Personal loan without income proof" (gig workers)
CLASSIFIES OUT — BORDERLINE NO
- "Home loan" →
finance.apply_home_loan - "Credit card" →
finance.apply_credit_card - "Vehicle loan" → v1.1+
- "Business loan for MSME" → v1.1+
- "What's my CIBIL score?" → v1.1+
finance.check_credit_score - "EMI calculator" — non-transactional research
- "Foreclose my existing loan" → v1.1+
finance.foreclose_loan
MULTI-INTENT TRIGGERS
- "Personal loan + insurance" →
finance.apply_personal_loan+finance.buy_term_insurance(loan-protection) - "Loan + financial advisor" →
finance.apply_personal_loan+finance.book_financial_advisor_session - "Wedding loan + venue + caterer" →
finance.apply_personal_loan+entertainment.book_event_venue+food.book_catering_event
SECTION 3 — INPUT (TOMO → PROVIDER)
{
"intent": "finance.apply_personal_loan",
"request_id": "req_01J9Z...",
"user_locale": "en-IN",
"user_currency": "INR",
"user_location": { "lat": 17.4475, "lng": 78.3563, "city": "Hyderabad", "pincode": "500032" },
"applicant": {
"first_name": "Rakesh", "last_name": "Kumar",
"date_of_birth": "1990-04-18",
"gender": "male",
"pan_last4": "5678",
"mobile_e164": "+919876543210",
"email": "rakesh@example.com",
"current_address_pincode": "500032",
"current_address_type": "owned", // STRICT ENUM §6
"years_at_current_address": 4,
"marital_status": "married",
"employment": {
"type": "salaried_corporate", // STRICT ENUM §6
"company_name": "Acme Pvt Ltd",
"industry": "information_technology", // STRICT ENUM §6
"designation": "Senior Engineer",
"years_at_current_employer": 3,
"total_work_experience_years": 8,
"net_monthly_income_inr": 125000,
"gross_annual_income_inr": 1800000,
"company_category": "listed_or_psu" // STRICT ENUM §6
},
"obligations": {
"existing_emi_monthly_inr": 22000,
"credit_cards_outstanding_inr": 35000,
"consent_for_credit_bureau_pull": true, // soft pull always
"consent_for_aa_aggregator_pull": true // Account Aggregator framework
}
},
"loan_request": {
"amount_inr": 500000,
"tenure_months": 36,
"purpose": "wedding", // STRICT ENUM §6
"preferred_lenders": [],
"rate_type_preference": "fixed", // STRICT ENUM §6
"first_emi_date_preference": "salary_date", // STRICT ENUM §6
"want_step_down_emi": false,
"want_part_prepayment_no_charge": true
},
"ttbs_user_band": {
"time": "fast",
"taste": "balanced",
"budget": "good",
"safety": "good"
},
"session_context": { "tomo_session_id": "ses_01J9Z...", "user_dna_hash": "dna_v3_a7c9..." }
}
| Field | Type | Constraint | Notes |
|---|---|---|---|
intent |
string | REQUIRED, STRICT ENUM | Always finance.apply_personal_loan |
applicant.pan_last4 |
string | REQUIRED, 4 digits | Full PAN at KYC |
applicant.mobile_e164 |
string | REQUIRED, E.164 | OTP-verified by partner |
applicant.employment.type |
enum | REQUIRED, STRICT ENUM §6 | Drives underwriting program |
applicant.employment.net_monthly_income_inr |
int | REQUIRED, INR_INTEGER | Drives EMI/FOIR cap |
applicant.employment.company_category |
enum | REQUIRED, STRICT ENUM §6 | Listed/PSU = lower rate; unlisted/proprietorship = loaded |
applicant.obligations.consent_for_credit_bureau_pull |
bool | REQUIRED, value MUST be true | Without consent, no quote |
loan_request.amount_inr |
int | REQUIRED, INR_INTEGER, ≥10000 | |
loan_request.tenure_months |
int | REQUIRED, 3-84 | |
loan_request.purpose |
enum | REQUIRED, STRICT ENUM §6 | |
loan_request.rate_type_preference |
enum | REQUIRED, STRICT ENUM §6 | fixed / floating |
Anti-fabrication preamble: Soft credit-bureau pull only at quote stage. Hard pull only after user accepts an offer. APR must include all fees disclosed per RBI Master Direction on KFS (Key Fact Statement). No usury rate above NBFC ceiling. No mandatory credit-life insurance bundle (RBI 2023 ruling).
SECTION 4 — PROVIDER TOOLS
Tool 1: search_loan_offers
PURPOSE: Up to 12 offers from eligible lenders (soft-pull-based pre-qualification)
SLA: p50 ≤ 1000ms, p95 ≤ 3500ms, p99 ≤ 7000ms
RATE LIMIT: 60 req/min
IDEMPOTENCY: request_id; 5min cache
RETRY: 1 on 429, 2 on 5xx
Tool 2: submit_full_application
PURPOSE: Hard-pull + full KYC + documents to chosen lender
SLA: p50 ≤ 3000ms, p95 ≤ 8000ms, p99 ≤ 15000ms
RATE LIMIT: 30 req/min
IDEMPOTENCY: request_id
RETRY: No retry on application submission
Tool 3: setup_repayment_mandate
PURPOSE: eNACH / UPI Autopay mandate for EMI
SLA: p50 ≤ 2000ms, p95 ≤ 5000ms
RATE LIMIT: 30 req/min
IDEMPOTENCY: application_id
RETRY: No retry
Tool 4: accept_and_disburse
PURPOSE: Final acceptance + disbursement to applicant's bank account
SLA: p50 ≤ 2500ms, p95 ≤ 7000ms, p99 ≤ 15000ms
RATE LIMIT: 30 req/min
IDEMPOTENCY: application_id; single-shot disbursement
RETRY: No retry
Tool 5: cancel_pre_disbursement
PURPOSE: Cancel application before disbursement
SLA: p50 ≤ 1500ms, p95 ≤ 4000ms
RATE LIMIT: 45 req/min
IDEMPOTENCY: application_id
RETRY: 1 on 5xx
SECTION 5 — RESPONSE SHAPE
LoanOffer
LoanOffer:
offer_id: { type: string, constraint: REQUIRED, opaque }
lender:
lender_id: { type: string, constraint: REQUIRED }
name: { type: string, constraint: REQUIRED }
lender_type: { type: enum, constraint: REQUIRED, STRICT ENUM §6, values: [scheduled_commercial_bank, small_finance_bank, nbfc_systemic, nbfc_other, fintech_nbfc_partner] }
rbi_registration_number: { type: string, constraint: REQUIRED }
ratings:
crisil_long_term: { type: string, constraint: REQUIRED nullable }
icra_long_term: { type: string, constraint: REQUIRED nullable }
care_long_term: { type: string, constraint: REQUIRED nullable }
is_pre_approved: { type: boolean, constraint: REQUIRED }
pa_offer_window_days: { type: int, constraint: REQUIRED, ≥0, semantics: "0 when not pre-approved" }
loan_amount_offered_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
loan_amount_max_eligible_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
tenure_months: { type: int, constraint: REQUIRED, 3-84 }
rate_type: { type: enum, constraint: REQUIRED, STRICT ENUM §6 }
interest_rate_pct: { type: float, constraint: REQUIRED, 0-36 }
apr_pct: { type: float, constraint: REQUIRED, 0-50, semantics: "All-in APR per RBI KFS rules — includes processing fee, insurance if mandatory, etc." }
emi_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
total_interest_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
total_repayment_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
fees:
processing_fee_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
processing_fee_pct: { type: float, constraint: REQUIRED, 0-5 }
gst_on_fees_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
documentation_fee_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
stamp_duty_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
insurance_premium_bundled_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0, semantics: "0 unless user opts in; never mandatory per RBI 2023" }
prepayment_terms:
full_prepayment_charge_pct: { type: float, constraint: REQUIRED, 0-5 }
part_prepayment_charge_pct: { type: float, constraint: REQUIRED, 0-5 }
part_prepayment_allowed_after_months: { type: int, constraint: REQUIRED, ≥0 }
full_prepayment_allowed_after_months: { type: int, constraint: REQUIRED, ≥0 }
floating_rate_no_charge: { type: boolean, constraint: REQUIRED, semantics: "RBI mandate: floating-rate retail loans no prepayment charge" }
disbursement:
estimated_disbursement_hours: { type: int, constraint: REQUIRED, 0-168 }
instant_disbursement_possible: { type: boolean, constraint: REQUIRED }
disbursement_mode: { type: enum, constraint: REQUIRED, STRICT ENUM §6, values: [neft, imps, rtgs] }
validity_minutes: { type: int, constraint: REQUIRED, 15-43200, semantics: "Offer lock window" }
hard_pull_required_on_acceptance: { type: boolean, constraint: REQUIRED }
key_fact_statement_url: { type: string, constraint: REQUIRED, HTTPS URL, semantics: "RBI-mandated KFS" }
loan_agreement_template_url: { type: string, constraint: REQUIRED, HTTPS URL }
partner_reference:
source: { type: string, constraint: REQUIRED }
deeplink: { type: string, constraint: REQUIRED, HTTPS URL }
ApplicationResult
ApplicationResult:
application_id: { type: string, constraint: REQUIRED }
offer_id: { type: string, constraint: REQUIRED }
status:
type: enum
constraint: REQUIRED, STRICT ENUM §6
values: [approved_at_offered_terms, approved_revised_terms, conditional_approval_docs_pending, in_underwriting, declined_credit, declined_policy, declined_income, withdrawn_by_applicant]
revised_offer:
type: object
constraint: REQUIRED nullable
shape: same as LoanOffer.* (amount / rate / fees / EMI)
underwriting_decision_reason_code:
type: enum
constraint: REQUIRED nullable, STRICT ENUM §6
values: [credit_score_low, dpd_recent, foir_exceeded, income_unverified, employer_uncovered, bureau_thin_file, fraud_flag, policy_decline_other]
decision_valid_until: { type: string, constraint: REQUIRED, ISO_DATETIME }
documents_pending: { type: array<string>, constraint: REQUIRED, may be empty }
kyc_status: { type: enum, constraint: REQUIRED, STRICT ENUM §6, values: [verified, video_kyc_pending, manual_pending, failed] }
MandateResult
MandateResult:
mandate_id: { type: string, constraint: REQUIRED }
application_id: { type: string, constraint: REQUIRED }
type: { type: enum, constraint: REQUIRED, STRICT ENUM §6, values: [enach, upi_autopay] }
status: { type: enum, constraint: REQUIRED, STRICT ENUM §6, values: [active, pending, rejected, cancelled] }
max_amount_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
emi_amount_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
start_date: { type: string, constraint: REQUIRED, ISO_DATE }
end_date: { type: string, constraint: REQUIRED, ISO_DATE }
bank_ifsc: { type: string, constraint: REQUIRED }
DisbursementResult
DisbursementResult:
application_id: { type: string, constraint: REQUIRED }
loan_account_number: { type: string, constraint: REQUIRED }
disbursed_amount_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
net_disbursed_after_fees_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
disbursed_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
expected_credit_in_bank_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
disbursement_mode: { type: enum, constraint: REQUIRED, STRICT ENUM §6 }
utr_reference: { type: string, constraint: REQUIRED }
first_emi_date: { type: string, constraint: REQUIRED, ISO_DATE }
emi_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
emi_schedule_url: { type: string, constraint: REQUIRED, HTTPS URL }
loan_agreement_signed_url: { type: string, constraint: REQUIRED, HTTPS URL }
welcome_kit_url: { type: string, constraint: REQUIRED, HTTPS URL }
CancellationResult
CancellationResult:
application_id: { type: string, constraint: REQUIRED }
cancelled_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
status: { type: enum, constraint: REQUIRED, STRICT ENUM §6, values: [cancelled_pre_disbursement, cancellation_failed_post_disbursement] }
refund_amount_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
refund_eta_days: { type: int, constraint: REQUIRED, 0-30 }
FORBIDDEN FIELDS
paid_placement_score,ad_bid,sponsored_rank,kickback_amountartificial_urgency_text("Offer expires in 1 hour!" without verifiable timer)ai_generated_photofor lender / bank imageryinterest_rate_displayed_excluding_fees(must always show APR per KFS)mandatory_insurance_bundle(RBI 2023 prohibits mandatory credit-life)editor_recommended,tomo_pick,best_for_salariedcommission_padded_processing_feedpa_score_displayed(internal to partner; never to TOMO ingest)usury_rate_above_36_pct(auto-rejected at ingest)
SECTION 6 — CONTROLLED VOCABULARIES
applicant.current_address_type:
values: [owned, rented, parental, company_provided, hostel_pg, other]
applicant.marital_status:
values: [single, married, divorced, widowed]
applicant.employment.type:
values:
salaried_corporate, salaried_government, salaried_psu,
self_employed_professional, self_employed_business,
gig_economy, contractual, retired, homemaker
applicant.employment.industry:
values:
information_technology, financial_services, manufacturing, retail,
healthcare, education, government, defence, hospitality, real_estate,
media_entertainment, agriculture, logistics, energy, telecom, e_commerce,
consulting, legal_services, other
applicant.employment.company_category:
values:
listed_or_psu: "Listed company or PSU (lowest rate)"
private_ltd_top_tier: "Top-tier private (e.g. unicorn, MNC subsidiary)"
private_ltd_other: "Other private limited"
partnership_or_llp: "Partnership / LLP"
proprietorship: "Sole proprietorship"
loan_request.purpose:
values:
wedding, medical_emergency, education_self_or_family, home_renovation,
debt_consolidation, business_personal_use, travel_holiday,
consumer_durable, vehicle_down_payment, other_disclosed
loan_request.rate_type_preference / rate_type:
values: [fixed, floating]
loan_request.first_emi_date_preference:
values: [salary_date, mid_month, custom_date]
lender_type:
values: [scheduled_commercial_bank, small_finance_bank, nbfc_systemic, nbfc_other, fintech_nbfc_partner]
disbursement_mode:
values: [neft, imps, rtgs]
ApplicationResult.status:
values: [approved_at_offered_terms, approved_revised_terms, conditional_approval_docs_pending, in_underwriting, declined_credit, declined_policy, declined_income, withdrawn_by_applicant]
underwriting_decision_reason_code:
values: [credit_score_low, dpd_recent, foir_exceeded, income_unverified, employer_uncovered, bureau_thin_file, fraud_flag, policy_decline_other]
kyc_status: [verified, video_kyc_pending, manual_pending, failed]
MandateResult.type: [enach, upi_autopay]
MandateResult.status: [active, pending, rejected, cancelled]
CancellationResult.status: [cancelled_pre_disbursement, cancellation_failed_post_disbursement]
SECTION 7 — TTBS DIMENSIONS
TIME (weight = 0.25):
signals_used:
- is_pre_approved (TRUE preferred when user fits)
- instant_disbursement_possible
- estimated_disbursement_hours
- hard_pull_required_on_acceptance (FALSE preferred where possible)
weighting:
pre_approved: 0.35
instant: 0.30
hours: 0.25
no_hard_pull: 0.10
TASTE (weight = 0.10):
signals_used:
- lender matches preferred_lenders
- lender matches user's existing bank relationship (account holder bonus)
- lender_type matches user preference (scheduled bank vs NBFC)
weighting:
preferred_lender: 0.45
existing_bank: 0.35
lender_type_pref: 0.20
BUDGET (weight = 0.40):
signals_used:
- apr_pct (THE primary signal — includes all fees)
- fees.processing_fee_inr
- prepayment_terms (no-charge prepayment preferred)
- insurance_premium_bundled_inr (zero preferred)
weighting:
apr: 0.70
processing_fee: 0.15
prepayment_flex: 0.10
insurance_zero: 0.05
user_band_handling:
ok: cheapest APR
good: balanced APR + flexibility
great: APR + flexible prepayment + zero bundled insurance
SAFETY (weight = 0.25):
signals_used:
- lender.lender_type (scheduled bank > small finance bank > systemic NBFC > other NBFC > fintech)
- lender.ratings (CRISIL / ICRA / CARE — investment-grade preferred)
- lender.rbi_registration_number present and current
- key_fact_statement_url present and KFS-compliant
- fees disclosed transparently (no surprise charges later)
weighting:
lender_type: 0.40
ratings: 0.25
rbi_active: 0.15
kfs_compliant: 0.10
transparency: 0.10
user_band_handling:
fast: relax to systemic NBFC + B-rated
balanced: scheduled bank or systemic NBFC with A-rating
great: scheduled bank with AAA rating only
Locked weights per finance.apply_personal_loan: time 0.25 / taste 0.10 / budget 0.40 / safety 0.25. Budget-heavy because APR variance across lenders for the same applicant can be 6 percentage points — directly translatable to material rupee impact.
SECTION 8 — COMPLETION CONTRACT
POST /api/v1/cpc/mcp_provider/<your_partner_id>
Body:
{
"intent": "finance.apply_personal_loan",
"external_id": "<application_id>",
"request_id": "<request_id>",
"amount_inr": 12000, // NET partner / DSA commission only (typically 1.5-3% of disbursed amount)
"gst_inr": 2160,
"tips_inr": 0,
"pass_through_inr": 500000, // disbursed loan amount; goes to applicant; NOT supplier revenue
"closed_at": "2026-05-15T11:20:00+05:30",
"status": "completed",
"lender_id": "hdfc_bank",
"loan_amount_disbursed_inr": 500000,
"tenure_months": 36,
"apr_pct": 11.99,
"is_pre_approved": false
}
Important: amount_inr = partner / DSA commission only (typically 1.5-3% of disbursed amount). Disbursed loan amount goes to applicant — not in amount_inr. TOMO charges 10% × commission only.
HMAC-SHA256, 5-min replay, NET-only commission.
SECTION 9 — WIDGET
PersonalLoanWidget (planned). Interim: comparison ListingsWidget.
Field mapping:
- LoanOffer.lender.name + logo → header
- lender_type → eyebrow ("Scheduled Bank" / "NBFC")
- apr_pct → big price ("X% APR")
- interest_rate_pct + processing_fee_pct → breakdown line
- emi_inr → "EMI ₹X" pill
- tenure_months → "X months" pill
- is_pre_approved → green "Pre-approved" badge
- instant_disbursement_possible → green "Instant" badge
- prepayment_terms.floating_rate_no_charge → "Free prepayment" pill
- key_fact_statement_url → "View KFS" link (mandatory)
- validity_minutes → countdown when <60min
SECTION 10 — CACHING POLICY
| Call | TTL | Rationale |
|---|---|---|
| search_loan_offers | 5min | Pre-qualification offers stable for short window |
| submit_full_application | NO CACHE | — |
| setup_repayment_mandate | NO CACHE | — |
| accept_and_disburse | NO CACHE | Single-shot |
| cancel_pre_disbursement | NO CACHE | — |
| Lender static (ratings, registration) | 24h | Updated by rating agencies periodically |
| KFS / Agreement templates | 7d | Versioned |
SECTION 11 — ERROR CODES
| Code | HTTP | Meaning | Retry |
|---|---|---|---|
INVALID_REQUEST |
400 | Malformed | No |
RATE_LIMITED |
429 | Throttle | 1, 2s |
INTERNAL_ERROR |
500 | Partner failure | 2, exp |
SIGNATURE_INVALID |
401 | HMAC fail | No |
CREDIT_BUREAU_CONSENT_MISSING |
422 | Cannot fetch bureau | No |
BUREAU_PULL_FAILED |
503 | Bureau API down | 1 retry, 5s |
THIN_BUREAU_FILE |
422 | Insufficient credit history | No |
AGE_OUT_OF_RANGE |
422 | Applicant age outside lender band | No |
INCOME_BELOW_THRESHOLD |
422 | Net income below lender floor | No |
EMPLOYER_NOT_COVERED |
422 | Employer outside lender's approved list | No |
FOIR_EXCEEDED |
422 | FOIR cap exceeded | No |
AMOUNT_OUT_OF_RANGE |
422 | Requested amount outside product band | No |
TENURE_OUT_OF_RANGE |
422 | Tenure outside lender's offer | No |
KYC_FAILED |
422 | KYC mismatch | No |
MANDATE_REQUIRED |
422 | Disbursement without active mandate | No (mandate tool first) |
BANK_ACCOUNT_INVALID |
422 | IFSC / account invalid | No |
DISBURSEMENT_BLOCKED |
422 | Internal compliance hold | No |
LENDER_DOWN |
503 | Lender API unavailable | 1 retry, 2s |
OFFER_EXPIRED |
410 | Offer past validity window | No |
DUPLICATE_APPLICATION_RECENT |
422 | Recent active app at same lender | No |
SECTION 12 — SANDBOX → PRODUCTION CHECKLIST
[ ] All five tools implemented
[ ] At least 6 lenders in catalog (mix of banks + NBFCs)
[ ] All RBI registration numbers verifiable on rbi.org.in
[ ] APR includes ALL fees per RBI KFS Master Direction
[ ] key_fact_statement_url returns RBI-compliant KFS for the offer
[ ] No mandatory credit-life insurance bundle (RBI 2023)
[ ] Floating-rate retail loans: no prepayment charge
[ ] Soft pull at quote stage; hard pull only on acceptance
[ ] AA (Account Aggregator) consent flow integrated if income from bank statements
[ ] All controlled vocabularies respected
[ ] HMAC signing verified
[ ] amount_inr in CPC is partner COMMISSION only (NET), NOT disbursed amount
[ ] pass_through_inr correctly captures disbursed amount to applicant
[ ] No usury rates above 36% APR per NBFC ceiling
[ ] No forbidden fields anywhere
[ ] No "Top Pick" / "Recommended" / "Best for Salaried" badges
[ ] SLA p95 met
[ ] DSA (Direct Selling Agent) / connector arrangement with each lender uploaded
[ ] Compliance: GSTIN, DSA agreement(s), privacy policy, grievance officer
[ ] Customer support: working hours + grievance escalation matrix
[ ] DPDP Act compliance: explicit consent for bureau pull + data sharing
SECTION 13 — ANTI-FABRICATION RULES
RULE 1: No paid_placement / ad / kickback fields. DSA commissions vary
widely; TOMO audits monthly.
RULE 2: apr_pct MUST include processing fee, stamp duty, mandatory
insurance, and any other fees per RBI's KFS Master Direction
(Aug 2023). Displaying nominal interest rate without APR =
customer-harm + regulatory breach + suspension.
RULE 3: key_fact_statement_url must return an RBI-compliant KFS for
the specific offer (matching terms shown). Marketing page in
this slot = immediate suspension.
RULE 4: insurance_premium_bundled_inr cannot be non-zero unless user
explicitly opted in. RBI 2023 prohibits mandatory credit-life
bundling. Any "auto-included" insurance = suspension.
RULE 5: prepayment charge fields must comply with RBI's floating-rate
retail loan rule: zero prepayment charge for floating-rate
loans to individuals. Misstating = customer-harm.
RULE 6: lender.lender_type must accurately reflect RBI classification.
Marketing a fintech as "bank-grade" without scheduled-bank
status = suspension.
RULE 7: lender.rbi_registration_number must be current. Lenders whose
license is suspended / cancelled removed from catalog.
RULE 8: No interest rate above 36% APR for personal loans. NBFCs
operating in this band are higher-risk and route through
v1.1+ separate intent (with explicit user disclosure).
RULE 9: Soft pull at quote stage; hard pull only AFTER user explicitly
accepts an offer. Hard-pulling at quote stage damages credit
score = customer-harm + suspension.
RULE 10: artificial_urgency_text forbidden ("Approval in 60 seconds
if you apply now!" without basis).
RULE 11: No AI-generated lender / bank imagery.
RULE 12: amount_inr in CPC is partner / DSA commission only — NEVER
disbursed loan amount.
RULE 13: No "Top Pick" / "Recommended" / "Best Rate" badges. Lending
recommendation is regulated; TOMO orchestrates source-blind
TTBS only.
RULE 14: information_completeness_score (hidden ranking factor, weight
0.10) rewards full §5 shape.
RULE 15: Fees disclosed in §5 must match KFS exactly. Surprise charges
at acceptance = immediate suspension.
RULE 16: validity_minutes must be respected. If user accepts within
window, terms cannot change.
RULE 17: For pre-approved (PA) offers, partners must honour the
disclosed terms throughout pa_offer_window_days. Withdrawing
a PA offer without policy basis = customer-harm + suspension.
VERSION HISTORY
v1.0.0 — 2026-05-13 — Initial spec. Unsecured personal loan only;
secured / vehicle / business / education /
BNPL via separate intents. APR enforced per RBI
KFS rules. Soft-pull-first underwriting.
NET commission base. Budget-dominant TTBS
reflecting APR variance impact.