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.buy_term_insurance

Intent Spec — finance.buy_term_insurance

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

User buys pure-term life insurance (no maturity benefit, payout only on death within policy term). Distinct from endowment, ULIPs, whole-life, annuity, or motor / health insurance. Covers sum-assured comparison, premium discovery, medical underwriting orchestration, IRDAI-mandated disclosures, nominee assignment, policy issuance.

Partner exemplars: Policybazaar, Coverfox, InsuranceDekho, Acko Life, HDFC Life, ICICI Pru Life, Max Life, LIC, Tata AIA Life, Aditya Birla Sun Life.


SECTION 1 — INTENT IDENTITY

User wants to buy a pure-term life insurance policy. Distinct from:

  • finance.buy_health_insurance — separate health/medical cover intent
  • finance.buy_motor_insurance / auto.book_insurance_renewal — motor vehicle cover
  • finance.buy_travel_insurance — short-term travel/trip insurance
  • Investment-linked life products (ULIPs, endowment) — out of scope v1; v1.1+ finance.buy_life_investment
  • Group term life via employer — out of scope v1; partner sales to individuals only
  • Renewal of an existing term policy — same intent (annual renewal flow is identical to first purchase from TOMO's contract POV)

Single intent per applicant. Joint-life policies route to v1.1+ finance.buy_joint_term_insurance.


SECTION 2 — NATURAL LANGUAGE COVERAGE

CLASSIFIES IN

  • "Buy term life insurance for ₹1 crore"
  • "Term plan for my family, 30 years tenure"
  • "Pure protection life cover, no investment"
  • "Cheapest term insurance for a 32-year-old non-smoker"
  • "1 cr term plan with critical illness rider"
  • "I need life insurance to cover my home loan"
  • "Compare HDFC Life vs ICICI Pru term plans"
  • "Term insurance with return of premium"
  • "Life insurance for my husband, he's 38 and earns ₹15L"
  • "Pure-term cover till age 65"

CLASSIFIES OUT — BORDERLINE NO

  • "Buy ULIP / endowment / whole-life" → v1.1+ finance.buy_life_investment
  • "Buy health insurance for my parents" → finance.buy_health_insurance
  • "Renew my car insurance" → auto.book_insurance_renewal
  • "File a death claim" → v1.1+ finance.file_insurance_claim
  • "Surrender my LIC policy" → v1.1+ finance.surrender_life_policy
  • "What's my current policy worth?" — non-transactional; research path

MULTI-INTENT TRIGGERS

  • "Term life + health insurance for the whole family" → finance.buy_term_insurance + finance.buy_health_insurance
  • "Home loan + term cover to match" → finance.apply_home_loan + finance.buy_term_insurance
  • "Term insurance + will preparation" → finance.buy_term_insurance + finance.create_will_or_estate_plan

SECTION 3 — INPUT (TOMO → PROVIDER)

{
  "intent": "finance.buy_term_insurance",
  "request_id": "req_01J9Z...",
  "user_locale": "en-IN",
  "user_currency": "INR",
  "user_location": { "lat": 17.4475, "lng": 78.3563, "city": "Hyderabad", "pincode": "500032" },
  "proposer": {
    "first_name": "Rakesh",
    "last_name": "Kumar",
    "date_of_birth": "1992-04-18",                 // ISO_DATE
    "gender": "male",                                // STRICT ENUM §6
    "tobacco_use": "never",                          // STRICT ENUM §6
    "annual_income_inr": 1500000,                   // INR_INTEGER
    "education": "graduate",                         // STRICT ENUM §6
    "occupation_class": "salaried_corporate",        // STRICT ENUM §6
    "marital_status": "married",                     // STRICT ENUM §6
    "residency_status": "resident_indian",           // STRICT ENUM §6
    "state": "TS",                                   // STRICT ENUM §6
    "pre_existing_conditions": [],                   // STRICT ENUM array §6
    "family_history_flags": []                       // STRICT ENUM array §6
  },
  "life_assured": {
    "is_same_as_proposer": true,
    "first_name": null,
    "last_name": null,
    "date_of_birth": null,
    "gender": null,
    "relationship_to_proposer": null                 // STRICT ENUM §6 when is_same_as_proposer=false
  },
  "cover_preferences": {
    "sum_assured_inr": 10000000,                    // INR_INTEGER (₹1 crore)
    "policy_term_years": 30,                         // 5-50
    "premium_payment_term_years": 30,               // ≤ policy_term_years
    "payout_mode": "lump_sum",                       // STRICT ENUM §6
    "premium_frequency": "annual",                   // STRICT ENUM §6
    "riders_required": ["critical_illness", "accidental_death"],  // STRICT ENUM array §6
    "return_of_premium": false,                      // ROP variant
    "preferred_insurers": []                         // empty = open to all
  },
  "ttbs_user_band": {
    "time":   "flexible",
    "taste":  "balanced",
    "budget": "good",
    "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.buy_term_insurance
proposer.date_of_birth string REQUIRED, ISO_DATE Drives age-banded premium
proposer.gender enum REQUIRED, STRICT ENUM §6 IRDAI mandates gender-specific premium
proposer.tobacco_use enum REQUIRED, STRICT ENUM §6 Smokers pay 40-80% higher premium
proposer.annual_income_inr int REQUIRED, INR_INTEGER Drives max permissible sum-assured (HLV multiple)
proposer.education enum REQUIRED, STRICT ENUM §6 Affects underwriting band
proposer.occupation_class enum REQUIRED, STRICT ENUM §6 Hazardous occupations pay loaded premium
proposer.state enum REQUIRED, STRICT ENUM §6 Indian state codes
proposer.pre_existing_conditions array REQUIRED, STRICT ENUM §6 May be empty
proposer.family_history_flags array REQUIRED, STRICT ENUM §6 May be empty
life_assured.is_same_as_proposer bool REQUIRED When FALSE, all life_assured fields REQUIRED non-null
cover_preferences.sum_assured_inr int REQUIRED, INR_INTEGER, ≥500000 IRDAI HLV cap = 25× annual income
cover_preferences.policy_term_years int REQUIRED, 5-50 Insurer-specific max age cap applies
cover_preferences.premium_payment_term_years int REQUIRED, ≤ policy_term_years Single-pay, limited-pay, regular-pay
cover_preferences.payout_mode enum REQUIRED, STRICT ENUM §6
cover_preferences.riders_required array REQUIRED, STRICT ENUM §6 May be empty

Anti-fabrication preamble: Provider may not quote a premium below the IRDAI minimum mortality table for the age-gender-tobacco-occupation combination. Sum-assured caps must respect Human Life Value (HLV) limits set by IRDAI / insurer. All disclosures must conform to IRDAI's POSP / direct-sale prescribed format. TOMO does not provide financial advice — partner ranks via TTBS only.


SECTION 4 — PROVIDER TOOLS

Tool 1: search_term_quotes

PURPOSE:      Up to 15 quotes across insurers matching age + sum_assured + term + riders
SLA:          p50 ≤ 700ms, p95 ≤ 2200ms, p99 ≤ 4500ms
RATE LIMIT:   60 req/min
IDEMPOTENCY:  request_id; 60s cache (premiums table-driven, low fluctuation)
RETRY:        1 on 429, 2 on 5xx

Tool 2: initiate_underwriting

PURPOSE:      Lock chosen quote + collect KYC + medical questionnaire + nominee
SLA:          p50 ≤ 1800ms, p95 ≤ 5000ms
RATE LIMIT:   30 req/min
IDEMPOTENCY:  request_id
RETRY:        No retry

Tool 3: schedule_medical_exam

PURPOSE:      Book tele-MER or in-person medical based on sum-assured + age + flags
SLA:          p50 ≤ 1200ms, p95 ≤ 3500ms
RATE LIMIT:   30 req/min
IDEMPOTENCY:  underwriting_id
RETRY:        1 on 5xx

Tool 4: issue_policy

PURPOSE:      Issue policy after underwriting approval + first premium paid
SLA:          p50 ≤ 2500ms, p95 ≤ 7000ms, p99 ≤ 15000ms
RATE LIMIT:   30 req/min
IDEMPOTENCY:  request_id; same id returns same policy
RETRY:        No retry on issuance

Tool 5: cancel_or_freelook

PURPOSE:      15-day IRDAI free-look cancellation OR pre-issuance cancellation
SLA:          p50 ≤ 1500ms, p95 ≤ 4000ms
RATE LIMIT:   30 req/min
IDEMPOTENCY:  policy_id
RETRY:        1 on 5xx

SECTION 5 — RESPONSE SHAPE

TermQuote

TermQuote:
  quote_id: { type: string, constraint: REQUIRED, opaque }
  insurer:
    insurer_id: { type: string, constraint: REQUIRED }
    name: { type: string, constraint: REQUIRED }
    irdai_registration_number: { type: string, constraint: REQUIRED }
    claim_settlement_ratio_pct: { type: float, constraint: REQUIRED, 0-100, semantics: "IRDAI-published latest year, individual death claims" }
    claim_paid_amount_ratio_pct: { type: float, constraint: REQUIRED, 0-100, semantics: "Total amount paid / total claimed" }
    solvency_ratio: { type: float, constraint: REQUIRED, 1.0-5.0, semantics: "IRDAI-published" }
    aum_inr_crore: { type: int, constraint: REQUIRED, ≥0, semantics: "Total assets under management" }

  product_name: { type: string, constraint: REQUIRED, semantics: "Insurer's published product name e.g. 'HDFC Life Click 2 Protect Super'" }
  product_uin: { type: string, constraint: REQUIRED, semantics: "IRDAI Unique Identification Number" }

  sum_assured_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
  policy_term_years: { type: int, constraint: REQUIRED, 5-50 }
  premium_payment_term_years: { type: int, constraint: REQUIRED }

  premium_breakdown:
    base_premium_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    riders_premium_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    gst_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0, semantics: "18% of premium" }
    total_first_year_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
    total_annual_recurring_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
    premium_frequency_payable_inr: { type: int, constraint: REQUIRED, INR_INTEGER, semantics: "amount per chosen frequency" }

  payout_mode:
    type: enum
    constraint: REQUIRED, STRICT ENUM §6
    values: [lump_sum, monthly_income, lump_sum_plus_income, increasing_payout]

  riders_included:
    type: array<Rider>
    constraint: REQUIRED, may be empty
    shape:
      code: { type: enum, constraint: REQUIRED, STRICT ENUM §6 }
      label: { type: string, constraint: REQUIRED }
      rider_sum_assured_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
      premium_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }

  return_of_premium: { type: boolean, constraint: REQUIRED }
  return_of_premium_amount_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0, semantics: "0 if ROP=false" }

  maturity_age: { type: int, constraint: REQUIRED, semantics: "Policy ends at this age" }

  medical_exam_required: { type: boolean, constraint: REQUIRED, semantics: "TRUE for sum_assured above insurer threshold or older ages" }
  medical_exam_type:
    type: enum
    constraint: REQUIRED, STRICT ENUM §6
    values: [none, tele_mer, in_person_basic, in_person_full]

  estimated_issuance_days: { type: int, constraint: REQUIRED, 0-45 }
  instant_issuance_possible: { type: boolean, constraint: REQUIRED, semantics: "TRUE when no medical required + KYC instant" }

  underwriting_basis:
    type: enum
    constraint: REQUIRED, STRICT ENUM §6
    values: [non_medical, simplified_issuance, medical_underwritten]

  benefit_illustration_url: { type: string, constraint: REQUIRED, HTTPS URL, semantics: "IRDAI-mandated benefit illustration document" }
  policy_wording_url: { type: string, constraint: REQUIRED, HTTPS URL, semantics: "IRDAI-compliant policy document" }

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

