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_home_loan

Intent Spec — finance.apply_home_loan

FULL ID:       finance.apply_home_loan
VERSION:       v1.0.0
STATUS:        draft
LAST UPDATED:  2026-05-13
DOMAIN:        finance
PRIMARY AGENT: FinanceAgent
TTBS WEIGHTS:  time=0.15 taste=0.10 budget=0.40 safety=0.35

User applies for a secured home loan (purchase, construction, plot, balance transfer, top-up, or loan-against-property). Covers eligibility check, property document collection, legal + technical valuation orchestration, KYC, sanction, disbursement (tranche-based for construction), and registration. Distinct from personal / vehicle loans.

Partner exemplars: BankBazaar, PaisaBazaar, MagicBricks Home Loan, HDFC Ltd (Bank), SBI Home Loan, LIC Housing Finance, ICICI Bank Home Loan, Axis Bank Home Loan, Tata Capital Housing, PNB Housing Finance, Bajaj Housing Finance.


SECTION 1 — INTENT IDENTITY

User wants a secured housing finance facility. Distinct from:

  • finance.apply_personal_loan — unsecured
  • finance.apply_credit_card — revolving credit
  • Vehicle loans — v1.1+
  • Loan against fixed deposits — v1.1+
  • Reverse mortgage — v1.1+
  • Loan against jewellery — v1.1+ gold loan
  • Bridge loan — v1.1+

Single intent per application. Joint applications use co_applicants array.


SECTION 2 — NATURAL LANGUAGE COVERAGE

CLASSIFIES IN

  • "Home loan ₹50 lakh, 20 years"
  • "Best home loan interest rate for salaried"
  • "Balance transfer my home loan to HDFC"
  • "Home loan top up"
  • "Plot purchase loan"
  • "Home construction loan"
  • "Loan against my flat"
  • "Self-employed home loan options"
  • "Joint home loan with my wife"
  • "Pre-approved home loan eligibility"

CLASSIFIES OUT — BORDERLINE NO

  • "Personal loan" → finance.apply_personal_loan
  • "Credit card" → finance.apply_credit_card
  • "Reverse mortgage" → v1.1+
  • "Property to buy" → marketplace.buy_used_car is for vehicles only; v1.1+ for real-estate listings
  • "EMI calculator" — non-transactional research
  • "Foreclose home loan" → v1.1+

MULTI-INTENT TRIGGERS

  • "Home loan + insurance" → finance.apply_home_loan + finance.buy_term_insurance (loan-protection)
  • "Home loan + financial advisor" → finance.apply_home_loan + finance.book_financial_advisor_session
  • "Home loan + tax consultation" → finance.apply_home_loan + finance.book_tax_consultation (§24, §80C deductions)

SECTION 3 — INPUT (TOMO → PROVIDER)

