> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hyparrow.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Bill Payments

> Buy airtime and data, pay for cable TV and electricity, and validate customers before charging the wallet.

## Overview

The Bills API lets you browse a catalog of billers, validate a customer (meter,
smartcard/IUC, or account) before charging, and pay for airtime, data, cable TV,
and electricity. Every successful payment debits your Hyparrow wallet.

There are two route families:

* **`/bills/*`** — the primary catalog-driven flow. You discover billers and
  payment items from the cache, validate a customer, then pay by `paymentCode`.
* **`/bills/topup/*`** — an alternate bill-pay route set that resolves products by
  catalog ids (network / plan / provider) instead of payment codes, and also
  supports printable PIN products (exam PINs, recharge-card PINs, data PINs).

Both families ultimately route to the bill provider and the same wallet.

<Note>
  **Base URL:** `https://api.hyparrow.cloud/api/v1`

  **Auth:** every request requires `X-API-Key` and `X-API-Secret` headers.

  **Sandbox:** point at `https://sandbox.hyparrow.cloud/api/v1` and use your
  `pk_test_` key. In sandbox you can simulate outcomes with magic amounts — an
  amount ending in `.01` forces a **failed** result, and `.99` forces a **pending**
  result. Everything else succeeds.
</Note>

### Amount units

| Route family     | Amount field      | Unit                                                                                     |
| ---------------- | ----------------- | ---------------------------------------------------------------------------------------- |
| `/bills/pay`     | `amount` (string) | **Minor units (kobo)** — must match the cached payment item amount for fixed-price items |
| `/bills/topup/*` | `amount` (string) | **Kobo** — the base price; Hyparrow adds its markup on top before debiting               |

## Discovering billers

The primary flow is driven by a cached catalog. Walk it from categories down to
the specific payment item, whose `PaymentCode` you pass to `/bills/pay`.

<Steps>
  <Step title="List categories">
    `GET /bills/categories` returns the available bill categories (airtime, data,
    cable TV, electricity, and more).
  </Step>

  <Step title="List billers in a category">
    `GET /bills/category/billers?category=<name|id>` returns the billers under a
    category. You can also browse everything with `GET /bills/services`.
  </Step>

  <Step title="Get payment options">
    `GET /bills/services/options?serviceId=<id>` returns the payment items
    (plans / packages) for a biller, each carrying the `PaymentCode` and `Amount`
    you need to pay.
  </Step>

  <Step title="Find a specific biller (optional)">
    `GET /bills/search?q=<term>` free-text searches the catalog, and
    `GET /bills/biller?category=<name>&biller=<name>` returns a single biller
    matched by category + biller name.
  </Step>
</Steps>

```bash List categories theme={null}
curl https://api.hyparrow.cloud/api/v1/bills/categories \
  -H "X-API-Key: $HYPARROW_KEY" \
  -H "X-API-Secret: $HYPARROW_SECRET"
```

```json Response theme={null}
{
  "success": true,
  "count": 6,
  "data": [
    { "Id": "1", "Name": "Airtime" },
    { "Id": "2", "Name": "Mobile Data" },
    { "Id": "4", "Name": "Cable TV" },
    { "Id": "5", "Name": "Utility Bills" }
  ],
  "error": ""
}
```

## Validate before you pay

For services tied to a customer account — electricity meters and cable
smartcards/IUCs — validate the customer first. This confirms the beneficiary name
and that the account is payable before any money leaves the wallet.

```bash Validate a customer theme={null}
curl -X POST https://api.hyparrow.cloud/api/v1/bills/validate \
  -H "X-API-Key: $HYPARROW_KEY" \
  -H "X-API-Secret: $HYPARROW_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "0123456789",
    "paymentCode": "10401"
  }'
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "CustomerId": "0123456789",
    "FullName": "JANE DOE",
    "Amount": "0",
    "AmountType": "OPEN"
  },
  "error": ""
}
```

<Note>
  `/bills/validate` accepts both the flat shape above and an array shape
  (`{ "customers": [{ "CustomerId": "...", "PaymentCode": "..." }] }`). Both
  `customerId` and `paymentCode` are required.
</Note>

## Paying a bill

`POST /bills/pay` charges the wallet and submits the payment. The amount is in
kobo and, for fixed-price payment items, must exactly match the cached item
amount — otherwise the request is rejected with the required amount. For
flexible-price items you supply the amount; if you omit it on a fixed-price item,
Hyparrow auto-fills it from the catalog.

<Steps>
  <Step title="Resolve the payment item">
    From the catalog, take the `PaymentCode` (and `Amount` for fixed items).
  </Step>

  <Step title="Validate (metered/cable services)">
    Confirm the beneficiary with `/bills/validate`.
  </Step>

  <Step title="Pay">
    Call `/bills/pay`. The wallet is debited and the payment is submitted to the
    bill provider in one call.
  </Step>

  <Step title="Confirm">
    Poll `/bills/status?reference=<ref>` to confirm the final outcome.
  </Step>
</Steps>