UnderwritingDecision

UnderwritingDecision:
  underwriting_id: { type: string, constraint: REQUIRED }
  quote_id: { type: string, constraint: REQUIRED }
  decision:
    type: enum
    constraint: REQUIRED, STRICT ENUM §6
    values: [approved_standard, approved_loaded, approved_with_exclusions, deferred_pending_medical, declined]
  loaded_premium_pct: { type: float, constraint: REQUIRED, 0-200, semantics: "0 when decision=approved_standard" }
  exclusions:
    type: array<string>
    constraint: REQUIRED, may be empty
    semantics: "List of conditions excluded from cover, plain language"
  decision_reason_code:
    type: enum
    constraint: REQUIRED, STRICT ENUM §6
    values: [standard, medical_history, family_history, occupation_loading, hazardous_lifestyle, financial_underwriting, insufficient_data]
  medical_required_after_decision: { type: boolean, constraint: REQUIRED }
  decision_valid_until: { type: string, constraint: REQUIRED, ISO_DATETIME, semantics: "Lock window typically 30 days" }
  kyc_status:
    type: enum
    constraint: REQUIRED, STRICT ENUM §6
    values: [verified_aadhaar, verified_pan, verified_passport, pending_manual, failed]
  documents_pending:
    type: array<string>
    constraint: REQUIRED, may be empty

