T
TOMO
Developer Docs
BETA These docs are under partner review. Some features described are roadmap items, not yet shipped. Verify against your sandbox before relying on any contract.
● DRAFTv1.0.0finance.apply_personal_loan

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 property
  • finance.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_amount
  • artificial_urgency_text ("Offer expires in 1 hour!" without verifiable timer)
  • ai_generated_photo for lender / bank imagery
  • interest_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_salaried
  • commission_padded_processing_fee
  • dpa_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.