{
  "intent": "finance.apply_home_loan",
  "request_id": "req_01J9Z...",
  "user_locale": "en-IN",
  "user_currency": "INR",
  "user_location": { "lat": 17.4475, "lng": 78.3563, "city": "Hyderabad", "pincode": "500032" },
  "loan_purpose": "purchase",                          // STRICT ENUM §6
  "primary_applicant":  {
    "first_name": "Rakesh", "last_name": "Kumar",
    "date_of_birth": "1985-04-18",
    "gender": "male",
    "pan_last4": "5678",
    "mobile_e164": "+919876543210",
    "marital_status": "married",
    "employment":  {
      "type": "salaried_corporate",                     // STRICT ENUM §6
      "industry": "information_technology",             // STRICT ENUM §6
      "designation": "Director of Engineering",
      "years_at_current_employer": 5,
      "total_work_experience_years": 14,
      "net_monthly_income_inr": 280000,
      "gross_annual_income_inr": 4000000,
      "company_category": "listed_or_psu"
    },
    "obligations":  {
      "existing_emi_monthly_inr": 22000,
      "credit_cards_outstanding_inr": 60000,
      "consent_for_credit_bureau_pull": true,
      "consent_for_aa_aggregator_pull": true
    }
  },
  "co_applicants": [
    {
      "first_name": "Anita", "last_name": "Kumar",
      "date_of_birth": "1988-09-12",
      "relationship_to_primary": "spouse",
      "pan_last4": "1234",
      "employment_type": "salaried_corporate",
      "net_monthly_income_inr": 180000
    }
  ],
  "property":  {
    "property_type": "ready_apartment",                  // STRICT ENUM §6
    "occupancy_status": "self_occupied_post_disbursement", // STRICT ENUM §6
    "city": "Hyderabad",
    "pincode": "500032",
    "state": "TS",
    "agreement_value_inr": 8500000,
    "expected_market_value_inr": 9000000,
    "ready_for_possession": true,
    "completion_certificate_available": true,
    "rera_registration_number": "P02400001234",
    "builder_or_seller_name": "ABC Developers Pvt Ltd",
    "approved_project_by_lender": []
  },
  "loan_request":  {
    "amount_inr": 6500000,
    "tenure_months": 240,
    "rate_type_preference": "floating",                  // STRICT ENUM §6
    "want_overdraft_facility": false,
    "step_up_emi": false,
    "first_emi_date_preference": "salary_date",
    "want_balance_transfer": false,
    "existing_loan_outstanding_inr": 0,
    "existing_loan_lender": null
  },
  "ttbs_user_band":  {
    "time":   "balanced",
    "taste":  "balanced",
    "budget": "great",
    "safety": "great"
  },
  "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_home_loan
loan_purpose enum REQUIRED, STRICT ENUM §6 Drives valuation + tenure rules
primary_applicant.pan_last4 string REQUIRED, 4 digits
primary_applicant.employment.type enum REQUIRED, STRICT ENUM §6 Salaried / self-employed underwriting differ
co_applicants array REQUIRED, may be empty, max 3 Income clubbed for eligibility
property.property_type enum REQUIRED, STRICT ENUM §6 Drives LTV cap
property.rera_registration_number string REQUIRED nullable Mandatory for under-construction; nullable for resale
property.agreement_value_inr int REQUIRED, INR_INTEGER, ≥0 Drives LTV
property.expected_market_value_inr int REQUIRED, INR_INTEGER, ≥0 Independent estimate
loan_request.amount_inr int REQUIRED, INR_INTEGER, ≥100000
loan_request.tenure_months int REQUIRED, 36-360 Up to 30y
loan_request.rate_type_preference enum REQUIRED, STRICT ENUM §6 fixed / floating / hybrid
loan_request.want_balance_transfer bool REQUIRED When TRUE, existing_loan_* REQUIRED non-null

Anti-fabrication preamble: APR must include all fees + bundled charges per RBI KFS Master Direction. LTV cap per RBI norms (up to ₹30L: 90%; ₹30-75L: 80%; >₹75L: 75%). No mandatory life-insurance bundle (RBI 2023). Floating-rate retail home loans no prepayment charge per RBI directive.


SECTION 4 — PROVIDER TOOLS

Tool 1: search_home_loan_offers

PURPOSE:      Up to 10 offers from eligible lenders (soft-pull-based)
SLA:          p50 ≤ 1200ms, p95 ≤ 4000ms, p99 ≤ 8000ms
RATE LIMIT:   45 req/min
IDEMPOTENCY:  request_id; 10min cache
RETRY:        1 on 429, 2 on 5xx

Tool 2: initiate_application_and_valuation

PURPOSE:      Full app + property doc upload + legal & technical valuation booking
SLA:          p50 ≤ 3500ms, p95 ≤ 9000ms, p99 ≤ 18000ms
RATE LIMIT:   20 req/min
IDEMPOTENCY:  request_id
RETRY:        No retry

Tool 3: schedule_valuation_visits

PURPOSE:      Book legal + technical valuation visits at property
SLA:          p50 ≤ 1500ms, p95 ≤ 4000ms
RATE LIMIT:   30 req/min
IDEMPOTENCY:  application_id
RETRY:        1 on 5xx

Tool 4: accept_sanction_and_disburse

PURPOSE:      Sanction acceptance + first / tranche disbursement
SLA:          p50 ≤ 3000ms, p95 ≤ 8000ms, p99 ≤ 20000ms
RATE LIMIT:   20 req/min
IDEMPOTENCY:  application_id (single sanction) / tranche_id (multi-tranche)
RETRY:        No retry

Tool 5: cancel_pre_disbursement

PURPOSE:      Cancel application pre-disbursement
SLA:          p50 ≤ 1500ms, p95 ≤ 4000ms
RATE LIMIT:   30 req/min
IDEMPOTENCY:  application_id
RETRY:        1 on 5xx

SECTION 5 — RESPONSE SHAPE

HomeLoanOffer

HomeLoanOffer:
  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 }
    rbi_registration_number: { type: string, constraint: REQUIRED }
    nhb_registration_number: { type: string, constraint: REQUIRED nullable, semantics: "Required for housing finance companies" }
    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 }

  loan_amount_offered_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
  loan_amount_max_eligible_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
  ltv_pct: { type: float, constraint: REQUIRED, 0-90 }
  tenure_months: { type: int, constraint: REQUIRED, 36-360 }
  rate_type: { type: enum, constraint: REQUIRED, STRICT ENUM §6 }

  interest_rate_pct: { type: float, constraint: REQUIRED, 0-15 }
  rate_benchmark:
    type: enum
    constraint: REQUIRED, STRICT ENUM §6
    values: [eblr, mclr, base_rate_legacy, fixed_promised]
  rate_spread_pct: { type: float, constraint: REQUIRED, -2 to 5, semantics: "Bps above/below benchmark" }
  apr_pct: { type: float, constraint: REQUIRED, 0-20 }

  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-2 }
    gst_on_fees_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    legal_and_technical_valuation_fee_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    documentation_fee_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    cersai_charge_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    franking_charge_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" }

  prepayment_terms:
    full_prepayment_charge_pct: { type: float, constraint: REQUIRED, 0-3 }
    part_prepayment_charge_pct: { type: float, constraint: REQUIRED, 0-3 }
    part_prepayment_min_pct: { type: float, constraint: REQUIRED, 0-50 }
    part_prepayment_max_per_year: { type: int, constraint: REQUIRED, ≥0 }
    floating_rate_no_charge: { type: boolean, constraint: REQUIRED, semantics: "RBI mandate: floating-rate retail no prepayment charge" }

  disbursement:
    estimated_sanction_days: { type: int, constraint: REQUIRED, 1-30 }
    estimated_disbursement_days_post_sanction: { type: int, constraint: REQUIRED, 1-30 }
    disbursement_mode: { type: enum, constraint: REQUIRED, STRICT ENUM §6, values: [neft, rtgs] }
    tranche_based: { type: boolean, constraint: REQUIRED, semantics: "TRUE for construction / plot+construction" }
    max_tranches: { type: int, constraint: REQUIRED, 1-12 }

  features:
    overdraft_facility_available: { type: boolean, constraint: REQUIRED }
    overdraft_facility_charge_pct: { type: float, constraint: REQUIRED, 0-1 }
    flexible_emi: { type: boolean, constraint: REQUIRED }
    step_up_emi_available: { type: boolean, constraint: REQUIRED }
    top_up_loan_available_after_months: { type: int, constraint: REQUIRED, ≥0 }
    balance_transfer_supported: { type: boolean, constraint: REQUIRED }

  pmay_eligibility:
    is_eligible_pmay: { type: boolean, constraint: REQUIRED }
    pmay_category: { type: enum, constraint: REQUIRED nullable, STRICT ENUM §6, values: [ews, lig, mig_1, mig_2, none] }
    estimated_subsidy_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }

  validity_minutes: { type: int, constraint: REQUIRED, 60-86400 }
  hard_pull_required_on_acceptance: { type: boolean, constraint: REQUIRED }

  key_fact_statement_url: { type: string, constraint: REQUIRED, HTTPS URL }
  loan_agreement_template_url: { type: string, constraint: REQUIRED, HTTPS URL }

  partner_reference:
    source: { type: string, constraint: REQUIRED }
    deeplink: { type: string, constraint: REQUIRED, HTTPS URL }