```bash Pay a bill theme={null}
curl -X POST https://api.hyparrow.cloud/api/v1/bills/pay \
  -H "X-API-Key: $HYPARROW_KEY" \
  -H "X-API-Secret: $HYPARROW_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "paymentCode": "10401",
    "customerId": "0123456789",
    "amount": "150000",
    "requestRef": "ORDER-2026-0001"
  }'
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "TransactionRef": "MBP1719772800000000000",
    "Amount": "150000",
    "ResponseCode": "90000",
    "ResponseMessage": "Successful"
  },
  "error": ""
}
```

The `data.TransactionRef` (or your own `requestRef`) is what you use to check
status later.

### Examples by category

<CodeGroup>
  ```json Airtime theme={null}
  {
    "paymentCode": "<airtime payment code>",
    "customerId": "08030000000",
    "amount": "50000"
  }
  ```

  ```json Data theme={null}
  {
    "paymentCode": "<data plan payment code>",
    "customerId": "08030000000",
    "amount": "100000"
  }
  ```

  ```json Cable TV theme={null}
  {
    "paymentCode": "<bouquet payment code>",
    "customerId": "1234567890",
    "amount": "650000"
  }
  ```

  ```json Electricity theme={null}
  {
    "paymentCode": "<meter payment code>",
    "customerId": "0123456789",
    "amount": "500000"
  }
  ```
</CodeGroup>

## Checking status

`GET /bills/status?reference=<ref>` returns the latest status for a bill
transaction. Pass either the provider `TransactionRef` returned by `/bills/pay`
or your own `requestRef`.

```json Response theme={null}
{
  "success": true,
  "data": {
    "TransactionRef": "MBP1719772800000000000",
    "Status": "COMPLETED",
    "ResponseCode": "90000"
  },
  "error": ""
}
```

<Note>
  In sandbox, use the magic amounts to exercise status handling: an amount ending
  in `.99` (e.g. `"19999"` for ₦199.99) lands in **pending** so you can test polling,
  and `.01` lands in **failed**.
</Note>

## Edge cases

<Warning>
  **Wrong amount for a fixed-price item.** If a payment item has a fixed price and
  your `amount` does not match it, the request is rejected before any charge:

  ```json theme={null}
  {
    "success": false,
    "error": "Invalid amount. This service requires exactly ₦1,500.00"
  }
  ```
</Warning>

<Warning>
  **Insufficient wallet balance.** If the wallet cannot cover the amount, the
  deduction fails and the payment is never submitted:

  ```json theme={null}
  { "success": false, "error": "insufficient wallet balance" }
  ```
</Warning>

<Warning>
  **Validation service unavailable.** If the upstream customer-validation lookup
  times out, `/bills/validate` returns `503` with a retry-friendly message. Retry
  shortly; do not attempt to pay an unvalidated metered/cable customer.
</Warning>

## Alternate route set — `/bills/topup`

`/bills/topup/*` is an alternate bill-pay route set. Instead of payment codes it
resolves products by catalog ids (network, plan, provider), and it adds printable
PIN products that the primary routes don't cover. Hyparrow prices each category
server-side and debits the result; if the provider declines or errors, the wallet
deduction is **automatically refunded**.

<Note>
  **Airtime costs less than face value.** It carries no markup — Hyparrow passes
  part of its wholesale discount back to you, so `chargeKobo` is *below* the
  `amount` you sent: **1.5% off MTN and Airtel, 2% off GLO and 9mobile**. ₦100 of
  MTN debits ₦98.50 and your customer still receives ₦100. Every other category
  adds a markup instead. Always trust the returned `chargeKobo`.
</Note>

### Resolve catalog ids

`GET /bills/topup/services?service=<type>` returns the catalog you need to build a
request. Valid `service` values include `network`, `data`, `cabletv`,
`cable-provider`, `electricity`, `exampin`, `datapin`, and `recharge-card`.

```bash Fetch data plans theme={null}
curl "https://api.hyparrow.cloud/api/v1/bills/topup/services?service=data" \
  -H "X-API-Key: $HYPARROW_KEY" \
  -H "X-API-Secret: $HYPARROW_SECRET"
```

### Validate (cable / electricity)

`POST /bills/topup/validate` resolves the customer name for a cable IUC or an
electricity meter. Airtime and data have nothing to validate.

```bash Validate a meter theme={null}
curl -X POST https://api.hyparrow.cloud/api/v1/bills/topup/validate \
  -H "X-API-Key: $HYPARROW_KEY" \
  -H "X-API-Secret: $HYPARROW_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "service": "electricity",
    "customerId": "45010101010",
    "provider": "<disco provider id>",
    "meterType": "prepaid"
  }'
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "customerName": "JANE DOE",
    "providerResponse": { "Customer_Name": "JANE DOE" }
  },
  "error": ""
}
```

### Pay (airtime / data / TV / electricity)

`POST /bills/topup` pays a bill resolved by catalog ids. `service` is one of
`airtime`, `data`, `tv`, `electricity`. `amount` is the **base** price in kobo —
the value your customer receives. Hyparrow prices it before debiting: a markup on
most categories, a cashback on airtime.

