Submitting transactions
Your core banking system submits a transaction to OneView and receives a decision in the same response.
Authenticate with an API service account scoped to Transaction Monitoring or All licensed modules. Create one under Administration → API Service Accounts. The API key is shown only at creation and rotation.
Before the first submission, an administrator must set the institution's base currency under Transaction Monitoring → Settings. OneView rejects ingestion until this one-time setting is complete. See Base currency settings.
POST /api/transactions
For complete, copy-pasteable requests for every account holder and counterparty type, and every possible response, see Transaction API examples.
The account holder is not "the sender"
Every transaction is recorded against an account holder: your institution's own customer (CUSTOMER) or general
ledger account (INTERNAL_GL) whose book the entry posts to. The account holder is the same party whichever way the
money moves:
direction | accountHolder | counterparty |
|---|---|---|
OUTBOUND | Your customer or GL, sending the money | The recipient |
INBOUND | Your customer or GL, receiving the money | The external sender |
So on an INBOUND transaction the account holder is the recipient, and the party who sent the money is the
counterparty. Use direction to tell sending from receiving; never read accountHolder as the sender.
Idempotency
A transaction is identified by its transactionReference together with its direction. Direction is part of
the key because an intra-bank transfer legitimately produces the same reference twice, once per leg.
- Resubmitting the same reference and direction with an identical body returns
200instead of201, withreplayed: true. The transaction is not evaluated again. The replay carries the storedoutcome,riskScoreandgradingVersion, buttriggeredRulesis empty,rulesEvaluatedis0andrulesetSnapshotisnull, so keep the original response if you need them. It is safe to retry with the identical body. Amount text counts:"150000.00"and150000are different bodies. - Resubmitting the same reference and direction with a different body is rejected with
409andIDEMPOTENCY_CONFLICT. OneView will not silently overwrite audit evidence.
You do not supply a separate idempotency key.
Complete request example
Send JSON with Content-Type: application/json. Send monetary values as decimal strings. JSON numbers are accepted
but converted to text, which loses trailing zeros and makes a retry differ from the original request.
{
"transactionReference": "TXN-NIP-10482",
"externalReference": "CBS-77104",
"direction": "OUTBOUND",
"accountHolderType": "CUSTOMER",
"paymentMethod": "NIP",
"channel": "MOBILE_APP",
"status": "POSTED",
"isInternal": false,
"isGlTransaction": false,
"isCrossBorder": false,
"amount": "150000.00",
"currency": "NGN",
"feeAmount": "50.00",
"taxAmount": "3.75",
"narration": "Consultancy retainer fee",
"executedAt": "2026-08-02T10:30:00Z",
"accountHolder": {
"customerUniqueId": "CUS-10482",
"accountNumber": "0123456789",
"accountName": "Sample Customer",
"riskLevel": "LOW"
},
"counterparty": {
"counterpartyType": "EXTERNAL_BANK",
"accountNumber": "9876543210",
"accountName": "Sample Recipient",
"institutionCode": "999999"
},
"telemetry": {
"ipAddress": "192.0.2.24",
"deviceId": "device-demo-42",
"latitude": 6.5244,
"longitude": 3.3792,
"country": "NG",
"appVersion": "4.8.0"
}
}
Top-level fields
| Field | Required | Type and accepted values | Purpose |
|---|---|---|---|
transactionReference | Yes | String, 1 to 64 characters. Letters, numbers, ., _, :, /, and -. | Your stable transaction identifier. It forms the idempotency key with direction. |
direction | Yes | INBOUND or OUTBOUND | Direction of value movement from the institution's perspective. |
accountHolderType | Yes | CUSTOMER or INTERNAL_GL | Whether the entry posts to a customer's account or to an internal general ledger account. |
paymentMethod | Yes | Fixed enum. See the table below. | Payment rail or method used to execute the transaction. |
channel | Yes | Fixed enum. See the table below. | Channel from which the transaction originated. |
status | Yes | PENDING, POSTED, or SETTLED | Your core banking lifecycle status. It is not OneView's risk decision. |
isCrossBorder | Yes | Boolean | Marks whether the movement of money crosses a national border. Currency alone does not determine this value. |
amount | Yes | Non-negative decimal, up to 14 whole digits and 4 fractional digits. | Principal transaction amount. |
currency | Yes | Three uppercase ISO 4217 letters. | Currency of amount, for example NGN, USD, or GBP. |
executedAt | Yes | ISO 8601 timestamp | Time the transaction was executed or posted. This determines its monitoring time window. |
accountHolder | Yes | Object | Snapshot of your customer or GL whose account the entry posts to, in either direction. See below. |
counterparty | Yes | Object | Snapshot of the receiving or opposing party. See below. |
collectionAccount | Conditional | Object | The collection account the payer paid into, held on the merchant's behalf. Required when paymentMethod is STATIC_ACCOUNT and direction is INBOUND, and rejected on any other combination. See below. |
externalReference | No | String using the reference characters above, up to 128 characters. | A related identifier from another system, rail, switch, or processor. |
isInternal | No | Boolean. Defaults to false. | Marks activity where both sides are within the institution. |
isGlTransaction | No | Boolean. Defaults to false. | Marks a transaction involving general ledger accounts. |
feeAmount | No | Non-negative decimal. Defaults to 0. | Total fee charged for the transaction. |
taxAmount | No | Non-negative decimal. Defaults to 0. | Tax included in or associated with the transaction fee. |
balanceBefore | No | Signed decimal | Account holder's balance before posting. Negative values are allowed for overdraft accounts. |
balanceAfter | No | Signed decimal | Account holder's balance after posting. Negative values are allowed for overdraft accounts. |
usdExchangeRate | Conditional | Positive decimal, up to 8 fractional digits. | Required for a non-USD transaction when isCrossBorder is true or its currency differs from the configured institution base currency. Express this as units of the transaction currency per 1 USD. OneView calculates usdAmount. |
narration | No | Text, up to 512 characters. | Human-readable transaction description supplied by the originating system. |
sourceOfFunds | No | Code, up to 64 characters. | Classified source of funds, when known. |
remittancePurposeCode | No | Code, up to 32 characters. | Regulatory or internal purpose code for the payment. |
initiatedAt | No | ISO 8601 timestamp | Time the customer or internal actor initiated the transaction. |
settledAt | No | ISO 8601 timestamp | Time the transaction settled. |
telemetry | No | Object | Device, network, location, and application context. |
ledgerPostings | No | Array of up to 64 objects | Debit and credit legs for GL-aware and multi-leg monitoring. |
receivedAt is not a request field. OneView generates it from the database clock when the transaction is first
stored, then returns it in the ingestion response and transaction read models. It records when OneView received
the transaction, while the required executedAt records when the source system executed or posted it. A replay
returns the original transaction's receivedAt value.
Fixed enum values
Values outside these lists are rejected.
| Field | Allowed values |
|---|---|
direction | INBOUND, OUTBOUND |
accountHolderType | CUSTOMER, INTERNAL_GL |
paymentMethod | NIP, NEFT, RTGS, SWIFT, INTERNAL_TRANSFER, CARD, CASH, CHEQUE, DIRECT_DEBIT, STANDING_ORDER, STATIC_ACCOUNT, WALLET, MOBILE_MONEY |
channel | MOBILE_APP, INTERNET_BANKING, USSD, ATM, POS, BRANCH, AGENT_BANKING, API, CORE_BANKING |
status | PENDING, POSTED, SETTLED |
accountHolder.entityType | INDIVIDUAL, CORPORATE, SOLE_PROPRIETORSHIP |
accountHolder.riskLevel | LOW, MEDIUM, HIGH |
accountHolder.status | ACTIVE, SUSPENDED, FROZEN, CLOSED |
counterparty.counterpartyType | EXTERNAL_BANK, INTERNAL_USER, VAS, INTERNAL_GL, CARD, WALLET |
ledgerPostings[].entryType | DEBIT, CREDIT |
sourceOfFunds and remittancePurposeCode do not have OneView-defined enums. Their accepted formats and length
limits are documented in the top-level field table.
Currency classification and USD conversion
OneView compares each transaction's currency with the institution base currency configured in Transaction
Monitoring settings. The server records isForeignCurrency: true when they differ. This is derived by OneView
and is not a request field.
isCrossBorder is a separate required fact supplied by the source system. A transaction can be cross-border in
the institution's base currency, or domestic in a foreign currency, so OneView does not infer one from the other.
For a transaction whose currency is USD, OneView uses a USD exchange rate of 1 and sets usdAmount equal
to amount. For another currency, provide usdExchangeRate whenever the transaction is cross-border or uses a
currency other than the configured base currency. OneView divides amount by the supplied rate and stores the
result as usdAmount.
For example, if a GBP transaction has amount: "100.00" and usdExchangeRate: "0.80", where 0.80 GBP
equals 1 USD, OneView records usdAmount: "125.0000".
Account holder object
The accountHolder object is always required. When accountHolderType is CUSTOMER, customerUniqueId is also
required. Use a stable internal customer identifier, not an email address or display name, so velocity and
historical rules resolve the same customer consistently.
For complete requests, see Customer account holder and Internal GL account holder.
| Field | Required | Description |
|---|---|---|
customerUniqueId | For CUSTOMER | Stable customer identifier in your core banking or customer master system. Must not be blank, up to 64 characters. |
accountNumber | No | The account holder's account number. |
accountName | No | Customer or account display name. |
entityType | No | Customer entity classification. Accepted values are listed in the fixed enum table. |
firstName, middleName, lastName | No | Individual customer names. |
companyName | No | Corporate or business name. |
identityType, identityNumber | No | identityType accepts only BVN. identityNumber must be an 11-digit BVN sent as a string. |
| Customer profile values | No | For a CUSTOMER account holder, profile values are length-checked, country and nationality must be two-letter codes, and dateOfBirth must be YYYY-MM-DD. See Customer account holder. |
kycLevel, riskLevel, isPep | No | Current customer due-diligence and risk context. riskLevel uses the fixed enum above. |
nationality, country, state, city, addressLine, postalCode | No | Location and address context. |
email, phoneNumber | No | Contact identifiers used for linkage and investigation. |
sourceOfWealth, occupationOrBusiness, status | No | Customer profile and account state context. status uses the fixed enum above. |
glCode, glName | No | The account holder's general ledger identifier and name. |
The object may contain up to 64 fields. Values must be strings of at most 512 characters, numbers, booleans,
null, or a single bounded nested object. Arrays and the NUL character (U+0000) are not accepted inside this object.
Counterparty object
The counterparty object and its counterpartyType are required. Every type except WALLET requires a non-blank
name field. WALLET requires at least one identifier instead. Supported types are:
counterpartyType | Required name field | Typical fields | Use for |
|---|---|---|---|
EXTERNAL_BANK | accountName | accountNumber, institutionCode, accountType, iban, country | A beneficiary or sender at another financial institution. |
INTERNAL_USER | accountName | customerUniqueId, accountNumber | Another customer within your institution. |
VAS | billerName | billerId, billerCategory | Airtime, utility, television, and other value-added services. |
INTERNAL_GL | accountName | glCode | An internal general ledger destination or source. A glCode without accountName is rejected. |
CARD | merchantName | cardFingerprint, cardBin, merchantId, merchantCategoryCode, terminalId, posEntryMode, emailFingerprint | Card, merchant, POS, and e-commerce activity. |
WALLET | None. An identifier is required instead. | accountNumber, email, emailFingerprint, counterpartyFingerprint, accountName, institutionCode, institutionName, country | An e-money or mobile-money payer, such as an OPay wallet or an MTN Mobile Money wallet. |
Use the card field names exactly as shown. Identify a card with cardFingerprint, your own stable token, and
cardBin. counterparty.country must be a two-letter ISO 3166-1 code.
The institution fields were renamed. counterparty.institutionCode and counterparty.institutionName are the
current names, because the field carries wallet and mobile money providers that are not banks as well as banks. The
former names, bankCode and bankName, are still accepted and are recorded under the current names, so an existing
integration keeps working and can move one field at a time. Sending a former name and its current name together with
the same value is accepted. Sending them with different values is rejected with
counterparty.bankCode is the former name of counterparty.institutionCode; they must not be supplied with different values, and bankName against institutionName the same way. institutionCode is trimmed and recorded in
uppercase, so surrounding spaces are dropped and mtn_momo_gh is recorded as MTN_MOMO_GH; institutionName is
recorded as you send it.
WALLET inverts two of the rules above. Its accountName is optional, because a wallet payer's name is not
always captured at the point of payment. In its place, a WALLET counterparty must carry at least one of
accountNumber, email, emailFingerprint or counterpartyFingerprint. Without one of those, the payer cannot be
tracked from one transaction to the next, so every rule that counts a payer's activity would quietly never match.
See Collection channels.
WALLET is both a counterparty type and a payment method, in the same way as CARD. The counterparty type says what
the other side is; the payment method says which rail the money moved on. paymentMethod never names the wallet or
mobile money provider: send the provider's institution code in counterparty.institutionCode, with
institutionName for the readable name, so a new provider needs a code rather than a new payment method. See
Collection channels.
The counterparty object has the same 64-field and value-size limits as accountHolder. Supply the identifiers your
institution needs for matching and investigation, but never send full card PAN or authentication secrets.
Collection account object
On a static account transfer, collectionAccount describes a third party alongside accountHolder and
counterparty: the account the payer paid into, which you hold on the merchant's behalf. A WALLET or
MOBILE_MONEY collection is paid through the provider instead, with no account in between, so it has two parties
and no collection account.
- It is not the merchant's own settlement account. That is
accountHolder.accountNumber. - It is not the payer's account. That is
counterparty.accountNumber.
Send it only when paymentMethod is STATIC_ACCOUNT and direction is INBOUND. It is required for that
combination, and any other combination that carries it is rejected.
| Field | Required | Description |
|---|---|---|
accountNumber | Yes | The collection account the money landed in. Up to 64 characters: letters, numbers, ., _, :, / and -, starting with a letter or number. Must not be blank. |
accountName | No | The name the collection account was issued in. Free text for analysts, not a stable identifier. |
accountType | No | Your own label for the kind of collection account, such as STATIC. |
The object follows the same bounded-object rules as accountHolder: up to 64 fields, string values of at most 512
characters, and extra keys stored as sent. For complete requests, see
Collection channels.
Once stored, the collection account is returned by the transaction detail read alongside the account holder and
counterparty snapshots, and is shown to analysts on the transaction detail page. It is null for every transaction
that never carried one.
Telemetry object
Telemetry is optional, but it improves device, network, and geographic rule coverage. See Telemetry for a complete example.
| Field | Required | Description |
|---|---|---|
ipAddress | No | Source IP address. |
deviceId or deviceFingerprintHash | No | Stable device identifier or privacy-preserving device fingerprint. |
latitude, longitude | No | Source coordinates when collected with the appropriate legal basis. |
country | No | Uppercase ISO 3166-1 alpha-2 or alpha-3 country code, such as NG or NGA. |
isVpnOrProxy | No | Whether the source system detected VPN or proxy routing. |
appVersion | No | Client application version. |
userAgent | No | Browser or application user agent. |
sessionId | No | Source-channel session identifier, such as a USSD or web session. |
Telemetry follows the same bounded-object rules as accountHolder. Do not include passwords, PINs, OTPs, CVVs,
full card PANs, private keys, or API credentials.
Ledger posting objects
Use ledgerPostings for journals, VAS fee splits, tax legs, and other multi-leg accounting flows. Unknown
fields in a ledger posting are rejected.
| Field | Required | Description |
|---|---|---|
glCode | Yes | General ledger code. |
entryType | Yes | DEBIT or CREDIT. |
amount | Yes | Non-negative decimal with up to 4 fractional digits. |
currency | Yes | Three uppercase ISO 4217 letters. |
postingId | No | Stable identifier for the posting leg. |
glName | No | Human-readable ledger name. |
accountNumber | No | Related customer or settlement account. |
costCenter | No | Branch, department, or operational cost-center code, up to 64 characters. |
OneView preserves the submitted array order as the leg index. Submit debits and credits as positive amounts;
use entryType to express the accounting direction.
Fields OneView will not accept
transactionId, riskScore, riskStatus, isForeignCurrency, usdAmount, baseCurrency,
baseExchangeRate, baseCurrencyAmount, baseAmount and any decision field are rejected. Identity, derived
currency facts, and the decision are OneView outputs or installation settings; accepting them from a caller
would make the stored record internally inconsistent.
status remains yours. It is your core banking lifecycle value. OneView's decision is a separate field.
Unknown fields are rejected rather than ignored, so a typo surfaces immediately instead of being silently
dropped. The earlier field names initiator and initiatorType are rejected with a message naming their
replacements, accountHolder and accountHolderType.
A successful response
{
"status": true,
"message": "Transaction evaluated",
"data": {
"transaction": {
"id": "0f4c1e2a-6b3d-4e8f-9a1c-2d5e7f809b1c",
"transactionReference": "TXN-20260729-009218",
"direction": "OUTBOUND",
"receivedAt": "2026-07-29T11:45:02.140Z",
"replayed": false
},
"decision": {
"outcome": "FLAG",
"riskScore": 35,
"evaluatedAt": "2026-07-29T11:45:02.152Z",
"evaluationMs": 11.8,
"complete": true,
"rulesEvaluated": 42,
"rulesetSnapshot": "a3f81c0d9e7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a19",
"gradingVersion": 3,
"triggeredRules": [
{
"ruleId": "7c9e2b41-3f6a-4d8e-b5c0-9a1d2e3f4b5c",
"ruleVersion": 4,
"name": "Large outbound transfer",
"action": "FLAG"
}
]
}
}
}
name is the name of the rule version that ran. triggeredRules lists matched live rules only, up to 20. Blacklist,
PEP and sanction screening, and geography risk hits can raise the outcome and score without adding an entry, so a
BLOCK with an empty triggeredRules is valid. See Decision
responses.
riskScore is assigned from the institution's configured Risk score grading. It is not
submitted by the caller and it is not the sum of per-rule points.
gradingVersion identifies the exact risk score grading policy that assigned the score. Store it with the
decision if your integration retains its own audit record.
Always check complete
complete: false means the decision is partial because some rules could not run. A skippedRules array names
which, with a reason code. The decision is still returned, and it is your policy whether to trust a partial
PASS.
This is not an error case and does not change the HTTP status. A rule that fails is neither treated as firing nor as not firing, because either would be misleading: one would block traffic on a bug, the other would weaken a control silently.
rulesetSnapshot identifies the exact set of rules and thresholds that ran. Together with gradingVersion, it
correlates the decision to the complete policy in force at the time.
Errors
| HTTP | code | What to do |
|---|---|---|
| 400 | VALIDATION_FAILED | Fix the payload. Do not retry. An errors array names the fields. |
| 400 | INVALID_JSON | The body is not well-formed JSON. Fix it. Do not retry unchanged. |
| 400 | INVALID_TENANT_HEADER | Remove the X-Tenant-Id header. Do not retry. |
| 401 | AUTH_REQUIRED, INVALID_API_CREDENTIAL | Fix credentials. Do not retry. |
| 403 | SERVICE_ACCOUNT_INTEGRATION_ONLY, SERVICE_ACCOUNT_SCOPE_FORBIDDEN, SERVICE_ACCOUNT_TENANT_MISMATCH | Fix the path, the service account scope, or the tenant header. Do not retry. |
| 403 | LICENSE_MISSING, LICENSE_INVALID_OR_EXPIRED, LICENSE_MODULE_NOT_INCLUDED | Apply your fallback and alert operations. |
| 409 | TRANSACTION_MONITORING_NOT_CONFIGURED | Ask an authorized administrator to set the institution base currency. Do not retry until configuration is complete. |
| 409 | IDEMPOTENCY_CONFLICT | Do not retry. The same reference and direction were sent with a different body. |
| 413 | Not provided | Reduce the body below 1 MB. Do not retry. |
| 503 | TRANSACTIONS_UNAVAILABLE, AUTH_UNAVAILABLE | Apply your fallback and retry with the identical body. |
| 500 | Not provided | Apply your fallback and retry with the identical body. |
See Errors for every message and validation code.
Reading a decision later
GET /api/transactions?transactionId=<id>
Use the list endpoint, filtered by the transaction.id returned at submission. This is how you resolve a held
transaction: poll it and read releaseDecision (RELEASED or REJECTED) once an analyst has recorded a review.
The item also carries the current decisionOutcome, which late screening can raise after the synchronous response.
GET /api/transactions/{id} is not available to API service accounts. Rule evidence is visible to analysts in
OneView and is not returned by the API. See Following up a held transaction in
Decision responses.