Skip to content

Responses and error codes

The API has two response families, and you must know which one an endpoint belongs to before you can read its answer. The rule is by path: /api/legacy/** endpoints use the legacy envelope; everything else is BankCore-native. The money-movement, agent and virtual-account endpoints in this documentation are almost all legacy.

Legacy endpoints answer HTTP 200 whether the call succeeded or failed. The outcome is in the body:

{
"code": "0000",
"desc": "Successful"
}

code is a four-digit string and desc a human-readable description safe to log or show. Null fields are omitted from the JSON entirely, so parse defensively: a field that is absent is simply not applicable to that call.

Money-movement calls return an enriched envelope. A successful fund, charge or transfer looks like:

{
"code": "0000",
"desc": "Successful",
"retRef": "TXN20260401143000001",
"clientRef": "SAL-20260401-001",
"fromAccount": "6020000001",
"toAccount": "6020012345",
"availableBalance": "145000.00",
"ledgerBalance": "150000.00",
"transdatetime": "20260401143000"
}

Every field the envelope can carry:

Field Appears on Meaning
code, desc Every legacy response Outcome and description
retRef Money movement, check-transaction BankCore’s own reference for the transaction. Quote it to the bank
clientRef Money movement, check-transaction Your transactionRef, echoed back so you can match the response to your request
fromAccount, toAccount Money movement The accounts debited and credited
availableBalance, ledgerBalance Money movement Balances of the debited account after posting, as strings with two decimals. Available is ledger minus holds
transdatetime Money movement Posting time in yyyyMMddHHmmss; see Environments for the zone caveat
sessionId Interbank transfer The NIP session id for the transfer
kycTier, accountDesignation, idType, nameEnquirySessionId, bvn Name enquiry Beneficiary attributes returned by the enquiry; echo them on the transfer call that follows
token, units, receipt, providerRef, subscriberAddress Bill payment Prepaid token and units vended, the biller’s receipt number, the provider’s reference, and the service address

Legacy list endpoints (statements, searches, downlines, the biller catalogue) do not use the envelope. They answer:

{
"success": true,
"total": 142,
"data": [ { "...": "..." } ]
}

with total the number of matching rows across all pages, and pagination 1-based (page=1 is the first page).

Native endpoints (the token endpoint, KYC documents, device registration, tier upgrades) use real HTTP statuses. A success is a 2xx with a resource body. A failure is a 4xx or 5xx with:

{
"timestamp": "2026-04-01T20:57:11.298Z",
"errorCode": "ENTITY_NOT_FOUND",
"message": "Account not found: 6020099999",
"path": "/api/kyc-documents/by-account/6020099999",
"status": 404
}
Field Meaning
timestamp When the error occurred, ISO-8601 UTC
errorCode A stable, upper-snake-case code to branch on. Business errors carry their own code (INSUFFICIENT_FUNDS, PARTNER_ACCOUNT_FORBIDDEN); framework errors use VALIDATION_ERROR, MALFORMED_REQUEST, INVALID_PARAMETER, ENTITY_NOT_FOUND, UNAUTHORIZED, ACCESS_DENIED, INTERNAL_ERROR
message Human-readable, safe to log
validationErrors Only on VALIDATION_ERROR: a map of field name to problem
path The request path
status The HTTP status, repeated

Native list endpoints are 0-based.

Code Meaning When you will see it
0000 Success The operation completed. For money movement, the posting is final
0004 Insufficient funds The debited account (yours, an agent’s, or your settlement account on a fund) cannot cover amount plus fees
0009 Pending / in flight The operation was accepted and is awaiting an external party: an interbank transfer not yet confirmed by NIBSS, a bill payment awaiting the provider, a POS authorisation awaiting the acquirer. Money may already have moved. Do not retry; poll Check a transaction with the retRef you were given until it settles to 0000 or 0096 (the agent check-transaction reports the state in desc instead)
0012 Invalid / not found A validation failure (missing field, bad format, missing X-Agent-Account), an entity that does not exist, a velocity or amount limit, a watchlisted BVN, or an ownership refusal (PARTNER_ACCOUNT_FORBIDDEN, PARTNER_SUBAGENT_FORBIDDEN). desc says which
0014 Duplicate record An agent with this BVN, NIN or phone number already exists
0062 Account not active The account is frozen, locked, suspended, closed or under a post-no-debit restriction
0094 Duplicate reference Your transactionRef has already been used for this kind of transaction. See Idempotency
0096 System error / failed Either the operation failed in a terminal way (an interbank transfer rejected or reversed, a bill payment the provider refused, with funds restored) or something unexpected happened. Read desc, then check the transaction before doing anything else

For a legacy money-movement call:

  1. Transport failure (timeout, connection reset, HTTP 5xx from the edge): you do not know what happened. On a provider money endpoint, call check-transaction with your transactionRef; if it is not found, retry the original call with the same reference. On an agent endpoint simply retry with the same reference and let 0094 tell you whether it had already posted (the agent check-transaction searches only BankCore’s retRef).
  2. HTTP 401: refresh the token and retry once.
  3. HTTP 403 or 429: nothing happened. Fix the cause (scope, address) or wait, then retry with the same reference.
  4. HTTP 200, code 0000: done. Store retRef.
  5. 0009: accepted, not final. Store retRef, poll check-transaction with backoff, and treat the money as moved until told otherwise.
  6. 0094: you already sent this. Look the reference up with check-transaction rather than assuming either outcome.
  7. 0004, 0012, 0014, 0062: refused before any posting. Safe to correct and resend, with a new reference if the amount or parties changed.
  8. 0096: read desc. If it describes a terminal failure of a known transaction (reversed, rejected), you are done and funds are restored. If it is a generic system error, check the transaction before retrying.

Never branch on desc text; it is written for people and may change. Branch on code, and on errorCode for native endpoints.

The Transactions page shows every transaction your client created with its reference, status and posting time, so an operator can confirm what the API told you.