ValuationSchedule

ValuationSchedule:
  schedule_id: { type: string, constraint: REQUIRED }
  application_id: { type: string, constraint: REQUIRED }
  legal_valuation:
    scheduled_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
    valuer_firm: { type: string, constraint: REQUIRED }
    valuer_contact_phone: { type: string, constraint: REQUIRED, E.164 }
    expected_completion_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
  technical_valuation:
    scheduled_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
    valuer_firm: { type: string, constraint: REQUIRED }
    valuer_contact_phone: { type: string, constraint: REQUIRED, E.164 }
    expected_completion_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
  is_combined_visit: { type: boolean, constraint: REQUIRED, semantics: "Single visit covering both" }
  documents_required_at_visit: { type: array<string>, constraint: REQUIRED, may NOT be empty }

SanctionResult

SanctionResult:
  application_id: { type: string, constraint: REQUIRED }
  offer_id: { type: string, constraint: REQUIRED }
  sanction_letter_url: { type: string, constraint: REQUIRED, HTTPS URL }
  sanctioned_amount_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
  sanctioned_ltv_pct: { type: float, constraint: REQUIRED, 0-90 }
  sanctioned_tenure_months: { type: int, constraint: REQUIRED, 36-360 }
  sanctioned_interest_rate_pct: { type: float, constraint: REQUIRED, 0-15 }
  sanctioned_emi_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
  legal_clearance_status:
    type: enum
    constraint: REQUIRED, STRICT ENUM §6
    values: [clear, observations_resolvable, encumbrance_flagged, title_unclear]
  technical_clearance_status:
    type: enum
    constraint: REQUIRED, STRICT ENUM §6
    values: [clear, minor_observations, major_observations, declined]
  sanction_valid_until: { type: string, constraint: REQUIRED, ISO_DATE }
  conditions_precedent: { type: array<string>, constraint: REQUIRED, may be empty }