<CodeGroup>
  ```json Airtime theme={null}
  {
    "service": "airtime",
    "customerId": "08030000000",
    "amount": "50000",
    "network": "<network id>"
  }
  ```

  ```json Data theme={null}
  {
    "service": "data",
    "customerId": "08030000000",
    "amount": "100000",
    "network": "<network id>",
    "plan": "<plan id>"
  }
  ```

  ```json Cable TV theme={null}
  {
    "service": "tv",
    "customerId": "1234567890",
    "amount": "650000",
    "provider": "<cable provider id>",
    "plan": "<bouquet id>",
    "subType": "renew",
    "phone": "08030000000"
  }
  ```

  ```json Electricity theme={null}
  {
    "service": "electricity",
    "customerId": "45010101010",
    "amount": "500000",
    "provider": "<disco provider id>",
    "meterType": "prepaid",
    "phone": "08030000000"
  }
  ```
</CodeGroup>

```json Response theme={null}
{
  "success": true,
  "data": {
    "service": "data",
    "description": "Data bundle",
    "reference": "TPM1719772800000000000",
    "TransactionRef": "TPM1719772800000000000",
    "baseKobo": 100000,
    "markupKobo": 25000,
    "chargeKobo": 125000,
    "status": "successful",
    "message": "Transaction successful",
    "providerResponse": { }
  },
  "error": ""
}
```

The response exposes the full charge breakdown: `baseKobo` (what you sent),
`markupKobo` (Hyparrow's markup), `cashbackKobo` (the discount off face value,
airtime only) and `chargeKobo` (what the wallet was debited). `markupKobo` and
`cashbackKobo` are never both non-zero — a category is priced one way or the
other.

```json Airtime response — charged below face value theme={null}
{
  "success": true,
  "data": {
    "service": "airtime",
    "baseKobo": 10000,
    "markupKobo": 0,
    "cashbackKobo": 150,
    "chargeKobo": 9850
  },
  "error": ""
}
```

<Warning>
  **Declined purchase is refunded.** If the bill provider declines (e.g. invalid
  plan), the endpoint returns `success: false` with the provider message, and the
  wallet deduction is refunded automatically:

  ```json theme={null}
  {
    "success": false,
    "error": "Invalid plan selected",
    "data": {
      "reference": "TPM1719772800000000000",
      "providerResponse": { }
    }
  }
  ```
</Warning>

### PIN products

These routes generate printable PINs. The `amount` you send is the base total
price in kobo (unit price × quantity); the wallet is charged the base plus the
category markup, and a declined purchase is refunded.

<CodeGroup>
  ```bash Exam PINs theme={null}
  curl -X POST https://api.hyparrow.cloud/api/v1/bills/topup/exampin \
    -H "X-API-Key: $HYPARROW_KEY" \
    -H "X-API-Secret: $HYPARROW_SECRET" \
    -H "Content-Type: application/json" \
    -d '{
      "provider": "1",
      "quantity": "2",
      "amount": "700000"
    }'
  ```

  ```bash Recharge-card PINs theme={null}
  curl -X POST https://api.hyparrow.cloud/api/v1/bills/topup/rechargepin \
    -H "X-API-Key: $HYPARROW_KEY" \
    -H "X-API-Secret: $HYPARROW_SECRET" \
    -H "Content-Type: application/json" \
    -d '{
      "network": "<network id>",
      "quantity": "10",
      "plan": "<denomination id>",
      "businessName": "Acme Stores",
      "amount": "100000"
    }'
  ```

  ```bash Data PINs theme={null}
  curl -X POST https://api.hyparrow.cloud/api/v1/bills/topup/datapin \
    -H "X-API-Key: $HYPARROW_KEY" \
    -H "X-API-Secret: $HYPARROW_SECRET" \
    -H "Content-Type: application/json" \
    -d '{
      "network": "<network id>",
      "quantity": "5",
      "dataPlan": "<data pin plan id>",
      "businessName": "Acme Stores",
      "amount": "250000"
    }'
  ```
</CodeGroup>

```json Response theme={null}
{
  "success": true,
  "data": {
    "service": "Exam PIN",
    "reference": "TPM1719772800000000000",
    "TransactionRef": "TPM1719772800000000000",
    "baseKobo": 700000,
    "markupKobo": 35000,
    "chargeKobo": 735000,
    "status": "successful",
    "message": "Transaction successful",
    "providerResponse": { }
  },
  "error": ""
}
```

| Route                           | Required fields                   | Notes                                                          |
| ------------------------------- | --------------------------------- | -------------------------------------------------------------- |
| `POST /bills/topup/exampin`     | `provider`, `quantity`            | WAEC / NECO / NABTEB exam PINs                                 |
| `POST /bills/topup/rechargepin` | `network`, `quantity`, `plan`     | Printable airtime recharge-card PINs (no markup, pass-through) |
| `POST /bills/topup/datapin`     | `network`, `quantity`, `dataPlan` | Printable data PINs                                            |

<Note>
  Status for `/bills/topup/*` payments is available through the same
  `GET /bills/status?reference=<ref>` endpoint, using the `reference` returned by
  the payment call.
</Note>
