VoltUtilityNG · API docs v1.2 · 23 Sep 2026 Receipt service live Purchase path: server-side connector Open the app →
Integration documentation

VoltUtility NG API

VoltUtility is a bill-payment front end for prepaid electricity, mobile data, airtime and cable television. Purchases are executed server-side through the API of a licensed bill-payment aggregator; every successful purchase produces a receipt that is stored by reference and can be read back from the site.

This page documents both halves of the platform: the aggregator purchase path and the receipt API.

Base URL https://voltsng.com Format JSON, UTF-8 Transport HTTPS only Version v1.2

01Overview

VoltUtility exposes two interfaces, deliberately separated:

InterfaceWho calls itAuthenticationPurpose
Purchase path
server-side
The operator's own node (voltpay-node) The aggregator API credential, held on the server Executes the actual purchase — token, data bundle, airtime, decoder subscription — through a licensed bill-payment aggregator
Receipt API
public + keyed write
The site (read), the operator's node (write) Read: the reference itself. Write: X-Receipt-Key Stores a receipt and reads it back by reference — this is what powers Find a receipt

What VoltUtility is not. VoltUtility does not hold token stock, does not maintain a balance with any utility, and is not a licensed vending agent in its own right. Tokens and subscriptions are obtained from an aggregator under the operator's own account.

Quick start — read a receipt

# every receipt has a reference of the form VP-YYMMDD-XXXXXXXX
curl -s "https://voltsng.com/api/receipts/VP-260923-A1B2C3D4"

Quick start — store a receipt (server-side, keyed)

curl -s -X POST "https://voltsng.com/api/receipts" \
  -H "Content-Type: application/json" \
  -H "X-Receipt-Key: $RECEIPT_KEY" \
  -d @receipt.json

02Architecture

Customer browser │ service → provider → plan → details ▼ https://voltsng.com (Cloudflare Pages · static site + Functions) │ │ │ receipt read / write │ purchase request (server-side only) ▼ ▼ Cloudflare KV · key receipt:<REF> operator node (voltpay-node) ▲ │ holds the aggregator credential │ ▼ │ bill-payment aggregator API │ VTpass · Buypower · Quickteller Business · Flutterwave Bills │ │ └──────── token + units + status ◄───────┤ ▼ utility · telco · broadcaster (DISCO, MTN, Airtel, Glo, 9mobile, DSTV, GOtv, StarTimes)

Three properties of this design matter to an integrator:

  • The credential never reaches the browser. Aggregator keys and operator keys are used server-side only. There is no client-side key of any kind in the website bundle.
  • Receipts are the only public data. Nothing else about a transaction is readable without the write key.
  • The site is static. The purchase flow is driven entirely from assets/plans.json and the receipt API; there is no server-side session to leak.

03Purchases and the bill-payment aggregator

Every purchase is executed server-side against a third-party bill-payment API. VoltUtility does not vend directly from any utility or telco system. The aggregator used is a licensed Nigerian bill-payment aggregator — for example the VTpass API, or an equivalent aggregator API such as Buypower, Quickteller Business (Interswitch) or Flutterwave Bills.

The aggregator account, its API key and its funding belong to the operator of the deployment. They are configured on the operator's own node, are used only from the server, and are never resold, shared or proxied by VoltUtility.

3.1 How a purchase is executed

  1. The customer selects a service, provider, plan and amount in the browser. Customer details (meter number, phone number, smartcard number) are entered in the form.
  2. The browser sends the request to the operator's own node — not to a public VoltUtility endpoint, and never with a credential.
  3. The node validates the request, applies the tariff or plan data held in assets/plans.json, and calls the aggregator API with the operator's credential.
  4. The aggregator executes the purchase with the utility, telco or broadcaster and returns the result — for prepaid electricity, a STS token, the units and the customer's details as held by the DISCO.
  5. The node builds the receipt and stores it through POST /api/receipts with the write key.
  6. The customer receives the printed or downloaded receipt, whose reference can later be read back from the receipt API.

No purchase, no receipt. A receipt is written only after a successful upstream response. If the aggregator returns an error, or no credential is configured for the service, no receipt is issued and nothing is recorded — the flow stops.

3.2 Aggregator connectors

A connector is the small server-side component that speaks to one aggregator. Swapping aggregator does not change the site or the receipt format: it changes the connector's base URL and credential.

AggregatorCoversCredential ownershipStatus
VTpassElectricity, data, airtime, cable TVOperator's own VTpass account and API keysConnector interface documented below
BuypowerElectricity, data, airtime, cable TVOperator's own accountConnector interface documented below
Quickteller Business (Interswitch)Bills, airtime, dataOperator's own merchant accountConnector interface documented below
Flutterwave BillsBills, airtime, dataOperator's own accountConnector interface documented below

The aggregator actually used by a deployment is a configuration value on the operator's node, not a property of the website. VoltUtility names these providers as the class of integration it is built for; the commercial relationship is between the operator and the aggregator.

3.3 Connector contract (server-side specification)