DisbursementResult

DisbursementResult:
  application_id: { type: string, constraint: REQUIRED }
  loan_account_number: { type: string, constraint: REQUIRED }
  tranche_number: { type: int, constraint: REQUIRED, 1-12 }
  total_tranches_for_application: { type: int, constraint: REQUIRED, 1-12 }
  disbursed_amount_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
  cumulative_disbursed_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
  beneficiary_name: { type: string, constraint: REQUIRED, semantics: "Builder / seller, NOT applicant" }
  beneficiary_account_last4: { type: string, constraint: REQUIRED }
  disbursed_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
  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 }

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_sanction, cancelled_post_sanction_pre_disbursement, cancellation_failed_post_disbursement] }
  refund_amount_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
  refund_breakdown:
    processing_fee_refundable_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    valuation_fee_refundable_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    cersai_franking_refundable_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
  refund_eta_days: { type: int, constraint: REQUIRED, 0-45 }

FORBIDDEN FIELDS

  • paid_placement_score, ad_bid, sponsored_rank, kickback_amount
  • artificial_urgency_text ("Rate goes up tomorrow!" without RBI / lender notification)
  • ai_generated_photo for lender / valuer imagery
  • interest_rate_displayed_excluding_fees (APR mandatory)
  • mandatory_insurance_bundle (RBI 2023 prohibits)
  • editor_recommended, tomo_pick, best_for_first_home_buyer
  • commission_padded_processing_fee
  • inflated_market_value for property (RICS / lender-empanelled valuer required)
  • pre_sanction_letter_without_underwriting (regulator-flagged practice)

SECTION 6 — CONTROLLED VOCABULARIES

loan_purpose:
  values:
    purchase:               "Ready / under-construction property purchase"
    construction:           "Construction on owned plot"
    plot_purchase:          "Plot only"
    plot_plus_construction: "Plot + construction combined"
    balance_transfer:       "Transfer existing home loan to new lender"
    top_up_on_existing:     "Top-up on existing home loan"
    loan_against_property:  "LAP (own residential / commercial)"
    renovation:             "Major renovation; up to ₹15L typically"

primary_applicant.employment.type:
  values:
    salaried_corporate, salaried_government, salaried_psu,
    self_employed_professional, self_employed_business, retired