MedicalExamSchedule

MedicalExamSchedule:
  exam_id: { type: string, constraint: REQUIRED }
  underwriting_id: { type: string, constraint: REQUIRED }
  exam_type:
    type: enum
    constraint: REQUIRED, STRICT ENUM §6
    values: [tele_mer, in_person_basic, in_person_full]
  scheduled_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
  location:
    type: object
    constraint: REQUIRED
    shape:
      address: { type: string, constraint: REQUIRED }
      city: { type: string, constraint: REQUIRED }
      pincode: { type: string, constraint: REQUIRED }
      is_home_visit: { type: boolean, constraint: REQUIRED }
  diagnostic_partner: { type: string, constraint: REQUIRED, semantics: "e.g. Thyrocare, MediBuddy, SRL" }
  tests_included:
    type: array<enum>
    constraint: REQUIRED, STRICT ENUM §6, may NOT be empty
  reschedule_window_hours: { type: int, constraint: REQUIRED, ≥0 }
  fasting_required: { type: boolean, constraint: REQUIRED }

IssuedPolicy

IssuedPolicy:
  policy_id: { type: string, constraint: REQUIRED, immutable }
  policy_number: { type: string, constraint: REQUIRED, semantics: "insurer-issued formal number" }
  insurer_id: { type: string, constraint: REQUIRED }
  product_uin: { type: string, constraint: REQUIRED }
  sum_assured_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
  policy_start_date: { type: string, constraint: REQUIRED, ISO_DATE }
  policy_end_date: { type: string, constraint: REQUIRED, ISO_DATE }
  total_first_year_paid_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
  next_premium_due_date: { type: string, constraint: REQUIRED, ISO_DATE }
  next_premium_amount_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
  policy_pdf_url: { type: string, constraint: REQUIRED, HTTPS URL, semantics: "signed URL ≥30 days validity" }
  benefit_illustration_url: { type: string, constraint: REQUIRED, HTTPS URL }
  nominee:
    full_name: { type: string, constraint: REQUIRED }
    relationship: { type: enum, constraint: REQUIRED, STRICT ENUM §6 }
    share_pct: { type: int, constraint: REQUIRED, 1-100 }
    date_of_birth: { type: string, constraint: REQUIRED, ISO_DATE }
  appointee_required: { type: boolean, constraint: REQUIRED, semantics: "TRUE when nominee is minor" }
  freelook_period_days: { type: int, constraint: REQUIRED, 15-30, semantics: "IRDAI minimum 15" }
  freelook_ends_at: { type: string, constraint: REQUIRED, ISO_DATE }
  grace_period_days: { type: int, constraint: REQUIRED, 15-30 }
  claim_helpline_phone: { type: string, constraint: REQUIRED, E.164 }
  claims_intimation_email: { type: string, constraint: REQUIRED }

