Pay a bill

Electricity, airtime, pay TV and tax — with the account checked before the money goes.

A bill payment is a payout with a biller on the other end. That is not a simplification: it is a payout, so everything that governs one governs this — the funds are reserved, your approval thresholds apply, the per-tier and per-day limits apply, and the destination is screened. A bill above your threshold comes back waiting on a signature and pays nobody until it is signed.

Bills are a dashboard session. An API key can neither read nor create them.

What can be paid

curl https://sandbox.inversepay.app/api/b/v1/bill-payments/catalog \
  -H "Authorization: Bearer $INVERSEPAY_SESSION_TOKEN"
service_typeproviderWhat the account number is
electricityREGMeter number
airtimeMTN, Airtel, TigoPhone number
paytvStarTimesSmartcard number
taxRRADeclaration number

The catalogue is the same list the consumer app sells — two hand-maintained lists drift within a release. What differs is the rate, and the response carries yours.

Check the account first

curl -X POST https://sandbox.inversepay.app/api/b/v1/bill-payments/validate \
  -H "Authorization: Bearer $INVERSEPAY_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "service_type": "electricity",
    "provider": "REG",
    "account_number": "04123456789"
  }'

The biller answers with the name on the meter, the phone or the smartcard. A mistyped digit costs a phone call when it is caught here and the money when it is not.

A biller that cannot answer returns nothing rather than failing. Most of them cannot, and refusing to pay a biller because it has no lookup would be worse than paying one unchecked. So treat an empty answer as "not checked", never as "does not exist".

Pay it

curl -X POST https://sandbox.inversepay.app/api/b/v1/bill-payments \
  -H "Authorization: Bearer $INVERSEPAY_SESSION_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "office-power-2026-08",
    "service_type": "electricity",
    "provider": "REG",
    "account_number": "04123456789",
    "amount_minor": 5000000,
    "currency": "RWF",
    "country_code": "RW",
    "note": "Kigali office — August"
  }'
Field
idempotency_keyrequired8–128 characters.
service_typerequiredFrom the catalogue.
providerrequiredFrom the catalogue.
account_numberrequiredThe meter, phone or smartcard.
amount_minorrequiredMinor units. 50,000 RWF is 5000000.
currencyrequired
productoptionalWhere a biller sells more than one thing.
country_codeoptional
noteoptionalYours.

The account is validated again as part of paying, and the name the biller returned becomes the payout's recipient_name — so your statement says who was paid rather than repeating the meter number back at you.

Behind it, a payout is raised with method: "bill" and transaction_type: "b2b". A utility company is a business, not a person, and getting that wrong is a different line in a regulator's report.

Don't build a bill by hand as a destination on /payouts. This endpoint composes it, validates the account and records the bill against the payout; a hand-written one skips all three.

When it does not work

An unknown biller is refused before anything is reserved — a provider not in the catalogue cannot be paid at all.

A biller nobody local fronts is a 503: bills may only travel over a rail that can actually deliver them, so there is no falling through to a second partner. For an ordinary bank transfer a second partner is a worse choice and still a choice; for a bill it is money sent somewhere that cannot deliver it.

Everything else — balance, limits, approvals, screening — fails exactly the way a payout does, because it is one.

Then what

On this page