primary_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

primary_applicant.employment.company_category:
  values: [listed_or_psu, private_ltd_top_tier, private_ltd_other, partnership_or_llp, proprietorship]

co_applicants[].relationship_to_primary:
  values: [spouse, parent, child, sibling]

property.property_type:
  values:
    ready_apartment, ready_independent_house, ready_villa,
    under_construction_apartment, under_construction_villa,
    resale_apartment, resale_independent_house,
    plot_residential, plot_with_construction_plan,
    commercial_office, commercial_shop

property.occupancy_status:
  values:
    self_occupied_immediate, self_occupied_post_disbursement,
    rented_out, intended_rental, vacant

property.state: [AP, AR, AS, BR, CG, GA, GJ, HR, HP, JH, KA, KL, MP, MH, MN, ML, MZ, NL, OD, PB, RJ, SK, TN, TS, TR, UP, UK, WB, AN, CH, DN, DD, DL, JK, LA, LD, PY]

loan_request.rate_type_preference / rate_type:
  values:
    floating: "Linked to benchmark (EBLR / MCLR)"
    fixed:    "Fixed for full or fixed period"
    hybrid:   "Fixed for first N years, floating thereafter"

rate_benchmark:
  values: [eblr, mclr, base_rate_legacy, fixed_promised]

loan_request.first_emi_date_preference: [salary_date, mid_month, custom_date]

lender_type:
  values: [scheduled_commercial_bank, housing_finance_company_nhb, small_finance_bank, nbfc_systemic, nbfc_other]

disbursement_mode: [neft, rtgs]

pmay_category: [ews, lig, mig_1, mig_2, none]

legal_clearance_status: [clear, observations_resolvable, encumbrance_flagged, title_unclear]
technical_clearance_status: [clear, minor_observations, major_observations, declined]

CancellationResult.status:
  values: [cancelled_pre_sanction, cancelled_post_sanction_pre_disbursement, cancellation_failed_post_disbursement]

SECTION 7 — TTBS DIMENSIONS

TIME (weight = 0.15):
  signals_used:
    - is_pre_approved
    - estimated_sanction_days
    - estimated_disbursement_days_post_sanction
  weighting:
    pre_approved: 0.45
    sanction_speed: 0.35
    disbursement_speed: 0.20

TASTE (weight = 0.10):
  signals_used:
    - lender matches preferred_lenders
    - existing bank relationship
    - lender_type matches user preference
  weighting:
    preferred_lender: 0.50
    existing_bank: 0.35
    lender_type_pref: 0.15

BUDGET (weight = 0.40):
  signals_used:
    - apr_pct (primary)
    - interest_rate_pct
    - fees aggregate
    - prepayment_terms (floating preferred when no-charge)
    - pmay_eligibility (when eligible, subsidy materially shifts ranking)
  weighting:
    apr: 0.60
    fees: 0.20
    prepay_flex: 0.10
    pmay_subsidy: 0.10

SAFETY (weight = 0.35):
  signals_used:
    - lender.lender_type (scheduled bank > HFC > NBFC)
    - lender.ratings (investment-grade preferred)
    - lender.rbi_registration / nhb_registration current
    - key_fact_statement_url present and compliant
    - balance_transfer / top-up policies favourable (option-value over 20y tenure)
    - features.overdraft_facility_available (interest-saving optionality)
  weighting:
    lender_type: 0.35
    ratings: 0.20
    regulator_status: 0.15
    kfs_compliant: 0.10
    long_term_flexibility: 0.10
    overdraft_option: 0.10
  user_band_handling:
    fast: scheduled bank or major HFC with A-rating
    balanced: scheduled bank with A-rating
    great: scheduled bank with AAA rating only

Locked weights per finance.apply_home_loan: time 0.15 / taste 0.10 / budget 0.40 / safety 0.35. APR variance compounded over 20-30y tenure makes Budget dominant; long-tenure relationship makes Safety material.


SECTION 8 — COMPLETION CONTRACT

