pay.utility_bill_pay — Full Intent Specification
INTENT NAMESPACE: pay
INTENT NAME: utility_bill_pay
FULL ID: pay.utility_bill_pay
VERSION: v1.0.0
STATUS: live
TTBS WEIGHTS: time 0.30 · taste 0.10 · budget 0.30 · safety 0.30
LAST UPDATED: 2026-05-10
Cross-cutting utility intent. Pay electricity, gas, mobile, broadband, water, DTH, LPG, postpaid card bills via BBPS / partner aggregators. TOMO never holds the rail — money flows user → biller via partner's BBPS settlement. TOMO orchestrates partner choice + UX + audit log.
1. NATURAL LANGUAGE COVERAGE
Classifies IN
- "pay electricity bill"
- "pay my mobile bill"
- "broadband bill due"
- "Tata Power bill"
- "DTH recharge"
- "LPG cylinder pay"
- "water bill HMWSSB"
- "credit card bill HDFC"
- "Airtel postpaid"
Classifies OUT — borderline NO
- "send money to friend" →
pay.send_money_upi - "split bill at restaurant" →
pay.split_bill - "wallet recharge" →
pay.add_money_to_wallet - "FASTag top-up" →
pay.fastag_topup - "buy insurance" →
finance.buy_motor_insurance
MULTI-INTENT TRIGGERS
- "pay electricity bill of 2400" (standalone, classifies into this intent)
- "pay broadband + mobile" → 2 sequential
pay.utility_bill_paycalls - "remind me when bill is due + auto pay" → setup of recurring pay (out-of-scope v1; v1 is single payment)
2. INPUT — TOMO → PROVIDER
{
"intent": "pay.utility_bill_pay",
"intent_version": "v1.0.0",
"request_id": "req_ub_8f4k_2026-05-10T10:14:00Z",
"user_session_id": "anon_user_token_or_uid",
"biller_kind": "electricity",
"biller_sub_kind": "tata_power_distribution",
"biller_state": "Maharashtra",
"biller_city": "Mumbai",
"biller_circle": "Mumbai_Distribution",
"consumer": {
"consumer_id_masked": "•••• 1234",
"account_kind": "consumer_number",
"registered_phone_masked": "+91 98XXX XX234"
},
"fetch_bill_first": true,
"expected_amount_inr": 2400,
"user_capped_amount_inr": 5000,
"context": {
"user_locale": "en-IN",
"user_currency_pref": "INR",
"trust_signals": {
"is_repeat_payment": true,
"prior_bills_at_partner": 18,
"user_account_age_days": 312,
"biometric_used_recently": true
}
}
}
| Field | Type | Constraint | Notes |
|---|---|---|---|
biller_kind |
enum | REQUIRED, see §6 | top-level utility category |
biller_sub_kind |
enum | REQUIRED, see §6 | specific biller |
biller_state / biller_city / biller_circle |
string | REQUIRED | for state-specific billers |
consumer.consumer_id_masked |
string | REQUIRED | last-4 only |
consumer.account_kind |
enum | REQUIRED, see §6 | drives partner lookup field |
fetch_bill_first |
bool | REQUIRED | true → BBPS pull first; false → user-typed amount |
expected_amount_inr |
INR_INTEGER | REQUIRED | user's expectation; partner returns actual |
user_capped_amount_inr |
INR_INTEGER | REQUIRED | hard ceiling for safety |
Anti-fabrication preamble: no paid placement, no commission-based partner ordering, TOMO never holds the rail.
3. PROVIDER TOOLS
Tool 1: fetch_bill
PURPOSE: pull current bill from biller via BBPS / API
INPUT: { biller_kind, biller_sub_kind, consumer_id, request_id }
OUTPUT: BillFetchResult (§5)
SLA: p95 < 2000ms (BBPS dependency)
RATE LIMIT: ≤ 1/min per (consumer_id, partner) — billers throttle
Tool 2: initiate_payment
PURPOSE: create payment intent against fetched bill
INPUT: { bill_ref OR amount_inr + biller_details, payment_token, idempotency_key, request_id }
OUTPUT: { payment_ref, status, payment_intent_url, expected_clearing_seconds }
SLA: p95 < 2000ms
IDEMPOTENCY: REQUIRED on idempotency_key
Tool 3: confirm_payment
PURPOSE: finalize after user authorizes
INPUT: { payment_ref, npci_or_biller_reference, request_id }
OUTPUT: PaymentStatus (§5)
SLA: p95 < 2000ms
Tool 4: get_payment_status
PURPOSE: poll if confirm_payment hasn't returned final
INPUT: { payment_ref, request_id }
OUTPUT: PaymentStatus (§5)
SLA: p95 < 600ms
RATE LIMIT: ≤ 1 every 5s
Tool 5: get_payment_history
PURPOSE: prior bills for same consumer_id (helps user verify)
INPUT: { consumer_id, biller_kind, request_id, limit }
OUTPUT: array<HistoricalPayment>
SLA: p95 < 1000ms
Tool 6: request_refund
PURPOSE: rare — biller declined post-payment, user disputes
INPUT: { payment_ref, reason, request_id, user_consent_token }
OUTPUT: { refund_status, refund_eta_minutes }
SLA: p95 < 5000ms
All six REQUIRED.
4. RESPONSE SHAPE
BillFetchResult (returned by fetch_bill)
bill_ref: string, REQUIRED # opaque, used in initiate_payment
fetched_at_iso: ISO_DATETIME, REQUIRED
expires_at: ISO_DATETIME, REQUIRED # bill_ref validity
biller:
name: string, REQUIRED
legal_name: string, REQUIRED
bbps_biller_id: string, REQUIRED
bbps_category: STRICT ENUM, REQUIRED # see §6
state: string, REQUIRED
city: string, REQUIRED
service_area: string, REQUIRED # specific circle / region
consumer:
consumer_id_masked: string, REQUIRED
consumer_name_redacted: string, REQUIRED # always REDACTED
account_kind: STRICT ENUM, REQUIRED # see §6
service_address_redacted: string, REQUIRED # always REDACTED
service_status: STRICT ENUM, REQUIRED # active | suspended | terminated
bill:
bill_number: string, REQUIRED # biller-issued
bill_period_from: ISO_DATE, REQUIRED
bill_period_to: ISO_DATE, REQUIRED
bill_issue_date: ISO_DATE, REQUIRED
bill_due_date: ISO_DATE, REQUIRED
bill_amount_inr: INR_INTEGER, REQUIRED
late_fee_already_applied_inr: INR_INTEGER, REQUIRED
late_fee_estimated_per_day_inr: INR_INTEGER, REQUIRED # 0 if no late fee
payable_today_inr: INR_INTEGER, REQUIRED # bill + late so far
partial_payment_allowed: boolean, REQUIRED
partial_payment_min_inr: INR_INTEGER, REQUIRED # 0 if not partial-payable
rebate_for_advance_payment_inr: INR_INTEGER, REQUIRED # 0 if no rebate
past_dues_inr: INR_INTEGER, REQUIRED # carry from prior bills
arrears_kind: STRICT ENUM, REQUIRED # see §6 ("none" if no arrears)
is_disconnected_warning: boolean, REQUIRED # service-cut imminent
disconnect_threat_iso: ISO_DATE, REQUIRED # epoch sentinel if no threat
usage:
current_meter_reading: string, REQUIRED # biller's reading; "" if N/A
prior_meter_reading: string, REQUIRED # "" if N/A
units_consumed: string, REQUIRED # e.g. "245 kWh", "" if N/A
consumption_kind: STRICT ENUM, REQUIRED # see §6 ("none" if N/A)
bill_breakdown: array, REQUIRED, ≥1
- line_label: string, REQUIRED # "Energy charges", "Fixed charge", "GST"
line_amount_inr: INR_INTEGER, REQUIRED # signed
line_kind: STRICT ENUM, REQUIRED # see §6
bill_pdf_url: URL, REQUIRED # downloadable PDF if biller provides
trust:
partner_bbps_authorized_OU: boolean, REQUIRED # BBPS Operating Unit
partner_npci_authorized_psp: boolean, REQUIRED
rbi_authorization_number: string, REQUIRED
rbi_authorization_kind: STRICT ENUM, REQUIRED # see §6
partner_pci_dss_compliant: boolean, REQUIRED
_provider:
name: string, REQUIRED
tomo_partner_id: string, REQUIRED
partner_tier: STRICT ENUM, REQUIRED
customer_support_phone: string, REQUIRED
customer_support_24x7: boolean, REQUIRED
in_app_chat_supported: boolean, REQUIRED
initiate_payment output
payment_ref: string, REQUIRED
status: STRICT ENUM, REQUIRED # see §6
payment_intent_url: URL, REQUIRED
intent_kind: STRICT ENUM, REQUIRED # see §6
expected_clearing_seconds: int, REQUIRED
intent_expires_at: ISO_DATETIME, REQUIRED
amount:
bill_amount_inr: INR_INTEGER, REQUIRED
late_fee_inr: INR_INTEGER, REQUIRED
partner_convenience_fee_inr: INR_INTEGER, REQUIRED # 0 ideally
gst_on_convenience_fee_inr: INR_INTEGER, REQUIRED
total_charged_to_user_inr: INR_INTEGER, REQUIRED # = sum of above
amount_credited_to_biller_inr: INR_INTEGER, REQUIRED # = bill_amount_inr + late_fee_inr exactly
cashback:
applicable: boolean, REQUIRED
amount_inr: INR_INTEGER, REQUIRED # 0 if N/A
cashback_kind: STRICT ENUM, REQUIRED # see §6
credit_iso: ISO_DATETIME, REQUIRED # epoch sentinel if pending
trust:
partner_bbps_authorized_OU: boolean, REQUIRED
partner_pci_dss_level: STRICT ENUM, REQUIRED
refund_policy:
full_refund_window_minutes: int, REQUIRED
refund_eta_days_if_biller_declines: int, REQUIRED
PaymentStatus (returned by confirm + status)
payment_ref: string, REQUIRED
status: STRICT ENUM, REQUIRED # see §6
status_updated_iso: ISO_DATETIME, REQUIRED
status_history: array, REQUIRED, ≥1
debit:
debit_status: STRICT ENUM, REQUIRED # see §6
debit_iso: ISO_DATETIME, REQUIRED
user_bank: string, REQUIRED
user_bank_reference: string, REQUIRED
biller_credit:
credit_status: STRICT ENUM, REQUIRED # see §6
credit_iso: ISO_DATETIME, REQUIRED
biller_receipt_number: string, REQUIRED
biller_account_id: string, REQUIRED # opaque biller-side
biller_response_code: STRICT ENUM, REQUIRED # see §6
bbps:
bbps_transaction_id: string, REQUIRED # standard BBPS ref
bbps_response_code: STRICT ENUM, REQUIRED # see §6
bbps_clearing_iso: ISO_DATETIME, REQUIRED
failure:
failure_reason: STRICT ENUM, REQUIRED # see §6
failure_recovery_action: STRICT ENUM, REQUIRED # see §6
refund_initiated: boolean, REQUIRED
refund_eta_minutes: int, REQUIRED # 0 if no refund
evidence:
receipt_url: URL, REQUIRED # tax-relevant
share_url: URL, REQUIRED
bbps_receipt_pdf_url: URL, REQUIRED
Forbidden fields
paid_placement_score | sponsored_rank | promotion_priority |
ad_bid | hidden_convenience_fee | inflated_late_fee |
unmasked_consumer_id | raw_service_address |
hidden_settlement_delay_to_biller
5. CONTROLLED VOCABULARIES
biller_kind
electricity | gas | water | mobile_postpaid | mobile_prepaid_recharge |
broadband | dth | landline | lpg_cylinder | piped_gas |
postpaid_credit_card | property_tax | municipal_tax |
fastag_recharge_via_bbps | clubs_associations
biller_sub_kind
Examples (incomplete list — actual list per biller_kind):
electricity: tata_power_distribution | bescom | mseb | hdec | tneb | unirep
gas: mahanagar_gas_mumbai | indraprastha_gas_delhi | gail_pune
water: hmwssb_hyderabad | bwssb_bangalore | mcd_delhi
mobile_postpaid: airtel_postpaid | jio_postpaid | vi_postpaid | bsnl_postpaid
broadband: airtel_broadband | jio_fiber | act_fibernet | hathway
dth: tata_play | airtel_dth | dish_tv | sun_direct
postpaid_credit_card: hdfc | icici | axis | sbi_card | amex | indusind_card
consumer.account_kind
consumer_number | service_number | mobile_number | dth_id | account_id |
ledger_number | meter_number | broadband_account
bill.arrears_kind
none | one_month | two_months | three_or_more_months | dispute_pending
usage.consumption_kind
none | kwh | gallon | gigabyte | minutes | sms | days | cubic_meter
bill_breakdown[].line_kind
energy_charge | fixed_charge | duty | service_tax | gst |
late_fee | rebate | adjustment | meter_rent | minimum_charge |
load_charge | electricity_duty | wheeling_charge | rsa_fee |
sgst | cgst | sub_charge | other
biller.bbps_category
Same as biller_kind enum (BBPS category names align).
_provider.intent_kind
upi_intent_app | upi_collect | net_banking | credit_card | debit_card | wallet_balance
initiate_payment.status / PaymentStatus.status
initiated | awaiting_user_authorization | user_authorized |
debit_pending | debited | bbps_clearing | biller_credit_pending |
biller_credited | failed_authorization | failed_debit |
failed_bbps_clearing | failed_biller_credit | refund_initiated |
refund_completed | cancelled_by_user | timeout | manual_review
cashback.cashback_kind
none | flat_inr | pct_of_bill | tier_based | promotional_partner_funded |
bbps_funded | bank_funded
trust.partner_pci_dss_level
not_applicable | level_4 | level_3 | level_2 | level_1
trust.rbi_authorization_kind
PSP | PA | bank_native | NPCI_BBPS_authorized
debit.debit_status
not_started | pending | succeeded | failed | reversed
biller_credit.credit_status
not_started | pending | succeeded | failed | partial_success
biller_credit.biller_response_code
SUCCESS | BILL_PAID | DUPLICATE_BILL | INVALID_BILL_REF |
BILLER_REJECTED | BILLER_DOWNTIME | UNKNOWN_ERROR
bbps.bbps_response_code
SUCCESS | T01 | T02 | U03 | U16 | U28 |
INVALID_AUTH | INVALID_BBPS_REF | UNKNOWN
failure.failure_reason
none | user_declined | insufficient_funds | bill_invalid |
biller_offline | bbps_timeout | npci_timeout |
biller_rejected_post_authorization | duplicate_payment_detected |
fraud_blocked | over_user_capped_amount
failure.failure_recovery_action
none | retry_payment | retry_with_different_psp |
manual_review_by_partner | escalate_to_BBPS |
refund_only | contact_biller_directly | contact_support
request_refund.reason
biller_didnt_credit | duplicate_payment | wrong_consumer_id |
service_already_paid | dispute_with_biller | fraud_suspected
6. TTBS DIMENSIONS
Per-domain weights (locked)
pay (utility overlay): { time: 0.30, taste: 0.10, budget: 0.30, safety: 0.30 }
Balanced — utility bill payment is mature use-case; users want fast, cheap, and reliable.
TIME
SIGNALS USED:
- expected_clearing_seconds (lower = better) weight 0.40
- partner_bbps_uptime_pct weight 0.30
- is_repeat_payment (cached consumer_id lookup) weight 0.10
- intent_kind == upi_intent_app (vs collect) weight 0.20
USER BAND HANDLING:
- bill is_disconnected_warning=true → time weight up to 0.50
- bill_due_date < today + 24h → time weight up
TASTE
SIGNALS USED:
- in_app_chat_supported weight 0.30
- vernacular language support weight 0.30
- cashback availability weight 0.20
- rich bill display (units consumed, comparison vs prior month) weight 0.20
BUDGET
SIGNALS USED:
- partner_convenience_fee_inr (lower = better) weight 0.50
- cashback effective rate weight 0.30
- rebate_for_advance_payment honored weight 0.20
HARD FILTERS:
- partner_convenience_fee_inr > 5 INR for personal bills → drop
(BBPS regulation: convenience fee ≤ 1% of bill, capped at ₹5 typically)
SAFETY
SIGNALS USED:
- trust.partner_bbps_authorized_OU=true HARD FILTER
- trust.rbi_authorization_number valid HARD FILTER
- trust.partner_pci_dss_compliant=true HARD FILTER
- customer_support_24x7 weight 0.30
- failure recovery flow well-defined weight 0.20
- dispute resolution SLA documented weight 0.20
- historical_settlement_to_biller_success_rate weight 0.30
Hidden ranking factor
information_completeness_score weight 0.10.
historical_biller_credit_success_rate weight 0.20 — partners with >2% biller-credit-failure rate get penalized.
7. COMPLETION CONTRACT
POST /api/v1/cpc/mcp_provider/{tomo_partner_id}
X-TOMO-Timestamp: <ms>
X-TOMO-Signature: sha256=<hex>
{
"intent": "pay.utility_bill_pay",
"intent_version": "v1.0.0",
"external_id": "PHONEPE-UB-XYZ",
"amount_inr": 2400,
"closed_at": "2026-05-10T10:18:14+05:30",
"request_id": "req_ub_8f4k_...",
"status": "biller_credited",
"currency": "INR",
"payment_ref": "PHONEPE-UB-XYZ",
"biller_kind": "electricity",
"biller_sub_kind": "tata_power_distribution",
"bill_amount_inr": 2400,
"convenience_fee_inr": 0,
"amount_credited_to_biller_inr": 2400,
"biller_receipt_number": "TPD-2026-05-10-XYZ",
"bbps_transaction_id": "BBPS412345678901",
"credit_iso": "2026-05-10T10:18:14+05:30",
"cashback_credited_inr": 24,
"notes": ""
}
Status enum: biller_credited | failed_debit | failed_bbps_clearing | failed_biller_credit | refund_completed | cancelled_by_user
TOMO commission scenario:
- User pays ₹2400 utility bill
- TOMO commission = 10% × 2400 = ₹240
- BUT — utility bill payments are public infrastructure (BBPS). Founder directive: TOMO commission on
pay.utility_bill_payis set to 0% at v1.
This matches the policy on pay.send_money_upi — public infrastructure rails are not commissioned. Partners report amount_inr for audit/ledger only. Locked at v1; may be revisited.
8. WIDGET
WIDGET TYPE: utility_bill_card
SOURCE: src/widgets/types.ts
TYPE NAME: UtilityBillPayPayload
RENDERED IN: components/widgets/UtilityBillPayWidget.tsx
Card with: biller logo + name + bill period, amount big, due date pill (green/orange/red), partner pick (auto-selected), "Pay ₹X" CTA → opens UPI / netbanking intent.
9. CACHING POLICY
| Call | TTL | Rationale |
|---|---|---|
fetch_bill |
15min | Bills don't change once issued; partial cache OK |
initiate_payment |
0s | Always fresh |
confirm_payment |
0s | |
get_payment_status |
0s | Live |
get_payment_history |
5min | |
request_refund |
0s |
10. ERROR CODES
| Code | HTTP | Meaning | TOMO behavior |
|---|---|---|---|
INVALID_REQUEST |
400 | Malformed | surface |
BILLER_NOT_FOUND |
404 | biller_sub_kind unknown to partner | surface |
CONSUMER_ID_NOT_FOUND |
404 | biller doesn't recognize ID | surface |
BILL_NOT_AVAILABLE |
404 | no current bill (already paid / not yet issued) | surface |
BILLER_OFFLINE |
503 | biller's API down | retry with delay |
BBPS_DOWNTIME |
503 | BBPS down | back off |
INSUFFICIENT_FUNDS |
402 | user account empty | surface |
OVER_CAPPED_AMOUNT |
400 | bill > user_capped_amount_inr | surface, ask for confirmation |
DUPLICATE_PAYMENT |
409 | bill already paid | surface |
BILL_DISPUTED |
422 | biller marks bill disputed | surface, manual flow |
INVALID_AUTH |
401 | partner credentials | partner re-auth |
RATE_LIMITED |
429 | partner-side throttle | back off |
INTERNAL_ERROR |
500 | partner-side failure | drop partner |
11. SANDBOX → PRODUCTION CHECKLIST
[ ] All §2 inputs validated, request_id echoed
[ ] fetch_bill returns valid result for sandbox biller IDs
[ ] initiate_payment returns valid intent_url within SLA
[ ] confirm_payment reflects biller credit within SLA
[ ] cancel + refund flows tested
[ ] All §4 required fields populated with REAL data
[ ] No forbidden fields anywhere
[ ] No unmasked consumer_id / address in responses
[ ] CPC webhook arrives within 60s (audit; 0% commission)
[ ] HMAC verification passes
[ ] BBPS Operating Unit authorization certificate uploaded
[ ] RBI license number valid + verifiable
[ ] PCI DSS attestation
[ ] customer_support 24x7 reachable (mandatory for utility partners)
[ ] No commission-based biller ordering (1% audit cross-check)
[ ] Biller categories supported list published + accurate
[ ] Failure recovery flow tested (BBPS downtime + biller offline simulation)
[ ] BBPS uptime > 99% over last 30 days verified
12. ANTI-FABRICATION RULES
RULE 1 — No paid placement signals
RULE 2 — Convenience fees must be transparent
partner_convenience_fee_inr disclosed in initiate_payment output. Hidden
fees discovered post-charge = breach + refund mandated.
RULE 3 — BBPS authorization mandatory
trust.partner_bbps_authorized_OU=true requires BBPS OU certificate on
demand within 24h. False claims = listing rejection.
RULE 4 — Bills must come from authoritative biller source
fetch_bill returns must derive from BBPS or biller's authoritative API.
Cached results > 15 minutes = breach.
RULE 5 — Late fees must be authoritatively sourced
late_fee_inr from biller, not partner-inflated. TOMO 1% audit cross-checks
vs biller's own portal.
RULE 6 — Biller credit must complete within stated SLA
Failure to credit biller after user-debit = breach + refund required.
Partners with > 2% biller-credit-failure rate get suspended.
RULE 7 — Customer support 24x7 honest
Field-tested.
RULE 8 — TOMO never holds the rail
Money flows user → biller via partner's BBPS settlement. TOMO never custody.
RULE 9 — No commission-based biller ordering
TOMO ranks partners by user-fit (TTBS), not biller-paid promotion.
RULE 10 — Receipts must be permanent
receipt_url + bbps_receipt_pdf_url must remain accessible for 7 years
(Indian tax law). Partners that delete = breach.
RULE 11 — No fabricated "due date imminent" urgency
is_disconnected_warning=true must derive from biller's actual disconnect
notice. Fake urgency to drive payment = breach.
VERSION HISTORY
v1.0.0 — 2026-05-10 — Initial spec (Block F.5)