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_credit_card

Intent Spec — finance.apply_credit_card

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

User applies for a credit card from a bank or co-branded NBFC. Covers eligibility check (soft pull), card discovery across issuers, KYC, instant virtual / physical card issuance, mandate setup, rewards / cashback / lounge / fuel-surcharge benefits, and joining-fee handling. Distinct from credit lines / personal loans.

Partner exemplars: BankBazaar, Paisabazaar, OneCard, Slice (partnership), HDFC Bank, ICICI Bank, SBI Card, Axis Bank, IDFC First, AmEx India, Standard Chartered, RBL Bank, IndusInd.


SECTION 1 — INTENT IDENTITY

User wants to apply for a credit card. Distinct from:

  • finance.apply_personal_loan — term loan, not revolving
  • BNPL / pay-later — v1.1+ finance.apply_bnpl
  • Co-branded charge cards (Diners) — same intent
  • Corporate cards — v1.1+
  • Secured credit card (FD-backed) — same intent; user specifies card_class=secured_fd_backed
  • Forex card — v1.1+
  • Add-on card — v1.1+

Single intent per card application. Comparing across issuers happens internally in §4 (TOMO fans out).


SECTION 2 — NATURAL LANGUAGE COVERAGE

CLASSIFIES IN

  • "Best travel credit card"
  • "Cashback credit card for online shopping"
  • "Lifetime free credit card"
  • "Credit card for first-time user no credit history"
  • "Premium card with lounge access"
  • "Fuel surcharge waiver card"
  • "Amazon Pay credit card"
  • "Credit card for self-employed"
  • "Secured credit card against fixed deposit"
  • "AmEx Membership Rewards card"

CLASSIFIES OUT — BORDERLINE NO

  • "Personal loan" → finance.apply_personal_loan
  • "BNPL / Pay Later" → v1.1+
  • "Forex card" → v1.1+
  • "Increase my credit limit" → v1.1+
  • "What's my CIBIL?" → v1.1+
  • "Credit card EMI conversion" — non-transactional within an existing card

MULTI-INTENT TRIGGERS

  • "Credit card + insurance" → finance.apply_credit_card + finance.buy_term_insurance
  • "Travel card + flight + hotel booking" → composite mission with travel.book_flight + travel.book_hotel
  • "Cashback card + grocery shopping setup" → finance.apply_credit_card (no second intent — TOMO surfaces card-stacking insights post-issuance)

SECTION 3 — INPUT (TOMO → PROVIDER)