CancellationResult

CancellationResult:
  policy_id: { type: string, constraint: REQUIRED }
  cancelled_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
  refund_amount_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
  refund_method: { type: enum, constraint: REQUIRED, STRICT ENUM §6, values: [original_payment, bank_transfer, cheque] }
  refund_eta_days: { type: int, constraint: REQUIRED, 0-30 }
  is_freelook_cancellation: { type: boolean, constraint: REQUIRED }
  deductions_inr:
    medical_cost_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    stamp_duty_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    proportionate_risk_premium_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }

FORBIDDEN FIELDS

  • paid_placement_score, ad_bid, sponsored_rank, promotion_priority, kickback_amount, commission_padded_premium
  • artificial_urgency_text (no "Premium goes up tomorrow!" without actual rate-change basis)
  • ai_generated_photo for insurer logos / certificate previews / agent photos
  • below_mortality_table_premium (any premium below IRDAI mortality table floor is per-se invalid)
  • inflated_claim_settlement_ratio (must match IRDAI-published value)
  • editor_recommended, tomo_pick, top_choice (TOMO does not advise on life insurance)
  • commission_rate, partner_revenue_share, payout_to_agent_inr anywhere in user-facing payload

SECTION 6 — CONTROLLED VOCABULARIES

proposer.gender:
  values:
    male:   "Male"
    female: "Female"
    other:  "Other / non-binary (IRDAI accepted)"