The connector is not a public endpoint. It runs on the operator's node, bound to the loopback interface, and exposes the following canonical shape which each connector adapter translates into the target aggregator's own API per that aggregator's published documentation.

Purchase request

POST /api/token            // operator node, loopback only
// query parameters
service   = electricity | data | airtime | cable
provider  = AEDC | MTN | DSTV | ...          // code from assets/plans.json
meterNo   = ••••••54321                       // meter, phone or smartcard number
amount    = 100000                            // naira, prepaid electricity
plan      = 1GB-30D                           // data and cable selections
custNo    = 1234567890                        // where the provider requires it

Purchase response

{
  "status":    "Successful",
  "token":     "1234-5678-9012-3456-7890",   // STS token, prepaid electricity
  "units":     "444.0",                  // kWh
  "amount":    100000,
  "reference": "VP-260923-A1B2C3D4",
  "customer": {
    "name":        "SAMPLE CUSTOMER",
    "meterNo":     "••••••54321",
    "address":     "12 Sample Close, Abuja",
    "tariffCode":  "R2",
    "tariffRate":  209.5
  }
}

Where the aggregator returns a different field naming (aggregators differ), the adapter normalises the response into the shape above before the receipt is built, so the receipt schema in section 06 is stable across aggregators.

3.4 Services and providers

ServiceDirection of the requestProvidersReturned to the customer
Electricity (prepaid)Amount in naira → energy value and units at the tariff rateAEDC, EEDC, EKEDC, IBEDC, IKEDC, JED, KAEDCO, KEDC, PHEDSTS token, units (kWh), customer name and address, tariff code
Mobile dataProvider, plan and phone numberMTN, Airtel, Glo, 9mobileBundle confirmation and validity
AirtimeProvider, phone number and amountMTN, Airtel, Glo, 9mobileTop-up confirmation
Cable TVProvider, package and smartcard numberDSTV, GOtv, StarTimesSubscription confirmation and period

Plan and bouquet prices are held in assets/plans.json, compiled from operator-published price lists and marked as indicative; the aggregator's response is authoritative for the amount actually applied.

3.5 When a service is unavailable

If no aggregator credential is configured for a service in a given deployment, or the aggregator returns an error or is unreachable, the flow ends with the notice “Service is down at this time.”

No token is invented, no units are estimated, no receipt is stored and no dummy row is printed. This is deliberate: a receipt in this system always corresponds to a real upstream transaction.

04Receipt API — store a receipt

POST/api/receipts

Stores a generated receipt. Call it from a server you control — it is the endpoint the operator's node uses after every successful purchase.

Headers

HeaderValue
Content-Typeapplication/jsonrequired
X-Receipt-KeyThe write key issued to the operatorrequired

Body

The receipt object. Only ref is validated by the store; the rest is stored as sent. Maximum body 64 KB.

Response — 200

{
  "ok":  true,
  "ref": "VP-260923-A1B2C3D4",
  "url": "https://voltsng.com/?ref=VP-260923-A1B2C3D4"
}

Example

curl -s -X POST "https://voltsng.com/api/receipts" \
  -H "Content-Type: application/json" \
  -H "X-Receipt-Key: $RECEIPT_KEY" \
  -d '{
    "ref": "VP-260923-A1B2C3D4",
    "generator": "voltpay-node",
    "status": "Successful",
    "issued": "2026-09-23T09:14:07.204Z",
    "date": "23 September 2026, 10:14",
    "amount": 100000,
    "amountText": "₦100,000.00",
    "vatRate": 0.075,
    "vatBasis": "energy-value",
    "showVat": true,
    "vat": 6976.74,
    "vatText": "₦6,976.74",
    "energyValue": 93023.26,
    "energyValueText": "₦93,023.26",
    "units": 444.0,
    "token": "1234-5678-9012-3456-7890",
    "customer": {
      "name": "SAMPLE CUSTOMER",
      "meterNo": "••••••54321",
      "address": "12 Sample Close, Abuja",
      "tariffCode": "R2",
      "tariffRate": 209.5
    }
  }'

05Receipt API — read a receipt

GET/api/receipts/{reference}

Returns the stored receipt for a reference. This is the endpoint behind Find a receipt. No key is required — the reference is the access token.

ParameterInDescription
referencepathThe receipt reference, e.g. VP-260923-A1B2C3D4. Case-insensitive; normalised to upper case.

Response — 200

The receipt object as stored. Cache-Control: no-store.

Response — 404

{ "error": "No receipt found for that reference." }

Eventual consistency. The receipt store is a globally distributed key-value store; a brand-new write can take up to about 60 seconds to be readable from every location. Clients should retry a 404 two or three times with a short delay before reporting that a reference does not exist — the site does exactly this.

Example

curl -s "https://voltsng.com/api/receipts/VP-260923-A1B2C3D4"

06Receipt object reference