POST /api/v1/cpc/mcp_provider/<your_partner_id>
Body:
{
  "intent":           "finance.apply_home_loan",
  "external_id":      "<application_id>",
  "request_id":       "<request_id>",
  "amount_inr":       32500,    // NET partner / DSA commission (typically 0.5% of disbursed)
  "gst_inr":          5850,
  "tips_inr":         0,
  "pass_through_inr": 6500000,  // disbursed loan amount; goes to builder / seller
  "closed_at":        "2026-06-20T11:20:00+05:30",
  "status":           "completed",
  "lender_id": "hdfc_bank",
  "loan_amount_disbursed_inr": 6500000,
  "tenure_months": 240,
  "apr_pct": 9.05,
  "ltv_pct": 76.5,
  "is_pmay_eligible": false
}

For tranche-based disbursement (construction loans), partners POST a completion per tranche with the per-tranche commission.

HMAC-SHA256, 5-min replay, NET-only commission.


SECTION 9 — WIDGET

HomeLoanWidget (planned). Interim: comparison ListingsWidget.

Field mapping:
  - HomeLoanOffer.lender.name + logo → header
  - lender_type → eyebrow ("Scheduled Bank" / "HFC")
  - apr_pct → big price ("X% APR")
  - interest_rate_pct → "Rate X%"
  - emi_inr → "EMI ₹X" pill
  - tenure_months → "X yrs" pill
  - ltv_pct → "LTV X%" pill
  - is_pre_approved → green "Pre-approved" badge
  - prepayment_terms.floating_rate_no_charge → "Free prepayment" badge
  - features.overdraft_facility_available → "Overdraft" pill
  - pmay_eligibility.is_eligible_pmay → green "PMAY ₹X subsidy" badge when eligible
  - key_fact_statement_url → "View KFS" link

SECTION 10 — CACHING POLICY

Call TTL Rationale
search_home_loan_offers 10min Offers stable for short window
initiate_application_and_valuation NO CACHE
schedule_valuation_visits NO CACHE Live calendar
accept_sanction_and_disburse NO CACHE Single-shot
cancel_pre_disbursement NO CACHE
Lender static (ratings, registration) 24h
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 No bureau consent No
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 approved list No
FOIR_EXCEEDED 422 FOIR cap exceeded No
LTV_EXCEEDED 422 LTV ask above RBI cap No (UI lowers ask)
PROPERTY_TYPE_NOT_FUNDED 422 Lender doesn't fund this property type No
PROPERTY_CITY_NOT_SERVICED 422 Pincode outside lender geography No
RERA_REGISTRATION_MISSING 422 Under-construction without RERA No
RERA_REGISTRATION_INVALID 422 RERA number not found / expired No
LEGAL_CLEARANCE_FAILED 422 Title unclear / encumbrance flagged No
TECHNICAL_CLEARANCE_FAILED 422 Property fails construction norms No
KYC_FAILED 422 KYC mismatch No
BANK_ACCOUNT_INVALID 422 IFSC / account invalid No
LENDER_DOWN 503 Lender API unavailable 1 retry, 2s
OFFER_EXPIRED 410 Past validity No
SANCTION_EXPIRED 410 Past sanction validity No
CONDITIONS_PRECEDENT_PENDING 422 Disbursement blocked till satisfied No

SECTION 12 — SANDBOX → PRODUCTION CHECKLIST

[ ] All five tools implemented
[ ] At least 6 lenders (mix of scheduled banks + HFCs)
[ ] All RBI / NHB registration numbers verifiable
[ ] APR includes ALL fees per RBI KFS Master Direction
[ ] LTV caps respected per RBI norms (90/80/75% by loan amount band)
[ ] key_fact_statement_url returns RBI-compliant KFS
[ ] No mandatory insurance bundle (RBI 2023)
[ ] Floating-rate retail: no prepayment charge
[ ] Soft pull at quote; hard pull on acceptance only
[ ] Legal + technical valuation by lender-empanelled valuers (not partner-affiliated)
[ ] RERA registration verified for under-construction property
[ ] PMAY eligibility checked correctly (income / property / area criteria)
[ ] Tranche-based disbursement for construction loans
[ ] All controlled vocabularies respected
[ ] HMAC signing verified
[ ] amount_inr in CPC is partner COMMISSION only (NET), NOT disbursed
[ ] pass_through_inr captures disbursed amount to builder / seller
[ ] No forbidden fields anywhere
[ ] No "Top Pick" / "Recommended" badges
[ ] SLA p95 met
[ ] DSA agreements with each lender uploaded
[ ] Compliance: GSTIN, DSA agreements, privacy policy, grievance officer
[ ] Customer support: grievance escalation matrix per RBI ombudsman framework
[ ] DPDP Act compliance: explicit consent for AA / bureau / property docs

