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 intentfinance.buy_motor_insurance/auto.book_insurance_renewal— motor vehicle coverfinance.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_premiumartificial_urgency_text(no "Premium goes up tomorrow!" without actual rate-change basis)ai_generated_photofor insurer logos / certificate previews / agent photosbelow_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_inranywhere 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.