{
  "intent": "finance.apply_credit_card",
  "request_id": "req_01J9Z...",
  "user_locale": "en-IN",
  "user_currency": "INR",
  "user_location": { "lat": 17.4475, "lng": 78.3563, "city": "Hyderabad", "pincode": "500032" },
  "applicant": {
    "first_name": "Rakesh", "last_name": "Kumar",
    "date_of_birth": "1990-04-18",
    "gender": "male",
    "pan_last4": "5678",
    "mobile_e164": "+919876543210",
    "email": "rakesh@example.com",
    "current_address_pincode": "500032",
    "marital_status": "married",
    "education": "graduate",
    "employment": {
      "type": "salaried_corporate",
      "industry": "information_technology",
      "company_name": "Acme Pvt Ltd",
      "company_category": "listed_or_psu",
      "designation": "Senior Engineer",
      "years_at_current_employer": 3,
      "net_monthly_income_inr": 125000,
      "gross_annual_income_inr": 1800000
    },
    "existing_relationships": {
      "has_savings_account_with_issuer": [],            // list of issuer IDs (relationship discount)
      "has_existing_credit_card": true,
      "existing_card_oldest_age_months": 60,
      "credit_cards_count": 2
    },
    "obligations": {
      "existing_emi_monthly_inr": 22000,
      "credit_cards_outstanding_inr": 35000,
      "consent_for_credit_bureau_pull": true,
      "consent_for_aa_aggregator_pull": true
    }
  },
  "card_preferences": {
    "primary_use_case": "travel_lounge",                // STRICT ENUM §6
    "card_class_preference": "premium",                 // STRICT ENUM §6
    "preferred_networks": ["visa_signature", "mastercard_world"],  // STRICT ENUM array §6
    "preferred_issuers": [],
    "joining_fee_tolerance_inr": 5000,
    "annual_fee_tolerance_inr": 5000,
    "want_lifetime_free": false,
    "want_lounge_access": true,
    "want_fuel_surcharge_waiver": true,
    "want_movie_offers": false,
    "want_milestone_benefits": true,
    "want_emi_conversion_facility": true,
    "secured_fd_amount_inr": null                        // non-null only when card_class_preference=secured_fd_backed
  },
  "ttbs_user_band": {
    "time":   "balanced",
    "taste":  "great",
    "budget": "good",
    "safety": "good"
  },
  "session_context": { "tomo_session_id": "ses_01J9Z...", "user_dna_hash": "dna_v3_a7c9..." }
}
Field Type Constraint Notes
intent string REQUIRED, STRICT ENUM Always finance.apply_credit_card
applicant.pan_last4 string REQUIRED, 4 digits Full PAN at KYC
applicant.mobile_e164 string REQUIRED, E.164 OTP-verified by issuer
applicant.employment.type enum REQUIRED, STRICT ENUM §6
applicant.employment.net_monthly_income_inr int REQUIRED, INR_INTEGER Drives card-class eligibility
applicant.existing_relationships.has_existing_credit_card bool REQUIRED Drives no-credit-history product surfacing
applicant.obligations.consent_for_credit_bureau_pull bool REQUIRED, value MUST be true
card_preferences.primary_use_case enum REQUIRED, STRICT ENUM §6 Drives rewards-mix ranking
card_preferences.card_class_preference enum REQUIRED, STRICT ENUM §6 starter / standard / premium / super_premium / secured_fd_backed
card_preferences.secured_fd_amount_inr int REQUIRED nullable Non-null required when card_class_preference=secured_fd_backed

Anti-fabrication preamble: Reward rates and milestones must reflect issuer's current published terms. Hidden devaluations (silently lowering reward rate post-issuance) violate consumer law. Joining + annual fees must match T&C exactly. No misrepresentation of complimentary lounge / movie / fuel benefits.


SECTION 4 — PROVIDER TOOLS

Tool 1: search_card_offers

PURPOSE:      Up to 12 card offers matching profile + preferences (soft-pull-based)
SLA:          p50 ≤ 800ms, p95 ≤ 2500ms, p99 ≤ 5000ms
RATE LIMIT:   60 req/min
IDEMPOTENCY:  request_id; 10min cache
RETRY:        1 on 429, 2 on 5xx

Tool 2: submit_card_application

PURPOSE:      Hard-pull + KYC + employment / income verification
SLA:          p50 ≤ 2500ms, p95 ≤ 7000ms, p99 ≤ 15000ms
RATE LIMIT:   30 req/min
IDEMPOTENCY:  request_id
RETRY:        No retry

Tool 3: issue_virtual_card

PURPOSE:      Instant virtual card credentials for online use
SLA:          p50 ≤ 1500ms, p95 ≤ 4000ms
RATE LIMIT:   30 req/min
IDEMPOTENCY:  application_id
RETRY:        No retry

Tool 4: schedule_physical_delivery

PURPOSE:      Schedule physical card delivery
SLA:          p50 ≤ 1200ms, p95 ≤ 3500ms
RATE LIMIT:   30 req/min
IDEMPOTENCY:  application_id
RETRY:        1 on 5xx

Tool 5: cancel_pre_issuance

PURPOSE:      Cancel application before card is dispatched
SLA:          p50 ≤ 1500ms, p95 ≤ 4000ms
RATE LIMIT:   45 req/min
IDEMPOTENCY:  application_id
RETRY:        1 on 5xx

SECTION 5 — RESPONSE SHAPE

CardOffer

