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.
01Overview
VoltUtility exposes two interfaces, deliberately separated:
| Interface | Who calls it | Authentication | Purpose |
|---|---|---|---|
| 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
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.jsonand 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
- 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.
- The browser sends the request to the operator's own node — not to a public VoltUtility endpoint, and never with a credential.
- 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. - 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.
- The node builds the receipt and stores it through
POST /api/receiptswith the write key. - 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.
| Aggregator | Covers | Credential ownership | Status |
|---|---|---|---|
| VTpass | Electricity, data, airtime, cable TV | Operator's own VTpass account and API keys | Connector interface documented below |
| Buypower | Electricity, data, airtime, cable TV | Operator's own account | Connector interface documented below |
| Quickteller Business (Interswitch) | Bills, airtime, data | Operator's own merchant account | Connector interface documented below |
| Flutterwave Bills | Bills, airtime, data | Operator's own account | Connector 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
| Service | Direction of the request | Providers | Returned to the customer |
|---|---|---|---|
| Electricity (prepaid) | Amount in naira → energy value and units at the tariff rate | AEDC, EEDC, EKEDC, IBEDC, IKEDC, JED, KAEDCO, KEDC, PHED | STS token, units (kWh), customer name and address, tariff code |
| Mobile data | Provider, plan and phone number | MTN, Airtel, Glo, 9mobile | Bundle confirmation and validity |
| Airtime | Provider, phone number and amount | MTN, Airtel, Glo, 9mobile | Top-up confirmation |
| Cable TV | Provider, package and smartcard number | DSTV, GOtv, StarTimes | Subscription 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
| Header | Value | |
|---|---|---|
Content-Type | application/json | required |
X-Receipt-Key | The write key issued to the operator | required |
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.
| Parameter | In | Description |
|---|---|---|
reference | path | The 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
| Field | Type | Description |
|---|---|---|
ref | string | Receipt reference, upper case, unique per receipt. required |
generator | string | The tool that produced the receipt, e.g. voltpay-node. |
status | string | Transaction status as returned upstream, e.g. Successful. |
issued | string | ISO 8601 UTC timestamp of issue. |
date | string | Human-readable date and time as printed on the receipt. |
amount | number | Amount tendered in naira. |
amountText | string | The same amount, formatted for display. |
vatRate | number | VAT rate applied, 0.075 at 7.5%. |
vatBasis | string | How VAT was derived. energy-value means VAT is 7.5% of the energy value and is added on top: energyValue = amount ÷ 1.075. |
showVat | boolean | Whether 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. |
vat | number | VAT amount in naira. |
vatText | string | The VAT amount, formatted. |
energyValue | number | Value of the energy purchased, excluding VAT. |
energyValueText | string | The energy value, formatted. |
units | number | null | Energy purchased in kWh, to one decimal place. null where the service is not metered (data, airtime, cable). |
token | string | The STS token as printed, grouped in fours. Empty for non-electricity services. |
customer.name | string | Customer name as returned by the utility. |
customer.meterNo | string | Meter, phone or smartcard number. |
customer.address | string | Service address as returned by the utility. |
customer.tariffCode | string | Tariff class code, e.g. R2. |
customer.tariffRate | number | Tariff 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.
| Status | Body | Meaning |
|---|---|---|
| 400 | body must be valid JSON | The request body could not be parsed. |
| 400 | body must be a JSON object | The body was an array or a scalar. |
| 400 | missing or invalid ref | No ref, or it does not match the pattern in section 07. |
| 400 | invalid reference | The path reference is malformed. |
| 401 | unauthorized | The X-Receipt-Key header is missing or wrong. |
| 404 | No receipt found for that reference. | No receipt stored for that reference; retry briefly before concluding it never existed. |
| 405 | method not allowed | A method other than GET, HEAD or POST on this endpoint. |
| 413 | receipt too large | Body exceeded 64 KB. |
| 503 | receipt store unavailable | The 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
| Version | Date | Change |
|---|---|---|
| v1.0 | Sep 2026 | Receipt store: keyed write and public read by reference; receipts persisted without a conventional database. |
| v1.1 | Sep 2026 | VAT 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.2 | 23 Sep 2026 | Result-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] |
| Site | www.voltsng.com |
13Legal
VTpass, Buypower, Quickteller, Interswitch and Flutterwave are the names and trademarks of their respective owners, as are the names of the distribution companies, networks and broadcasters listed above. They are named here to describe the class of integration this software is built for.
VoltUtility is not affiliated with, endorsed by, sponsored by, or an authorised reseller or agent of any of them. Aggregator accounts and credentials are held by the operator of the deployment, who contracts with the aggregator directly. Nothing on this page is an offer to sell tokens, credit or subscriptions, and nothing on it should be read as a representation of any provider's approval of this software.