FieldTypeDescription
refstringReceipt reference, upper case, unique per receipt. required
generatorstringThe tool that produced the receipt, e.g. voltpay-node.
statusstringTransaction status as returned upstream, e.g. Successful.
issuedstringISO 8601 UTC timestamp of issue.
datestringHuman-readable date and time as printed on the receipt.
amountnumberAmount tendered in naira.
amountTextstringThe same amount, formatted for display.
vatRatenumberVAT rate applied, 0.075 at 7.5%.
vatBasisstringHow VAT was derived. energy-value means VAT is 7.5% of the energy value and is added on top: energyValue = amount ÷ 1.075.
showVatbooleanWhether the VAT line is printed on the receipt. The arithmetic is identical either way; this flag is stored so the on-screen receipt matches the printed one exactly.
vatnumberVAT amount in naira.
vatTextstringThe VAT amount, formatted.
energyValuenumberValue of the energy purchased, excluding VAT.
energyValueTextstringThe energy value, formatted.
unitsnumber | nullEnergy purchased in kWh, to one decimal place. null where the service is not metered (data, airtime, cable).
tokenstringThe STS token as printed, grouped in fours. Empty for non-electricity services.
customer.namestringCustomer name as returned by the utility.
customer.meterNostringMeter, phone or smartcard number.
customer.addressstringService address as returned by the utility.
customer.tariffCodestringTariff class code, e.g. R2.
customer.tariffRatenumberTariff rate in naira per kWh used to compute units.

Worked example. ₦100,000 at a tariff of ₦209.50/kWh: energy value 100,000 ÷ 1.075 = ₦93,023.26, VAT ₦6,976.74, units 93,023.26 ÷ 209.50 = 444.0 kWh (rounded to one decimal place).

07References and idempotency

A reference is a string, upper case, and must match:

^[A-Z0-9][A-Z0-9-]{5,39}$          // 6–40 characters

The site issues references in the form VP-YYMMDD-XXXXXXXX — prefix, date of issue, and an eight-character random suffix, for example VP-260923-A1B2C3D4.

  • Idempotent by reference. Storing a reference again replaces the record. Retrying a write after a network error is therefore safe, and the reference is the natural idempotency key for a purchase.
  • Uniqueness is the caller's responsibility. The store does not reject a duplicate reference; it overwrites.
  • Do not recycle a reference for a different transaction — a reference is the only handle a customer has on their receipt.

08Errors

Errors are returned as JSON with an error string.

StatusBodyMeaning
400body must be valid JSONThe request body could not be parsed.
400body must be a JSON objectThe body was an array or a scalar.
400missing or invalid refNo ref, or it does not match the pattern in section 07.
400invalid referenceThe path reference is malformed.
401unauthorizedThe X-Receipt-Key header is missing or wrong.
404No receipt found for that reference.No receipt stored for that reference; retry briefly before concluding it never existed.
405method not allowedA method other than GET, HEAD or POST on this endpoint.
413receipt too largeBody exceeded 64 KB.
503receipt store unavailableThe store binding is not available to the running deployment.

Upstream aggregator errors are not exposed on the public API. In the product they surface as the service notice described in section 03.5.

09Rate limits and caching

  • Writes are limited to holders of the write key and are intended for one call per completed purchase. There is no published hard quota; sustained abuse of the write path will be blocked at the edge.
  • Reads are unauthenticated and served with Cache-Control: no-store. Please do not poll — one lookup per reference, with the retry described in section 05.
  • Static assets (css/styles.css, js/app.js, assets/*) are versioned with a ?v= query string and cached at the edge.

10Security and data protection

  • The write key is a server-side secret. It is held in the Pages environment and on the operator's node, is never shipped in the browser bundle, and must be rotated if it is ever exposed. Requests without it are rejected before any parsing.
  • Constant-time comparison. The key check does not short-circuit, so it cannot be probed by timing.
  • A reference is a bearer credential. Anyone holding a reference can read that receipt, including the customer's name, meter number and address. Share references the way you would share a receipt — with the customer, not publicly.
  • No payment data. VoltUtility is not a payment processor and stores no card or bank credentials. Purchases are settled upstream; the receipt records the transaction, not the means of payment.
  • Receipts contain personal data (name, service address, meter or phone number). Where a deployment handles them at scale, the operator is a data controller for that data and should hold it under a lawful basis, keep a retention rule and restrict access to it.
  • Transport. HTTPS only; plain HTTP is redirected. Responses are sent with X-Content-Type-Options: nosniff.

11Versioning and changelog

VersionDateChange
v1.0Sep 2026Receipt store: keyed write and public read by reference; receipts persisted without a conventional database.
v1.1Sep 2026VAT line made optional. showVat, vatBasis and the energy-value fields added to the receipt object, so a hidden VAT line stays hidden when the receipt is read back.
v1.223 Sep 2026Result-only receipt view on lookup; this documentation page published.

The receipt object is versioned additively — new fields may appear, existing ones will not change meaning without a version note here.

12Support

General enquiries[email protected]
Integration & recurring customers[email protected]
Sitewww.voltsng.com