VAS counterparty
Use counterpartyType: "VAS" for value-added services: airtime and data top-ups, electricity, cable TV, water,
betting wallets, and other billers. billerName is required. A request without it is rejected with
counterparty.billerName is required for a VAS counterparty.
| Field | Required | What it means | Why it matters for monitoring |
|---|---|---|---|
billerName | Yes | The biller's display name. | Shown to analysts. |
billerId | No | Your or your aggregator's ID for the biller. | Rules can target a biller. It can also match a biller on your blacklist. |
billerCategory | No | Your category, such as AIRTIME, DATA, ELECTRICITY, CABLE_TV or BETTING. | Category rules, such as limits on betting top-ups. Use consistent values, because rules compare the text exactly. |
accountNumber | No | The customer's account or meter number with the biller, or the phone number being topped up. | Rules can count how many of your customers pay the same recipient. |
accountName | No | The name registered with the biller, when you have it. | Shown to analysts and used for screening. |
Airtime top-up
POST /api/transactions
Authorization: Bearer <oneview-service-account-api-key>
Content-Type: application/json
{
"transactionReference": "VAS-20260921-001207",
"direction": "OUTBOUND",
"accountHolderType": "CUSTOMER",
"paymentMethod": "INTERNAL_TRANSFER",
"channel": "USSD",
"status": "POSTED",
"isCrossBorder": false,
"amount": "2000.00",
"currency": "NGN",
"narration": "Airtime top-up",
"executedAt": "2026-09-21T06:45:18Z",
"accountHolder": {
"customerUniqueId": "CUS-100231",
"accountNumber": "0012345678",
"accountName": "Ada Demo Okafor"
},
"counterparty": {
"counterpartyType": "VAS",
"billerName": "Demo Mobile Network",
"billerId": "BLR-AIRTIME-001",
"billerCategory": "AIRTIME",
"accountNumber": "08000000001"
},
"telemetry": {
"sessionId": "ussd-session-000001207",
"country": "NG"
}
}
Here accountNumber is the phone number receiving the airtime. Many small top-ups to many different numbers from
one account is a common pattern that rules look for.
Electricity bill with fee, tax and ledger postings
The customer pays a prepaid electricity bill with a convenience fee and VAT on the fee. The ledger postings show where each part of the debit went.
POST /api/transactions
Authorization: Bearer <oneview-service-account-api-key>
Content-Type: application/json
{
"transactionReference": "VAS-20260921-001208",
"externalReference": "BILLER-TOKEN-000001208",
"direction": "OUTBOUND",
"accountHolderType": "CUSTOMER",
"paymentMethod": "INTERNAL_TRANSFER",
"channel": "MOBILE_APP",
"status": "POSTED",
"isGlTransaction": true,
"isCrossBorder": false,
"amount": "10000.00",
"currency": "NGN",
"feeAmount": "100.00",
"taxAmount": "7.50",
"narration": "Prepaid electricity meter 04500000001",
"executedAt": "2026-09-21T19:30:41Z",
"accountHolder": {
"customerUniqueId": "CUS-100231",
"accountNumber": "0012345678",
"accountName": "Ada Demo Okafor"
},
"counterparty": {
"counterpartyType": "VAS",
"billerName": "Demo Electricity Distribution",
"billerId": "BLR-POWER-014",
"billerCategory": "ELECTRICITY",
"accountNumber": "04500000001",
"accountName": "Ada Demo Okafor"
},
"ledgerPostings": [
{
"postingId": "VAS-20260921-001208-1",
"glCode": "20100",
"glName": "Customer savings deposits",
"entryType": "DEBIT",
"amount": "10107.50",
"currency": "NGN",
"accountNumber": "0012345678"
},
{
"postingId": "VAS-20260921-001208-2",
"glCode": "20450",
"glName": "Biller settlement payable",
"entryType": "CREDIT",
"amount": "10000.00",
"currency": "NGN"
},
{
"postingId": "VAS-20260921-001208-3",
"glCode": "40230",
"glName": "Bill payment fee income",
"entryType": "CREDIT",
"amount": "100.00",
"currency": "NGN"
},
{
"postingId": "VAS-20260921-001208-4",
"glCode": "25010",
"glName": "VAT payable",
"entryType": "CREDIT",
"amount": "7.50",
"currency": "NGN"
}
]
}
| Field | What it means | Why it matters for monitoring |
|---|---|---|
amount | The bill value, 10000.00. | The value rules compare against. Do not include the fee or tax in it. |
feeAmount | The convenience fee, 100.00. | Fee rules, and helps analysts see the full cost. Defaults to "0". |
taxAmount | VAT on the fee, 7.50. | Kept apart from the fee. Defaults to "0". |
isGlTransaction | true, because ledger legs are attached. | Lets GL rules include the transaction. |
ledgerPostings | One debit of 10107.50 and three credits totalling 10107.50. | Rules can check that the journal balances and inspect each GL leg. |
externalReference | The biller's token or receipt reference. | Reconciliation with the biller. |
The ledger legs are optional. Send them if your core banking system already produces them, and see Internal GL account holder for the leg format rules.
billerCategoryis not a OneView-defined enum.AIRTIME,DATA,ELECTRICITY,CABLE_TVandBETTINGabove are this example's own values — OneView accepts and stores whatever text you send. OneView also does not enforce that aledgerPostingsjournal balances; see the same warning on Internal GL account holder.
Response
This is the response to the airtime top-up above:
{
"status": true,
"message": "Transaction evaluated",
"data": {
"transaction": {
"id": "5a1c9e73-2b4f-4d86-a015-8f3c6e2b0d97",
"transactionReference": "VAS-20260921-001207",
"direction": "OUTBOUND",
"receivedAt": "2026-09-21T06:45:18.203Z",
"replayed": false
},
"decision": {
"outcome": "PASS",
"riskScore": 0,
"evaluatedAt": "2026-09-21T06:45:18.211Z",
"evaluationMs": 6.774,
"complete": true,
"rulesEvaluated": 18,
"rulesetSnapshot": "9ffa3b395a9799b1a1104ab1b536ed1667b487510829ceefc27593941fafb35e",
"gradingVersion": 3,
"triggeredRules": []
}
}
}
Status is 201 for a new transaction, 200 for a replay. See Decision responses for every
outcome.
Errors specific to a VAS counterparty
| Cause | field | code | message |
|---|---|---|---|
No billerName | counterparty | required | counterparty.billerName is required for a VAS counterparty |
A ledger leg on the electricity example has no glCode | ledgerPostings.0 | required | ledgerPostings.0.glCode is required |
See Errors for the complete catalogue.
Idempotency
Deduplication is by transactionReference and direction. A resubmitted identical body, including identical
ledgerPostings, returns 200 with replayed: true without re-debiting the customer or re-posting the ledger
legs. A different body under the same reference and direction, for example a retried top-up with a different
amount, is rejected with 409 IDEMPOTENCY_CONFLICT — use a new reference for a genuinely new top-up. See
Idempotency in Submitting transactions.
Billers are not screened by name
PEP and sanction screening resolves a counterparty's name from firstName/lastName, companyName, then
accountName (see Customer account holder).
billerName is not in that list. A VAS counterparty sent with only billerName, billerId, billerCategory and
accountNumber — the airtime example above — is not screened by name. The electricity example additionally sends
accountName on the counterparty (the customer's own name registered with the biller); because accountName is in
the fallback list, that field alone is what would be screened, not the biller itself. Rules and blacklist matching
on billerId/billerCategory are unaffected either way.