Customer account holder
Use accountHolderType: "CUSTOMER" when the entry posts to one of your customers' accounts. The accountHolder
object describes that customer: the customer whose account the entry posts to, whichever way the money moves. On an
OUTBOUND transaction the account holder is the sender. On an INBOUND transaction the account holder is the
recipient, and the sender is the counterparty.
The only required account holder field is customerUniqueId, a non-blank string of up to 64 characters. Use the stable
identifier from your core banking or customer master system, never an email address, phone number or display name.
Velocity and history rules group activity by this value, so it must stay the same across every transaction for the same
customer.
Minimal customer example
POST /api/transactions
Authorization: Bearer <oneview-service-account-api-key>
Content-Type: application/json
{
"transactionReference": "TXN-20260921-000101",
"direction": "OUTBOUND",
"accountHolderType": "CUSTOMER",
"paymentMethod": "NIP",
"channel": "MOBILE_APP",
"status": "POSTED",
"isCrossBorder": false,
"amount": "15000.00",
"currency": "NGN",
"executedAt": "2026-09-21T09:20:00Z",
"accountHolder": {
"customerUniqueId": "CUS-100231"
},
"counterparty": {
"counterpartyType": "EXTERNAL_BANK",
"accountName": "Demo Recipient Limited",
"accountNumber": "9000000001",
"institutionCode": "999999"
}
}
This passes validation, but most customer rules read more than the ID. The examples below show the fields worth sending.
A customer profile is created the first time you send a customer
The first time OneView receives a customerUniqueId it has not seen, it creates a customer profile from the
accountHolder fields listed below. Later transactions for the same customerUniqueId do not overwrite that profile.
Send the full profile on the customer's first transaction, and keep sending the fields your rules read on every
transaction, because rules evaluate the accountHolder object exactly as it arrives with each transaction.
Profile fields: firstName, middleName, lastName, companyName, dateOfBirth (YYYY-MM-DD), gender,
nationality, identityType, identityNumber, kycLevel, riskLevel, isPep, occupationOrBusiness,
sourceOfWealth, email, phoneNumber, addressLine, city, state, country, postalCode and status. Send
only what you need. When entityType is omitted, the profile is recorded as an individual.
OneView checks these profile values on every customer transaction, not only the first, and rejects a request whose value would not fit the profile:
| Field | Rule |
|---|---|
customerUniqueId, firstName, middleName, lastName, sourceOfWealth, city, state | Up to 64 characters |
companyName, occupationOrBusiness | Up to 128 characters |
email | Up to 254 characters |
phoneNumber | Up to 32 characters |
kycLevel | Up to 24 characters |
postalCode | Up to 20 characters |
gender | Up to 16 characters |
country, nationality | A two-letter ISO 3166-1 code in any case, such as NG or ng. Stored in uppercase. |
dateOfBirth | A real calendar date in YYYY-MM-DD format |
An over-long value is rejected with a message such as accountHolder.firstName must be at most 64 characters. An
invalid country returns accountHolder.country must be an ISO 3166-1 alpha-2 country code, and an invalid date returns
accountHolder.dateOfBirth must be a calendar date in YYYY-MM-DD format. An empty string or a non-string value is not
checked and is not copied to the profile.
Full individual customer
POST /api/transactions
Authorization: Bearer <oneview-service-account-api-key>
Content-Type: application/json
{
"transactionReference": "TXN-20260921-000102",
"externalReference": "CBS-20260921-88410",
"direction": "OUTBOUND",
"accountHolderType": "CUSTOMER",
"paymentMethod": "NIP",
"channel": "MOBILE_APP",
"status": "POSTED",
"isCrossBorder": false,
"amount": "185000.00",
"currency": "NGN",
"feeAmount": "26.88",
"taxAmount": "2.02",
"balanceBefore": "412500.50",
"balanceAfter": "227471.60",
"narration": "Rent contribution September",
"sourceOfFunds": "SALARY",
"initiatedAt": "2026-09-21T09:24:51Z",
"executedAt": "2026-09-21T09:25:03Z",
"accountHolder": {
"customerUniqueId": "CUS-100231",
"entityType": "INDIVIDUAL",
"accountNumber": "0012345678",
"accountName": "Ada Demo Okafor",
"accountType": "SAVINGS",
"accountOpeningDate": "2019-04-02",
"firstName": "Ada",
"middleName": "Demo",
"lastName": "Okafor",
"dateOfBirth": "1990-06-15",
"gender": "FEMALE",
"nationality": "NG",
"identityType": "BVN",
"identityNumber": "12345678901",
"kycLevel": "TIER_3",
"riskLevel": "LOW",
"isPep": false,
"occupationOrBusiness": "Software engineer",
"sourceOfWealth": "Employment income",
"email": "ada.okafor@example.com",
"phoneNumber": "+2348000000001",
"addressLine": "1 Example Street",
"city": "Ikeja",
"state": "Lagos",
"country": "NG",
"postalCode": "100001",
"status": "ACTIVE"
},
"counterparty": {
"counterpartyType": "EXTERNAL_BANK",
"accountName": "Demo Properties Limited",
"accountNumber": "9000000002",
"institutionCode": "999999",
"accountType": "CURRENT",
"country": "NG"
}
}
| Field | What it means | Why it matters for monitoring |
|---|---|---|
entityType | INDIVIDUAL, CORPORATE or SOLE_PROPRIETORSHIP. | Rules often apply different thresholds to people and businesses. |
accountNumber, accountType | The debited account and its product type. | Velocity rules can group by account. The account number is also shown to analysts and can be searched. |
accountOpeningDate | When the account was opened. | New-account rules, such as large transfers in an account's first 30 days. |
firstName, middleName, lastName | The customer's legal name. | Used as the name for PEP and sanction screening when that control is enabled. For customers without a personal name, screening uses companyName, then accountName. |
identityType, identityNumber | Identity document type and number. | BVN is the only supported identity type. See the BVN rules below. |
kycLevel | Your KYC tier, in your own values. | Tier-based limits. Use the same values consistently, because rules compare the text exactly. |
riskLevel | LOW, MEDIUM or HIGH. | Rules can tighten thresholds for higher-risk customers. OneView also uses the value you send as a source for the customer's risk classification. |
isPep | Boolean. | PEP-specific rules and enhanced review. |
nationality, country | Two-letter ISO 3166-1 country codes, such as NG. Three-letter codes are rejected. | Country rules. The account holder country can match a country on your blacklist. |
status | ACTIVE, SUSPENDED, FROZEN or CLOSED. | Activity on a frozen or closed account is a common rule. |
balanceBefore, balanceAfter | Account balance around the posting. Negative values are allowed. | Rules that look for accounts being emptied. |
sourceOfFunds | Your code for where the money came from. | Mismatch rules, such as a large transfer funded by an unexpected source. |
initiatedAt | When the customer started the transaction. | Shows delays between initiation and execution. |
BVN rules
identityTypeaccepts onlyBVN. Any other value is rejected.identityNumbermust be an 11-digit string, for example"12345678901". A JSON number, a shorter value or a value with spaces is rejected withaccountHolder.identityNumber must be an 11-digit BVN.identityNumberis stored with the transaction and is visible to analysts. Send it only if your institution needs it in OneView.
Corporate customer
POST /api/transactions
Authorization: Bearer <oneview-service-account-api-key>
Content-Type: application/json
{
"transactionReference": "TXN-20260921-000103",
"direction": "OUTBOUND",
"accountHolderType": "CUSTOMER",
"paymentMethod": "RTGS",
"channel": "INTERNET_BANKING",
"status": "POSTED",
"isCrossBorder": false,
"amount": "48500000.00",
"currency": "NGN",
"narration": "Supplier settlement invoice INV-2026-0911",
"sourceOfFunds": "BUSINESS_REVENUE",
"executedAt": "2026-09-21T11:02:40Z",
"accountHolder": {
"customerUniqueId": "CUS-500871",
"entityType": "CORPORATE",
"accountNumber": "0012345679",
"accountName": "Demo Logistics Limited",
"accountType": "CURRENT",
"accountOpeningDate": "2015-01-19",
"companyName": "Demo Logistics Limited",
"occupationOrBusiness": "Freight and haulage",
"kycLevel": "CORPORATE_FULL",
"riskLevel": "MEDIUM",
"isPep": false,
"email": "treasury@example.com",
"phoneNumber": "+2348000000002",
"addressLine": "10 Demo Industrial Avenue",
"city": "Apapa",
"state": "Lagos",
"country": "NG",
"status": "ACTIVE"
},
"counterparty": {
"counterpartyType": "EXTERNAL_BANK",
"accountName": "Demo Fuel Supplies Limited",
"accountNumber": "9000000003",
"institutionCode": "999999",
"accountType": "CURRENT",
"country": "NG"
}
}
A corporate customer has no personal name, so send companyName. Screening uses it as the customer's name, and
analysts see it on the transaction. Do not send firstName or lastName for the company itself, because a personal
name takes priority over companyName for screening. BVN belongs to individuals, so the corporate example sends no
identityType or identityNumber.
Sole proprietorship
POST /api/transactions
Authorization: Bearer <oneview-service-account-api-key>
Content-Type: application/json
{
"transactionReference": "TXN-20260921-000104",
"direction": "INBOUND",
"accountHolderType": "CUSTOMER",
"paymentMethod": "NIP",
"channel": "CORE_BANKING",
"status": "POSTED",
"isCrossBorder": false,
"amount": "320000.00",
"currency": "NGN",
"narration": "Catering order payment",
"executedAt": "2026-09-21T13:47:12Z",
"accountHolder": {
"customerUniqueId": "CUS-300412",
"entityType": "SOLE_PROPRIETORSHIP",
"accountNumber": "0012345680",
"accountName": "Demo Kitchen Enterprises",
"companyName": "Demo Kitchen Enterprises",
"firstName": "Bola",
"lastName": "Adeyemi",
"identityType": "BVN",
"identityNumber": "10987654321",
"occupationOrBusiness": "Catering services",
"kycLevel": "TIER_2",
"riskLevel": "LOW",
"isPep": false,
"country": "NG",
"status": "ACTIVE"
},
"counterparty": {
"counterpartyType": "EXTERNAL_BANK",
"accountName": "Demo Events Limited",
"accountNumber": "9000000004",
"institutionCode": "999999",
"country": "NG"
}
}
A sole proprietorship is a business owned by one person. Send the trading name in companyName and the owner's name
in firstName and lastName. The owner's BVN can be sent because it identifies an individual. Screening uses the
owner's personal name, because it takes priority over companyName.
INBOUND and OUTBOUND for a customer
direction describes the movement from the account holder's side. The account holder is always your customer,
whose account the entry posts to, and the counterparty is always the other party.
| Situation | direction | accountHolder | counterparty |
|---|---|---|---|
| Your customer sends money to someone | OUTBOUND | Your customer (the sender) | The recipient |
| Your customer receives money from someone | INBOUND | Your customer (the recipient) | The sender |
The sole proprietorship example above is INBOUND: the customer received a payment, and the counterparty describes
the external sender. Send inbound credits as well as outbound debits, because rules such as rapid movement of funds
compare what arrives with what leaves.
For a transfer between two of your own customers, see Internal user counterparty, which sends one transaction for each side.
Response
A successful submission always returns the same envelope, whichever account holder or counterparty type you sent. This is the response to the full individual customer example above:
{
"status": true,
"message": "Transaction evaluated",
"data": {
"transaction": {
"id": "8f2a6c14-9d3b-4e71-b825-1c6f0a4d2e91",
"transactionReference": "TXN-20260921-000102",
"direction": "OUTBOUND",
"receivedAt": "2026-09-21T09:25:03.412Z",
"replayed": false
},
"decision": {
"outcome": "PASS",
"riskScore": 0,
"evaluatedAt": "2026-09-21T09:25:03.431Z",
"evaluationMs": 11.482,
"complete": true,
"rulesEvaluated": 18,
"rulesetSnapshot": "9ffa3b395a9799b1a1104ab1b536ed1667b487510829ceefc27593941fafb35e",
"gradingVersion": 3,
"triggeredRules": []
}
}
}
Status is 201 for a new transaction, 200 when the same transactionReference and direction are resubmitted
with an identical body. The full response shape, every outcome, and the risk score bands are documented once in
Decision responses rather than repeated per account holder type.
Errors specific to a customer account holder
| Cause | field | code | message |
|---|---|---|---|
accountHolderType is CUSTOMER and customerUniqueId is missing or blank | accountHolder | required | accountHolder.customerUniqueId is required when accountHolderType is CUSTOMER |
A profile value, such as firstName, is over its length limit | accountHolder.firstName | invalid | accountHolder.firstName must be at most 64 characters |
country or nationality is not an assigned two-letter code | accountHolder.nationality | invalid | accountHolder.nationality must be an ISO 3166-1 alpha-2 country code |
dateOfBirth is not a real calendar date in YYYY-MM-DD | accountHolder.dateOfBirth | invalid | accountHolder.dateOfBirth must be a calendar date in YYYY-MM-DD format |
identityType is anything other than BVN | accountHolder.identityType | invalid | accountHolder.identityType must be one of: BVN |
identityNumber is not an 11-digit string | accountHolder.identityNumber | invalid | accountHolder.identityNumber must be an 11-digit BVN |
Every request-level error uses the same envelope and the same status codes, whatever account holder type triggered
it. See Errors for the complete catalogue, including INVALID_JSON, licence errors, and retry guidance.
Idempotency
A customer transaction is deduplicated the same way as any other: by transactionReference and direction
together. Resubmitting the identical body returns 200 with replayed: true and does not re-evaluate the
transaction or re-check the customer profile. Resubmitting a different body under the same reference and direction
is rejected with 409 IDEMPOTENCY_CONFLICT. See Idempotency in Submitting transactions
and Replays in Decision responses.
How PEP and sanction screening picks the name
When PEP and sanction screening is licensed and enabled, it resolves one name per party in this order, stopping at
the first match: firstName/lastName (joined with middleName if present), then companyName, then
accountName. A party with none of these fields is simply not screened by name — this is not an error, and rules
and blacklist matching still apply to whatever fields you did send. This order is what makes a personal name take
priority over companyName for the sole proprietorship example above, and it applies identically to the
counterparty object. See Card counterparty and VAS counterparty for two
counterparty types this order does not cover by default.