proposer.tobacco_use:
  values:
    never:     "Never used tobacco"
    former:    "Quit ≥12 months ago"
    occasional: "<5 cigarettes / equivalent per week"
    regular:   "Regular tobacco use"

proposer.education:
  values:
    below_10th, class_10, class_12, diploma, graduate, postgraduate, doctorate

proposer.occupation_class:
  values:
    salaried_corporate:      "Standard salaried, corporate sector"
    salaried_government:     "Government / PSU employee"
    self_employed_professional: "Doctor, lawyer, CA, architect"
    self_employed_business:  "Business owner, trader"
    armed_forces:            "Defence services (loading applies)"
    police_law_enforcement:  "Police / paramilitary (loading applies)"
    hazardous_occupation:    "Mining, offshore, aviation crew (high loading)"
    homemaker:               "Homemaker / household income"
    student:                 "Currently studying"
    retired:                 "Retired"

proposer.marital_status:
  values: [single, married, divorced, widowed]

proposer.residency_status:
  values: [resident_indian, nri, oci, pio, foreign_national]

proposer.state:
  values:
    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

proposer.pre_existing_conditions:
  values:
    none, hypertension, diabetes_type1, diabetes_type2, cardiac, cancer_remission,
    stroke_history, kidney_disease, liver_disease, mental_health, autoimmune,
    respiratory_chronic, neurological, other_disclosed
  resolution_order: "Partner maps internal codes to this list; 'other_disclosed' triggers manual review"

proposer.family_history_flags:
  values:
    none, cardiac_under_60, cancer_under_60, diabetes_first_degree, stroke_under_60,
    hereditary_disorder, genetic_screened_positive

life_assured.relationship_to_proposer:
  values:
    self, spouse, parent, child, sibling, grandparent, grandchild, dependent_inlaw, key_man_business

cover_preferences.payout_mode:
  values:
    lump_sum:                "Single payment of sum-assured on death"
    monthly_income:          "Monthly income to nominee for fixed period"
    lump_sum_plus_income:    "Partial lump + monthly income"
    increasing_payout:       "Sum-assured increases each year"

cover_preferences.premium_frequency:
  values: [annual, semi_annual, quarterly, monthly, single_pay]

cover_preferences.riders_required / riders_included.code:
  values:
    critical_illness:           "Critical illness rider"
    accidental_death:           "Accidental death benefit"
    accidental_disability:      "Permanent / partial disability"
    waiver_of_premium:          "Premium waived on disability"
    terminal_illness:           "Acceleration on terminal diagnosis"
    income_benefit:             "Annual income to family"
    hospital_cash:              "Daily hospital cash"
    return_of_premium:          "Premiums refunded at maturity (variant)"

decision (UnderwritingDecision):
  values: [approved_standard, approved_loaded, approved_with_exclusions, deferred_pending_medical, declined]

decision_reason_code:
  values: [standard, medical_history, family_history, occupation_loading, hazardous_lifestyle, financial_underwriting, insufficient_data]

medical_exam_type:
  values:
    none:               "No medical exam"
    tele_mer:           "Tele-medical examination (audio/video)"
    in_person_basic:    "BP, BMI, urine, basic blood panel"
    in_person_full:     "Full panel + ECG + treadmill / specialist as needed"