CardOffer:
  offer_id: { type: string, constraint: REQUIRED, opaque }
  issuer:
    issuer_id: { type: string, constraint: REQUIRED }
    name: { type: string, constraint: REQUIRED }
    rbi_registration_number: { type: string, constraint: REQUIRED }

  card:
    card_id: { type: string, constraint: REQUIRED }
    card_name: { type: string, constraint: REQUIRED, semantics: "Marketed product name" }
    card_class: { type: enum, constraint: REQUIRED, STRICT ENUM §6 }
    network: { type: enum, constraint: REQUIRED, STRICT ENUM §6 }
    network_tier_label: { type: string, constraint: REQUIRED, semantics: "Visa Signature / Mastercard World / etc." }
    is_co_branded: { type: boolean, constraint: REQUIRED }
    co_brand_partner: { type: string, constraint: REQUIRED nullable }
    image_url: { type: string, constraint: REQUIRED, HTTPS URL }

  is_pre_approved: { type: boolean, constraint: REQUIRED }

  estimated_credit_limit_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
  estimated_cash_limit_pct: { type: float, constraint: REQUIRED, 0-100, semantics: "% of credit limit as cash-advance limit" }

  fees:
    joining_fee_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    annual_fee_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    gst_on_fees_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    is_lifetime_free: { type: boolean, constraint: REQUIRED }
    joining_fee_waiver_condition: { type: string, constraint: REQUIRED, semantics: "Plain-language condition or empty if none" }
    annual_fee_waiver_condition: { type: string, constraint: REQUIRED, semantics: "Plain-language condition or empty if none" }

  interest:
    apr_finance_charge_pct_monthly: { type: float, constraint: REQUIRED, 0-5 }
    apr_finance_charge_pct_annual: { type: float, constraint: REQUIRED, 0-50 }
    interest_free_days_max: { type: int, constraint: REQUIRED, 18-55 }
    cash_advance_interest_pct_monthly: { type: float, constraint: REQUIRED, 0-5 }

  charges:
    late_payment_charge_inr_min: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    late_payment_charge_inr_max: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    over_limit_charge_pct: { type: float, constraint: REQUIRED, 0-10 }
    cash_advance_charge_pct: { type: float, constraint: REQUIRED, 0-5 }
    forex_markup_pct: { type: float, constraint: REQUIRED, 0-5 }
    fuel_surcharge_pct: { type: float, constraint: REQUIRED, 0-5 }
    fuel_surcharge_waiver_cap_monthly_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }

  rewards:
    type:
      type: enum
      constraint: REQUIRED, STRICT ENUM §6
      values: [cashback, points, miles, mixed]
    base_rate_pct: { type: float, constraint: REQUIRED, 0-10, semantics: "Base reward rate on default spends" }
    accelerated_categories:
      type: array<AcceleratedCategory>
      constraint: REQUIRED, may be empty
      shape:
        category: { type: enum, constraint: REQUIRED, STRICT ENUM §6 }
        rate_pct: { type: float, constraint: REQUIRED, 0-15 }
        monthly_cap_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    point_value_inr_per_point: { type: float, constraint: REQUIRED, ≥0, semantics: "0 for cashback / miles" }
    capping_rule: { type: string, constraint: REQUIRED }

  milestone_benefits:
    type: array<Milestone>
    constraint: REQUIRED, may be empty
    shape:
      milestone_inr_per_year: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
      reward_label: { type: string, constraint: REQUIRED }
      reward_value_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }

  complimentary_benefits:
    domestic_lounge_visits_per_year: { type: int, constraint: REQUIRED, ≥0 }
    international_lounge_visits_per_year: { type: int, constraint: REQUIRED, ≥0 }
    priority_pass: { type: boolean, constraint: REQUIRED }
    movie_buy_one_get_one: { type: boolean, constraint: REQUIRED }
    movie_offer_cap_monthly_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    golf_access: { type: boolean, constraint: REQUIRED }
    concierge_24x7: { type: boolean, constraint: REQUIRED }
    travel_insurance_included_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    purchase_protection_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    fraud_liability_protection: { type: boolean, constraint: REQUIRED }

  emi_conversion:
    available: { type: boolean, constraint: REQUIRED }
    min_transaction_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    interest_rate_pct: { type: float, constraint: REQUIRED, 0-25 }
    tenure_months_available: { type: array<int>, constraint: REQUIRED, may NOT be empty }

  issuance:
    virtual_card_instant: { type: boolean, constraint: REQUIRED }
    physical_card_estimated_days: { type: int, constraint: REQUIRED, 1-30 }
    instant_pin_setup: { type: boolean, constraint: REQUIRED }

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

  most_important_terms_url: { type: string, constraint: REQUIRED, HTTPS URL, semantics: "RBI-mandated MITC document" }
  card_agreement_url: { type: string, constraint: REQUIRED, HTTPS URL }
  rewards_terms_url: { type: string, constraint: REQUIRED, HTTPS URL }

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

