Create a virtual account
Allocates the next free number from a LOCAL band and records the rules a payment to it must satisfy. The number is live immediately; a payer at any bank can name-enquire and pay it as soon as you have the response. You need a LOCAL band assigned to the customer who owns the account in X-Agent-Account; ask your relationship manager if the call returns “No allocatable LOCAL virtual account range”.
Request
Section titled “Request”| Header | Required | Description |
|---|---|---|
X-Agent-Account |
Yes | A 10-digit account you may read: your own, or a sub-agent’s transaction account. Decides whose band is used and the fallback settlement account. |
The body is JSON. Every field is optional; an empty object is a valid request and yields a single-use number with no expiry, no amount rule, the name VIRTUAL ACCOUNT and a generated reference.
| Field | Type | Required | Description |
|---|---|---|---|
account_name |
string | No | The name a payer sees at name enquiry. Stored upper-cased. Default Virtual Account. Max 255 characters. |
external_ref |
string | No | Your reference for this number (order id, invoice number). Must be unique across all numbers your bank has ever issued to the tenant. A random UUID is generated if omitted. Max 255 characters. |
amount |
decimal | No | The amount you expect. Sent as a JSON number or a numeric string. |
exact_amount |
boolean | No | When true (the default) and amount is set, a transfer for any other value is refused. false keeps amount as a note only. Ignored when amount is absent. |
multi |
boolean | No | true accepts any number of payments. Default false: the number becomes USED after the first accepted payment. |
ttl |
integer | No | Minutes until the number expires. 0 or omitted means no expiry. |
beneficiary_account |
string | No | 10-digit account the payments settle to. Must exist at the bank and be able to receive credits. |
curl -X POST https://api.bankcore.ng/api/legacy/agent/virtual_account/create \ -H "Authorization: Bearer $TOKEN" \ -H "X-Agent-Account: 6020012345" \ -H "Content-Type: application/json" \ -d '{ "account_name": "ACME STORES ORDER 1042", "external_ref": "ORD-1042", "amount": 50000.00, "exact_amount": true, "ttl": 1440 }'{ "account_name": "ACME STORES ORDER 1042", "external_ref": "ORD-1042", "amount": 50000.00, "exact_amount": true, "ttl": 1440}{ "account_name": "ACME STORES COLLECTIONS", "external_ref": "COLL-2026-11", "multi": true, "beneficiary_account": "6020023456"}Response
Section titled “Response”{ "code": "0000", "desc": "4000001042"}| Field | Description |
|---|---|
code |
0000 on success. |
desc |
The 10-digit virtual account number. On failure, the reason in words. |
Nothing else is returned. Keep your own record of external_ref against the number; the portal and the native lookup GET /api/virtual-accounts/by-account/{accountNumber} can return the full record later.
Errors
Section titled “Errors”HTTP 200 always. Read code, then desc.
| Code | desc begins with |
When |
|---|---|---|
0012 |
Account ... is not linked to this API client |
X-Agent-Account names an account the ownership guard does not let you read. |
0096 |
X-Agent-Account header is required |
Header missing or blank. |
0096 |
VA creation failed: Agent account not found: ... |
The header account does not exist in your tenant. |
0096 |
Account does not belong to an agent |
The header account’s owner is an individual customer, not a corporate or agent. |
0096 |
VA creation failed: Duplicate external reference: ... |
external_ref was used before. Nothing was created. |
0096 |
VA creation failed: No allocatable LOCAL virtual account range found for customer: ... |
The owner of the header account has no LOCAL band, or the band is closed to new numbers. |
0096 |
VA creation failed: All virtual account ranges exhausted ... or Virtual account range ... exhausted |
Every band is full. Ask the bank for more space. |
0096 |
VA creation failed: Beneficiary account not found: ... |
beneficiary_account does not exist at the bank. |
0096 |
VA creation failed: Beneficiary account ... is not active (status: ...) |
beneficiary_account is closed, frozen, dormant or restricted. |
0096 |
VA creation failed: Settlement account ... |
No beneficiary_account, no band-linked account, and the header account cannot receive credits. |
Most failures share the code 0096; the text is the discriminator. Do not retry a Duplicate external reference with the same reference.
Settlement account precedence. beneficiary_account wins. Without it, an account the bank linked to the band wins over your header account, so on a band the bank has pointed at one collection account, payments go there whatever X-Agent-Account says. Without either, the header account is used; without that, the oldest account of the band’s owner.
Reference uniqueness is tenant-wide and permanent. An external_ref from an expired or used number cannot be reused. Make references globally unique on your side (prefix with your system name).
Expiry is judged at arrival. A payment that reached the bank while the number was live is credited even if the number lapses while NIBSS confirms it. A payment that arrives after expiry is refused before the money moves.
Bands reserved to your client are drained first; after that the shared bands of the same customer, lowest start first. Numbers may be sequential or permuted across the band depending on how the bank configured it, so do not infer anything from the order you receive them in.
Idempotency. The call is not idempotent on its own. Use external_ref as your idempotency key: a retry after a timeout returns 0096 Duplicate external reference if the first call succeeded, and the portal or native lookup will give you the number.
In the portal
Section titled “In the portal”The Virtual accounts page lists the number the moment it is created, with status, expiry, the amount rule, your external_ref under “client reference”, and the settlement account.