tests_included:
  values:
    height_weight, blood_pressure, urine_routine, fasting_blood_sugar,
    hba1c, lipid_profile, liver_function, kidney_function, hiv,
    hepatitis_b, hepatitis_c, ecg, treadmill_test, chest_xray,
    cotinine_nicotine

kyc_status:
  values: [verified_aadhaar, verified_pan, verified_passport, pending_manual, failed]

nominee.relationship:
  values:
    spouse, son, daughter, father, mother, brother, sister, grandparent,
    grandchild, legal_guardian, charitable_trust

refund_method:
  values: [original_payment, bank_transfer, cheque]

SECTION 7 — TTBS DIMENSIONS

TIME (weight = 0.10):
  signals_used:
    - instant_issuance_possible
    - estimated_issuance_days (lower preferred)
    - medical_exam_required (FALSE preferred)
    - underwriting_basis (non_medical > simplified > medical_underwritten)
  weighting:
    instant: 0.35
    issuance_days: 0.30
    no_medical: 0.25
    underwriting_basis: 0.10
  user_band_handling:
    fast: prefer non_medical even at slightly higher premium (max +10%)
    balanced: standard
    flexible: accept full medical for lowest premium

TASTE (weight = 0.15):
  signals_used:
    - insurer matches preferred_insurers (when set)
    - insurer matches user DNA history (past purchases at this or related insurer)
    - product_name brand familiarity (e.g. LIC for traditional preference)
  weighting:
    preferred_insurer: 0.50 (when set)
    dna_familiarity: 0.30
    brand_signal: 0.20

BUDGET (weight = 0.30):
  signals_used:
    - premium_breakdown.total_annual_recurring_inr
    - premium_breakdown.total_first_year_inr
    - riders_premium_inr fit to riders_required (avoid over-bundling)
    - return_of_premium_amount_inr present-value adjustment
  weighting:
    annual_recurring: 0.65
    first_year: 0.15
    rider_fit: 0.15
    rop_pv: 0.05
  user_band_handling:
    ok:    cheapest passing safety floor
    good:  balanced premium-vs-safety
    great: premium-insensitive; safety dominates

SAFETY (weight = 0.45):
  signals_used:
    - insurer.claim_settlement_ratio_pct
    - insurer.claim_paid_amount_ratio_pct
    - insurer.solvency_ratio
    - insurer.aum_inr_crore (size proxy for stability)
    - policy_wording_url (IRDAI-compliant)
    - benefit_illustration_url present
  weighting:
    csr: 0.40
    amount_ratio: 0.20
    solvency: 0.20
    aum: 0.10
    documents_compliant: 0.10
  user_band_handling:
    fast: relax CSR floor to 95%
    balanced: floor 97%
    great: prefer CSR ≥98% AND amount_ratio ≥95% even at higher premium

Locked weights per finance.buy_term_insurance: time 0.10 / taste 0.15 / budget 0.30 / safety 0.45. Safety dominates — the product's entire purpose is payout-on-death; insurer reliability outranks everything else.


SECTION 8 — COMPLETION CONTRACT

POST /api/v1/cpc/mcp_provider/<your_partner_id>
Body:
{
  "intent":           "finance.buy_term_insurance",
  "external_id":      "<policy_id>",
  "request_id":       "<request_id>",
  "amount_inr":       2800,    // NET partner commission only (typically 15-35% of first-year premium)
  "gst_inr":          504,
  "tips_inr":         0,
  "pass_through_inr": 14000,   // first-year premium remitted to insurer — NOT supplier revenue
  "closed_at":        "2026-05-30T11:20:00+05:30",
  "status":           "completed",
  "insurer_id": "hdfc_life",
  "product_uin": "101N137V03",
  "sum_assured_inr": 10000000,
  "policy_term_years": 30,
  "underwriting_basis": "medical_underwritten",
  "riders_count": 2
}

Important: The customer pays the FULL first-year premium (e.g. ₹14,000), but only the partner's commission (~₹2,800) is partner revenue. amount_inr reflects ONLY the partner's commission. The remitted premium goes in pass_through_inr. TOMO charges 10% × amount_inr = ₹280 — not 10% of ₹14,000.