SECTION 13 — ANTI-FABRICATION RULES

RULE 1: No paid_placement / ad / kickback fields. Home loan DSA
        commissions are smaller % (0.3-0.7%) but larger absolute rupee
        amounts; bias risk is real. TOMO audits monthly.

RULE 2: apr_pct MUST include processing fee, legal / tech valuation
        fee, CERSAI, franking, stamp duty, and any mandatory insurance
        per RBI KFS Master Direction. Misstating APR by hiding fees =
        immediate suspension.

RULE 3: key_fact_statement_url must return RBI-compliant KFS for the
        specific offer. Marketing brochures = suspension.

RULE 4: insurance_premium_bundled_inr cannot be non-zero unless user
        explicitly opted in. RBI 2023 prohibits mandatory bundling.

RULE 5: prepayment fields must comply with RBI's floating-rate retail
        rule: zero prepayment charge for individual floating-rate
        home loans. Misstating = customer-harm.

RULE 6: ltv_pct cannot exceed RBI cap (90% up to ₹30L; 80% ₹30-75L;
        75% above ₹75L; 60% for plot-only). Above cap = invalid.

RULE 7: lender.lender_type, rbi_registration_number, and
        nhb_registration_number must accurately reflect regulator
        records.

RULE 8: rate_benchmark must reflect actual product structure (EBLR
        per RBI Oct-2019 mandate for floating retail). Misstating
        benchmark = regulatory breach.

RULE 9: rate_spread_pct reset behaviour must follow lender's
        published policy; surprise spread hikes = customer-harm +
        suspension.

RULE 10: Legal + technical valuation must be done by lender-
         empanelled (or RICS-registered) valuers — NOT partners or
         partner-affiliated firms (conflict of interest).

RULE 11: Sanction without underwriting (purely marketing letters
         labelled "sanction") = regulatory breach + suspension.

RULE 12: Disbursement to builder / seller account ONLY (not to
         applicant); for construction, only against verified work
         milestone. Diverting funds = fraud + immediate suspension.

RULE 13: artificial_urgency_text forbidden ("EBLR moves tomorrow!"
         without RBI notification).

RULE 14: No AI-generated lender / valuer imagery.

RULE 15: amount_inr in CPC is partner / DSA commission only — NEVER
         disbursed loan amount.

RULE 16: No "Top Pick" / "Best for First Home Buyer" badges. Lending
         recommendation is regulated; TOMO orchestrates source-blind
         TTBS only.

RULE 17: information_completeness_score (hidden ranking factor,
         weight 0.10) rewards full §5 shape.

RULE 18: PMAY eligibility flag must reflect actual eligibility per
         current PMAY 2.0 criteria (income, property type, carpet
         area, beneficiary norms). Misstating = customer-harm + GoI-
         programme breach.

RULE 19: Fees disclosed in §5 must match KFS exactly. Surprise
         charges at sanction or disbursement = immediate suspension.

RULE 20: For balance-transfer loans, partners cannot quote without
         the existing loan's foreclosure / handover discharge plan
         being documented and verifiable.

VERSION HISTORY

v1.0.0 — 2026-05-13 — Initial spec. Purchase, construction, plot, BT,
                       top-up, LAP under one intent via loan_purpose.
                       APR per RBI KFS. LTV caps per RBI norms. PMAY
                       eligibility surfaced as material rank shifter
                       when applicable. NET commission base. Budget-
                       and-Safety dominant TTBS reflecting tenure-
                       compounded impact.