ApplicationResult

ApplicationResult:
  application_id: { type: string, constraint: REQUIRED }
  offer_id: { type: string, constraint: REQUIRED }
  status:
    type: enum
    constraint: REQUIRED, STRICT ENUM §6
    values: [approved_at_offered_terms, approved_revised_limit, conditional_approval_docs_pending, in_underwriting, declined_credit, declined_policy, declined_income, withdrawn_by_applicant]
  revised_credit_limit_inr: { type: int, constraint: REQUIRED nullable, INR_INTEGER, ≥0 }
  decision_reason_code:
    type: enum
    constraint: REQUIRED nullable, STRICT ENUM §6
    values: [credit_score_low, dpd_recent, foir_exceeded, income_unverified, employer_uncovered, bureau_thin_file, fraud_flag, policy_decline_other]
  decision_valid_until: { type: string, constraint: REQUIRED, ISO_DATETIME }
  documents_pending: { type: array<string>, constraint: REQUIRED, may be empty }
  kyc_status: { type: enum, constraint: REQUIRED, STRICT ENUM §6, values: [verified, video_kyc_pending, manual_pending, failed] }

VirtualCardCredentials

VirtualCardCredentials:
  application_id: { type: string, constraint: REQUIRED }
  virtual_card_token: { type: string, constraint: REQUIRED, semantics: "Tokenised reference; never raw PAN" }
  card_holder_name: { type: string, constraint: REQUIRED }
  expiry_yyyymm: { type: string, constraint: REQUIRED, semantics: "MM/YY" }
  credit_limit_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
  available_limit_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
  cash_limit_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
  is_activated: { type: boolean, constraint: REQUIRED }
  network_token_provisionable: { type: boolean, constraint: REQUIRED, semantics: "Eligible to add to GPay / Apple Pay" }
  issued_at: { type: string, constraint: REQUIRED, ISO_DATETIME }

PhysicalDeliverySchedule

PhysicalDeliverySchedule:
  application_id: { type: string, constraint: REQUIRED }
  delivery_id: { type: string, constraint: REQUIRED }
  delivery_partner: { type: string, constraint: REQUIRED, semantics: "Courier name" }
  tracking_number: { type: string, constraint: REQUIRED nullable }
  estimated_delivery_date: { type: string, constraint: REQUIRED, ISO_DATE }
  delivery_address: { type: string, constraint: REQUIRED }
  contact_phone: { type: string, constraint: REQUIRED, E.164 }
  ovd_required_at_delivery: { type: boolean, constraint: REQUIRED, semantics: "Officially valid document for OTP-less hand-over" }

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_issuance, cancellation_failed_post_issuance] }
  refund_amount_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
  refund_eta_days: { type: int, constraint: REQUIRED, 0-30 }