For renewal-year premiums, partners POST a follow-up completion with intent: finance.buy_term_insurance.renewal_year_<N> and the trail-commission amount_inr for that year. Trail commissions are usually 5-15% in years 2-5.

HMAC-SHA256 over canonical body, 5-min replay window, NET-only commission.


SECTION 9 — WIDGET

TermInsuranceWidget (planned). Interim: comparison ListingsWidget.

Field mapping:
  - TermQuote.insurer.name + logo → header
  - product_name → subhead
  - claim_settlement_ratio_pct → CSR pill (color-coded ≥98 / 95-98 / <95)
  - premium_breakdown.total_annual_recurring_inr → big price (₹/year)
  - sum_assured_inr → "₹X cr cover"
  - policy_term_years → "X-yr term"
  - medical_exam_type → "No medical" / "Tele-MER" / "Medical" pill
  - riders_included.length → "+X riders" pill
  - instant_issuance_possible → green instant badge
  - benefit_illustration_url → "View illustration" link
  - policy_wording_url → "Read the wording" link

SECTION 10 — CACHING POLICY

Call TTL Rationale
search_term_quotes 60s Mortality-table driven, low fluctuation
initiate_underwriting NO CACHE Idempotent by request_id
schedule_medical_exam NO CACHE Calendar booking; live availability
issue_policy NO CACHE Idempotent; never retry
cancel_or_freelook NO CACHE
Insurer static (CSR, solvency, AUM, logos) 24h IRDAI publishes annually
Product wording / illustration URLs 7d Versioned by product_uin

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 (webhook) HMAC fail No
AGE_OUT_OF_RANGE 422 Age outside insurer entry/exit band No
SUM_ASSURED_OUT_OF_HLV 422 Sum-assured exceeds 25× annual income (HLV cap) No (UI lowers ask)
OCCUPATION_DECLINED 422 Hazardous occupation outside insurer appetite No
PRE_EXISTING_DECLINED 422 Disclosed condition outside underwriting appetite No
KYC_FAILED 422 PAN / Aadhaar / passport mismatch No (user re-submits)
MEDICAL_DEFERRED 200 (advisory) medical_required=true returned in underwriting n/a
UNDERWRITING_DECLINED 422 Final decline after medical No
NOMINEE_INVALID 422 Nominee data missing, minor without appointee, share % ≠ 100 No
PAYMENT_FAILED 402 Gateway declined No (user re-tries)
INSURER_DOWN 503 Insurer API unavailable 1 retry, 2s
FREELOOK_WINDOW_EXPIRED 410 (cancel) Past free-look period No
PRODUCT_DISCONTINUED 410 UIN no longer sold No (UI re-quotes)

SECTION 12 — SANDBOX → PRODUCTION CHECKLIST

[ ] All five tools implemented
[ ] At least 6 insurers in catalog (covering HDFC Life / ICICI Pru / Max Life / Tata AIA / Bajaj Allianz Life / LIC or similar)
[ ] All insurer IRDAI registration numbers verifiable on irdai.gov.in
[ ] All product UINs verifiable on IRDAI product approval list
[ ] claim_settlement_ratio_pct and claim_paid_amount_ratio_pct match latest IRDAI annual publication
[ ] solvency_ratio and aum_inr_crore match latest IRDAI publication
[ ] benefit_illustration_url returns IRDAI-mandated illustration in approved format
[ ] policy_wording_url returns IRDAI-compliant policy document with correct UIN
[ ] HLV cap (25× annual income) enforced server-side; rejected requests return SUM_ASSURED_OUT_OF_HLV
[ ] Mortality table floor enforced; no quotes below IRDAI minimum
[ ] All controlled vocabularies respected
[ ] HMAC signing verified on test CPC webhook
[ ] amount_inr in CPC is partner COMMISSION only (NET), NOT total premium
[ ] pass_through_inr correctly captures premium remitted to insurer
[ ] freelook_period_days ≥ 15 (IRDAI minimum)
[ ] grace_period_days ≥ 15 (IRDAI minimum)
[ ] No forbidden fields anywhere
[ ] SLA p95 met (full 100-call sandbox run)
[ ] IRDAI corporate-agent / broker / web-aggregator license uploaded
[ ] Compliance docs: GSTIN, IRDAI license number, privacy policy URL, grievance redressal officer contact
[ ] Customer support: 24×7 claims helpline + dedicated claims email
[ ] Tele-MER vendor contract uploaded (if offering tele-MER)
[ ] Diagnostic partner network coverage map (city/state)

