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.
● LIVEv1.0.0pay.utility_bill_pay

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_pay calls
  • "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_pay is 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)