FORBIDDEN FIELDS

  • paid_placement_score, ad_bid, sponsored_rank, kickback_amount
  • artificial_urgency_text ("Free joining expires tonight!" without basis)
  • ai_generated_photo for issuer / card imagery (real card image only)
  • inflated_reward_rate (must match issuer published)
  • hidden_devaluation_clause (silent reward-rate cuts forbidden in disclosures)
  • mandatory_insurance_bundle
  • editor_recommended, tomo_pick, best_for_travel
  • forex_markup_below_disclosed_floor
  • over_inflated_credit_limit (display must be lender's actual eligibility, not aspirational)

SECTION 6 — CONTROLLED VOCABULARIES

applicant.education: [class_10, class_12, diploma, graduate, postgraduate, doctorate]

applicant.marital_status: [single, married, divorced, widowed]

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

applicant.employment.industry:
  values:
    information_technology, financial_services, manufacturing, retail,
    healthcare, education, government, defence, hospitality, real_estate,
    media_entertainment, agriculture, logistics, energy, telecom, e_commerce,
    consulting, legal_services, other

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

card_preferences.primary_use_case:
  values:
    travel_lounge:          "Frequent travel, lounges, miles / hotel points"
    cashback_general:       "Everyday spends with flat / category cashback"
    online_shopping:        "Amazon / Flipkart / e-commerce co-branded"
    grocery_dining:         "Grocery + dining accelerated rewards"
    fuel_commute:           "Fuel surcharge waiver + commute spends"
    movie_entertainment:    "Movies + dining benefits"
    business_self_employed: "Higher limits + GST capture + travel"
    no_credit_history:      "Starter / secured card for first-timers"

card_preferences.card_class_preference / card_class:
  values:
    starter:           "Entry-level; income ₹15k-25k/mo; ₹0-500 joining"
    standard:          "Mid-tier; income ₹25k-75k/mo; ₹500-2000 joining"
    premium:           "Premium; income ₹75k-2L/mo; ₹2k-10k joining"
    super_premium:     "Super-premium; income ≥₹2L/mo; ₹10k+ joining (Infinia, Magnus etc.)"
    secured_fd_backed: "FD-backed; for thin-file / no-credit-history users"

card_preferences.preferred_networks / network:
  values:
    visa_classic, visa_signature, visa_infinite,
    mastercard_standard, mastercard_titanium, mastercard_world, mastercard_world_elite,
    rupay_classic, rupay_platinum, rupay_select,
    amex_green, amex_gold, amex_platinum, diners_club_black

rewards.type: [cashback, points, miles, mixed]

rewards.accelerated_categories[].category:
  values:
    dining, grocery, fuel, online_shopping, travel_air, travel_hotel,
    movies, utility_bills, education, e_wallets, insurance,
    upi_p2m, rent, government_payments, healthcare, telecom

ApplicationResult.status:
  values: [approved_at_offered_terms, approved_revised_limit, conditional_approval_docs_pending, in_underwriting, declined_credit, declined_policy, declined_income, withdrawn_by_applicant]

decision_reason_code:
  values: [credit_score_low, dpd_recent, foir_exceeded, income_unverified, employer_uncovered, bureau_thin_file, fraud_flag, policy_decline_other]

kyc_status: [verified, video_kyc_pending, manual_pending, failed]

CancellationResult.status: [cancelled_pre_issuance, cancellation_failed_post_issuance]

SECTION 7 — TTBS DIMENSIONS

TIME (weight = 0.20):
  signals_used:
    - is_pre_approved
    - virtual_card_instant
    - physical_card_estimated_days
    - instant_pin_setup
  weighting:
    pre_approved: 0.40
    virtual_instant: 0.30
    physical_speed: 0.20
    instant_pin: 0.10

TASTE (weight = 0.25):
  signals_used:
    - card_class matches user's preference exactly
    - network matches preferred_networks
    - is_co_branded matches user DNA (e.g. heavy Amazon spender + Amazon card)
    - issuer matches existing_relationships.has_savings_account_with_issuer
    - primary_use_case match to accelerated_categories
  weighting:
    use_case_match: 0.35
    network_match: 0.25
    co_brand_dna: 0.20
    existing_relationship: 0.15
    class_match: 0.05

BUDGET (weight = 0.30):
  signals_used:
    - is_lifetime_free
    - joining_fee_inr vs tolerance
    - annual_fee_inr vs tolerance
    - annual_fee_waiver_condition realistic for user's spending profile
    - apr_finance_charge_pct_monthly
    - rewards.base_rate_pct + accelerated effective return
    - milestone_benefits net value vs spend assumption
  weighting:
    fee_fit: 0.40
    effective_rewards_return: 0.45
    apr: 0.10
    milestone_value: 0.05
  user_band_handling:
    ok:    lifetime free preferred; lowest annual
    good:  balanced fee vs reward value
    great: fee-insensitive; max reward + benefits dominate

SAFETY (weight = 0.25):
  signals_used:
    - issuer.rbi_registration_number active
    - fraud_liability_protection
    - complimentary_benefits.purchase_protection_inr
    - most_important_terms_url present and current
    - issuer's regulator-grievance handling reputation
    - network tier reflects actual benefit eligibility (no fake "World" labelling)
  weighting:
    issuer_regulatory: 0.40
    fraud_protection: 0.25
    purchase_protection: 0.15
    documents_compliant: 0.10
    network_labelling_accurate: 0.10
  user_band_handling:
    fast: relaxed
    balanced: standard
    great: prefer issuers with strongest fraud-resolution track record

Locked weights per finance.apply_credit_card: time 0.20 / taste 0.25 / budget 0.30 / safety 0.25. Taste is uplifted (0.25 vs 0.10 for loans) because cards are deeply preference-driven (network, co-brand, lounges, miles).


SECTION 8 — COMPLETION CONTRACT

POST /api/v1/cpc/mcp_provider/<your_partner_id>
Body:
{
  "intent":           "finance.apply_credit_card",
  "external_id":      "<application_id>",
  "request_id":       "<request_id>",
  "amount_inr":       1800,    // NET partner / DSA commission per activated card (typically ₹500-3000)
  "gst_inr":          324,
  "tips_inr":         0,
  "pass_through_inr": 0,       // No money passes through — credit instrument, not disbursement
  "closed_at":        "2026-05-18T11:20:00+05:30",
  "status":           "completed",
  "issuer_id": "hdfc_bank",
  "card_id": "millennia",
  "card_class": "standard",
  "is_pre_approved": false,
  "credit_limit_inr": 200000
}

Important: amount_inr = partner / DSA commission only. Credit cards have NO pass-through (instrument, not money flow). TOMO charges 10% × commission only.

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


SECTION 9 — WIDGET

CreditCardWidget (planned). Interim: comparison ListingsWidget.

Field mapping:
  - CardOffer.card.image_url → card visual
  - card.card_name → title
  - issuer.name → subhead
  - network_tier_label → eyebrow ("Visa Signature")
  - is_lifetime_free → green "LTF" badge
  - joining_fee_inr / annual_fee_inr → fee line
  - rewards.base_rate_pct + top accelerated category → reward line
  - complimentary_benefits.domestic_lounge_visits_per_year → "X lounges" pill
  - milestone_benefits → "₹X / year milestones" pill
  - is_pre_approved → green badge
  - virtual_card_instant → "Instant card" badge
  - most_important_terms_url → "View MITC" link (mandatory)

SECTION 10 — CACHING POLICY

Call TTL Rationale
search_card_offers 10min Stable for short window
submit_card_application NO CACHE
issue_virtual_card NO CACHE
schedule_physical_delivery NO CACHE
cancel_pre_issuance NO CACHE
Issuer / card static (image, terms, benefits) 24h Issuer publishes updates
MITC / Agreement / Rewards URLs 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 consent No
THIN_BUREAU_FILE 422 Insufficient credit history No
AGE_OUT_OF_RANGE 422 Outside issuer age band No
INCOME_BELOW_THRESHOLD 422 Below issuer floor No
EMPLOYER_NOT_COVERED 422 Outside approved employer list No
EXISTING_DUPLICATE_CARD 422 Active card of same product exists No
KYC_FAILED 422 KYC mismatch No
SECURED_FD_REQUIRED 422 Secured card without FD reference No
ADDRESS_VERIFICATION_FAILED 422 Cannot verify delivery address No
VIRTUAL_CARD_NOT_SUPPORTED 422 Product doesn't offer virtual card No
ISSUER_DOWN 503 Issuer API unavailable 1 retry, 2s
OFFER_EXPIRED 410 Past offer validity No
APPLICATION_BLOCKED_FRAUD_FLAG 422 Fraud flag at issuer No

SECTION 12 — SANDBOX → PRODUCTION CHECKLIST

[ ] All five tools implemented
[ ] At least 6 issuers in catalog (mix of banks + co-branded)
[ ] All RBI registration numbers verifiable
[ ] Reward rates match issuer's current published terms
[ ] Accelerated category rates accurate per latest schedule
[ ] Joining + annual fees match T&C exactly
[ ] Annual fee waiver conditions accurate (spend thresholds verifiable)
[ ] APR finance charge (monthly + annual) accurate
[ ] Interest-free days accurate (18-55 typical)
[ ] forex_markup_pct accurate; min 1.5% for RBI compliance
[ ] fuel_surcharge_pct + waiver_cap accurate
[ ] Lounge benefits accurate (domestic + international counts)
[ ] Milestone benefits accurate (₹ thresholds + rewards)
[ ] Insurance / purchase protection accurate
[ ] EMI conversion rates + tenures accurate
[ ] most_important_terms_url returns RBI-mandated MITC
[ ] All controlled vocabularies respected
[ ] HMAC signing verified
[ ] amount_inr in CPC is partner COMMISSION only (NET)
[ ] pass_through_inr is ZERO (instrument, not money flow)
[ ] No forbidden fields anywhere
[ ] No "Top Pick" / "Best for X" badges
[ ] No fake "World" / "Signature" tier labelling for cards not actually that tier
[ ] SLA p95 met
[ ] DSA / connector arrangement with each issuer uploaded
[ ] Compliance: GSTIN, DSA agreements, privacy policy, grievance officer
[ ] Customer support: working hours + RBI ombudsman escalation
[ ] DPDP Act compliance: explicit consent for bureau + KYC

SECTION 13 — ANTI-FABRICATION RULES

RULE 1: No paid_placement / ad / kickback fields. Credit card DSA
        commissions vary widely (₹500-3000+ per card). Bias risk is
        material. TOMO audits monthly.

RULE 2: rewards.base_rate_pct, accelerated_categories rates, and
        milestone_benefits MUST match issuer's current published
        terms. Inflating to win on Budget axis = customer-harm +
        immediate suspension.

RULE 3: rewards.point_value_inr_per_point must reflect realistic
        redemption value. Quoting "1 point = ₹1" when actual
        airline-mile redemption is ₹0.25 = customer-harm.

RULE 4: capping_rule must accurately describe per-category and
        overall monthly caps. Misstating = customer-harm.

RULE 5: most_important_terms_url MUST be the issuer's RBI-mandated
        MITC document for the specific card variant. Marketing pages
        in this slot = regulatory breach + suspension.

RULE 6: Annual fee waiver conditions must be accurate. If actual
        waiver requires ₹5L annual spend but partner states ₹3L,
        customer trusts and renews = suspension.

RULE 7: Lounge access counts must match issuer's actual schedule
        (often differs by network tier; misstating = customer-harm
        at airport).

RULE 8: forex_markup_pct must accurately reflect issuer's actual
        markup including DCC handling. Hiding markup = customer-
        harm + suspension.

RULE 9: estimated_credit_limit_inr cannot be aspirational. Display
        must be based on actual eligibility derived from soft pull.
        Bait-and-switch = suspension.

RULE 10: network_tier_label must reflect the ACTUAL network tier of
         the card. Labelling a Mastercard Titanium as "Mastercard
         World" to win taste signals = network-violation + suspension.

RULE 11: artificial_urgency_text forbidden ("Free joining last day!"
         without verifiable promotion calendar).

RULE 12: No AI-generated card / issuer imagery. Real card images
         only, per issuer brand-use agreement.

RULE 13: amount_inr in CPC is partner / DSA commission only — NEVER
         pass-through (which is always zero for cards).

RULE 14: No "Top Pick" / "Best for Travel" / "Recommended" badges.

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

RULE 16: For co-branded cards, co_brand_partner must be the actual
         co-branding partner (not a similar-name imposter).

RULE 17: Joining / annual fee in §5 must match what issuer charges
         at activation. Surprise fees = immediate suspension.

RULE 18: complimentary_benefits flags (priority_pass / golf /
         concierge / movie / travel insurance) must match issuer's
         actual disclosure for this card variant. Padding =
         customer-harm + suspension.

RULE 19: For secured / FD-backed cards, secured_fd_amount_inr must
         match the FD lien value. Misrepresenting lien amount =
         consumer-law breach + suspension.

RULE 20: emi_conversion.interest_rate_pct must match issuer's
         published EMI rate. EMI conversion fee + rate disclosure
         is RBI-mandated.

VERSION HISTORY

v1.0.0 — 2026-05-13 — Initial spec. Credit card application across
                       starter / standard / premium / super_premium /
                       secured. Taste-uplifted TTBS (network +
                       co-brand + use-case fit). NET commission base.
                       MITC enforced. No reward / fee / benefit
                       fabrication.