SECTION 13 — ANTI-FABRICATION RULES

RULE 1: No paid_placement / ad / kickback. Sustained promotion of higher-
        commission products irrespective of TTBS = suspension. Life insurance
        commissions vary 15-40% — bias is high-risk; TOMO audits monthly.

RULE 2: claim_settlement_ratio_pct MUST match IRDAI's latest annual
        publication. Inflating CSR to win rank = brand-relationship breach +
        immediate suspension. TOMO audits quarterly against IRDAI data.

RULE 3: claim_paid_amount_ratio_pct must match IRDAI publication. Some
        partners only display claim COUNT ratio (often inflated); TOMO
        requires AMOUNT ratio because that's what protects the customer.

RULE 4: solvency_ratio and aum_inr_crore must match IRDAI publication. Same
        enforcement.

RULE 5: base_premium_inr cannot be below the IRDAI minimum mortality table
        for the age-gender-tobacco-occupation combination. Below-floor
        quotes are per-se invalid and rejected by ingest.

RULE 6: sum_assured_inr cannot exceed the IRDAI HLV cap (25× annual income
        for salaried; insurer-specific multiples for self-employed).
        Quotes exceeding cap = insurer-relationship breach.

RULE 7: irdai_registration_number must be the insurer's actual current
        registration. product_uin must be on IRDAI's approved-product list.
        Mock or expired identifiers = immediate suspension.

RULE 8: policy_wording_url must be the IRDAI-approved document for the
        specific UIN. Marketing brochures in this slot = customer-harm +
        regulatory risk + suspension.

RULE 9: benefit_illustration_url must use the IRDAI-prescribed format
        (gross/net yield, year-by-year projection, signed by user before
        issuance). Non-compliant illustration = immediate suspension.

RULE 10: artificial_urgency_text forbidden ("Premium goes up in 4 hours!").
         Premium revisions are publicly notified to IRDAI before taking
         effect; specific countdown copy must reflect verifiable revision.

RULE 11: No AI-generated insurer logos, agent photos, or testimonial imagery.
         Real insurer brand assets only, under brand-use agreements.

RULE 12: amount_inr in CPC is partner's NET commission only — NEVER the
         total premium remitted to insurer. Partners falsely reporting
         premium as their revenue to inflate commission base = suspension.
         TOMO audits via partner's IRDAI-filed agency accounts.

RULE 13: No "Top Pick" / "Editor's Choice" / "TOMO Recommended" / "Best for
         You" badges. Life insurance recommendation is regulated advice;
         TOMO does not advise — it orchestrates source-blind TTBS ranking.

RULE 14: medical_exam_required cannot be falsely flagged FALSE to fast-track
         issuance when sum_assured + age cross insurer's documented threshold.
         IRDAI / insurer underwriting guidelines are auditable.

RULE 15: nominee data must be collected and stored per IRDAI rules
         (mandatory before issuance, share % must sum to 100). Skipping
         nominee or auto-filling "self" = customer-harm + regulatory breach.

RULE 16: information_completeness_score (hidden ranking factor, weight 0.10)
         rewards partners returning the full §5 shape. Sparse responses rank
         lower automatically.

RULE 17: Loaded-premium percentage (loaded_premium_pct) must be supported by
         a documented underwriting reason from the controlled vocabulary
         (§6 decision_reason_code). Arbitrary loading = customer-harm +
         insurer-relationship breach.

VERSION HISTORY

v1.0.0 — 2026-05-13 — Initial spec. Pure-term life only (no investment-linked).
                       Safety-dominant TTBS (weight 0.45) reflecting payout-on-
                       death product purpose. NET commission base in §8 (first-
                       year commission only, not premium remitted). IRDAI
                       mortality-floor + HLV-cap + CSR / amount-ratio / solvency
                       anti-fabrication rules. Free-look + grace period IRDAI-
                       minimum enforced. Nominee mandatory pre-issuance.