VAS catalog
Value-added services — airtime, data bundles, and utility payments (ECG, DSTV,
GWCL) — are exposed under POST /api/v1/vas/{airtime|data|utilities}, with
catalog and validation lookups (e.g. meter/smartcard validation) before
payment.
These endpoints ship with the VAS milestone (A8). Once live, they will appear in the API Reference with full request/response schemas and code samples, and this guide will document each service, the provider catalog, and validation flows.
Like every fund-moving call, VAS purchases compute a fee breakdown, post to the ledger, and return the standard envelope.
Your account must be enabled for the service
A vend consults the service StarPay granted you, the same way POST /transfers
consults your disbursements grant:
| You are buying | The grant it needs |
|---|---|
| Airtime | airtime_data |
| Data | airtime_data |
| A utility bill | utility_payments |
You have the grant unless StarPay rejected it. If it was rejected, the call
answers 403 permission_denied, and error.details.requirement names which
one:
{ "status": false,
"error": { "code": "permission_denied",
"message": "This service is not enabled for your account. Contact StarPay support.",
"details": { "requirement": "airtime_data" } } }
Your own metadata
A vend accepts an optional metadata object of your own labels, stored on the
purchase and returned with it. Two keys are reserved for StarPay's own use, and
a request that sends either is refused with 400 validation_error, before
anything is reserved: selfservice_order and selfservice_role. They were
added in October 2026: StarPay's own self-service orders use them to find the
purchase they settle. The payments API refuses the same two names. If your
integration used either as its own label, rename it (for example my_order).
Reading a purchase back
A vend often answers pending and settles seconds or minutes later. Poll it:
GET /api/v1/vas/{reference}
It needs the read scope, and it is scoped to your key's mode, so a test key
never reads a live purchase. The payload adds two fields a failed vend needs:
| Field | What it is |
|---|---|
failure_code |
A short, stable code, for example provider_rejected |
failure_reason |
One sentence you can show a person |
updated_at |
When it last changed, so you can stop polling |
A VAS purchase is not a transaction, so it never appears in
GET /api/v1/transactions. This endpoint is how you learn its outcome.
Bank codes
POST /transfers and POST /resolve/bank-name both take a bank_code. List
the valid ones with:
GET /api/v1/banks
The response names the rail as well as the banks, because a code only means
something inside the namespace of the rail that pays it. Read the codes again if
your account's routing changes.