Submitting transactions
Quick start

OneView documentation

Submitting transactions

The ingest API, idempotency, decision responses, and error handling.

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:

directionaccountHoldercounterparty
OUTBOUNDYour customer or GL, sending the moneyThe recipient
INBOUNDYour customer or GL, receiving the moneyThe 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 200 instead of 201, with replayed: true. The transaction is not evaluated again. The replay carries the stored outcome, riskScore and gradingVersion, but triggeredRules is empty, rulesEvaluated is 0 and rulesetSnapshot is null, so keep the original response if you need them. It is safe to retry with the identical body. Amount text counts: "150000.00" and 150000 are different bodies.
  • Resubmitting the same reference and direction with a different body is rejected with 409 and IDEMPOTENCY_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

FieldRequiredType and accepted valuesPurpose
transactionReferenceYesString, 1 to 64 characters. Letters, numbers, ., _, :, /, and -.Your stable transaction identifier. It forms the idempotency key with direction.
directionYesINBOUND or OUTBOUNDDirection of value movement from the institution's perspective.
accountHolderTypeYesCUSTOMER or INTERNAL_GLWhether the entry posts to a customer's account or to an internal general ledger account.
paymentMethodYesFixed enum. See the table below.Payment rail or method used to execute the transaction.
channelYesFixed enum. See the table below.Channel from which the transaction originated.
statusYesPENDING, POSTED, or SETTLEDYour core banking lifecycle status. It is not OneView's risk decision.
isCrossBorderYesBooleanMarks whether the movement of money crosses a national border. Currency alone does not determine this value.
amountYesNon-negative decimal, up to 14 whole digits and 4 fractional digits.Principal transaction amount.
currencyYesThree uppercase ISO 4217 letters.Currency of amount, for example NGN, USD, or GBP.
executedAtYesISO 8601 timestampTime the transaction was executed or posted. This determines its monitoring time window.
accountHolderYesObjectSnapshot of your customer or GL whose account the entry posts to, in either direction. See below.
counterpartyYesObjectSnapshot of the receiving or opposing party. See below.
collectionAccountConditionalObjectThe 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.
externalReferenceNoString using the reference characters above, up to 128 characters.A related identifier from another system, rail, switch, or processor.
isInternalNoBoolean. Defaults to false.Marks activity where both sides are within the institution.
isGlTransactionNoBoolean. Defaults to false.Marks a transaction involving general ledger accounts.
feeAmountNoNon-negative decimal. Defaults to 0.Total fee charged for the transaction.
taxAmountNoNon-negative decimal. Defaults to 0.Tax included in or associated with the transaction fee.
balanceBeforeNoSigned decimalAccount holder's balance before posting. Negative values are allowed for overdraft accounts.
balanceAfterNoSigned decimalAccount holder's balance after posting. Negative values are allowed for overdraft accounts.
usdExchangeRateConditionalPositive 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.
narrationNoText, up to 512 characters.Human-readable transaction description supplied by the originating system.
sourceOfFundsNoCode, up to 64 characters.Classified source of funds, when known.
remittancePurposeCodeNoCode, up to 32 characters.Regulatory or internal purpose code for the payment.
initiatedAtNoISO 8601 timestampTime the customer or internal actor initiated the transaction.
settledAtNoISO 8601 timestampTime the transaction settled.
telemetryNoObjectDevice, network, location, and application context.
ledgerPostingsNoArray of up to 64 objectsDebit 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.

FieldAllowed values
directionINBOUND, OUTBOUND
accountHolderTypeCUSTOMER, INTERNAL_GL
paymentMethodNIP, NEFT, RTGS, SWIFT, INTERNAL_TRANSFER, CARD, CASH, CHEQUE, DIRECT_DEBIT, STANDING_ORDER, STATIC_ACCOUNT, WALLET, MOBILE_MONEY
channelMOBILE_APP, INTERNET_BANKING, USSD, ATM, POS, BRANCH, AGENT_BANKING, API, CORE_BANKING
statusPENDING, POSTED, SETTLED
accountHolder.entityTypeINDIVIDUAL, CORPORATE, SOLE_PROPRIETORSHIP
accountHolder.riskLevelLOW, MEDIUM, HIGH
accountHolder.statusACTIVE, SUSPENDED, FROZEN, CLOSED
counterparty.counterpartyTypeEXTERNAL_BANK, INTERNAL_USER, VAS, INTERNAL_GL, CARD, WALLET
ledgerPostings[].entryTypeDEBIT, 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.

FieldRequiredDescription
customerUniqueIdFor CUSTOMERStable customer identifier in your core banking or customer master system. Must not be blank, up to 64 characters.
accountNumberNoThe account holder's account number.
accountNameNoCustomer or account display name.
entityTypeNoCustomer entity classification. Accepted values are listed in the fixed enum table.
firstName, middleName, lastNameNoIndividual customer names.
companyNameNoCorporate or business name.
identityType, identityNumberNoidentityType accepts only BVN. identityNumber must be an 11-digit BVN sent as a string.
Customer profile valuesNoFor 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, isPepNoCurrent customer due-diligence and risk context. riskLevel uses the fixed enum above.
nationality, country, state, city, addressLine, postalCodeNoLocation and address context.
email, phoneNumberNoContact identifiers used for linkage and investigation.
sourceOfWealth, occupationOrBusiness, statusNoCustomer profile and account state context. status uses the fixed enum above.
glCode, glNameNoThe 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:

counterpartyTypeRequired name fieldTypical fieldsUse for
EXTERNAL_BANKaccountNameaccountNumber, institutionCode, accountType, iban, countryA beneficiary or sender at another financial institution.
INTERNAL_USERaccountNamecustomerUniqueId, accountNumberAnother customer within your institution.
VASbillerNamebillerId, billerCategoryAirtime, utility, television, and other value-added services.
INTERNAL_GLaccountNameglCodeAn internal general ledger destination or source. A glCode without accountName is rejected.
CARDmerchantNamecardFingerprint, cardBin, merchantId, merchantCategoryCode, terminalId, posEntryMode, emailFingerprintCard, merchant, POS, and e-commerce activity.
WALLETNone. An identifier is required instead.accountNumber, email, emailFingerprint, counterpartyFingerprint, accountName, institutionCode, institutionName, countryAn 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.

FieldRequiredDescription
accountNumberYesThe collection account the money landed in. Up to 64 characters: letters, numbers, ., _, :, / and -, starting with a letter or number. Must not be blank.
accountNameNoThe name the collection account was issued in. Free text for analysts, not a stable identifier.
accountTypeNoYour 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.

FieldRequiredDescription
ipAddressNoSource IP address.
deviceId or deviceFingerprintHashNoStable device identifier or privacy-preserving device fingerprint.
latitude, longitudeNoSource coordinates when collected with the appropriate legal basis.
countryNoUppercase ISO 3166-1 alpha-2 or alpha-3 country code, such as NG or NGA.
isVpnOrProxyNoWhether the source system detected VPN or proxy routing.
appVersionNoClient application version.
userAgentNoBrowser or application user agent.
sessionIdNoSource-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.

FieldRequiredDescription
glCodeYesGeneral ledger code.
entryTypeYesDEBIT or CREDIT.
amountYesNon-negative decimal with up to 4 fractional digits.
currencyYesThree uppercase ISO 4217 letters.
postingIdNoStable identifier for the posting leg.
glNameNoHuman-readable ledger name.
accountNumberNoRelated customer or settlement account.
costCenterNoBranch, 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

HTTPcodeWhat to do
400VALIDATION_FAILEDFix the payload. Do not retry. An errors array names the fields.
400INVALID_JSONThe body is not well-formed JSON. Fix it. Do not retry unchanged.
400INVALID_TENANT_HEADERRemove the X-Tenant-Id header. Do not retry.
401AUTH_REQUIRED, INVALID_API_CREDENTIALFix credentials. Do not retry.
403SERVICE_ACCOUNT_INTEGRATION_ONLY, SERVICE_ACCOUNT_SCOPE_FORBIDDEN, SERVICE_ACCOUNT_TENANT_MISMATCHFix the path, the service account scope, or the tenant header. Do not retry.
403LICENSE_MISSING, LICENSE_INVALID_OR_EXPIRED, LICENSE_MODULE_NOT_INCLUDEDApply your fallback and alert operations.
409TRANSACTION_MONITORING_NOT_CONFIGUREDAsk an authorized administrator to set the institution base currency. Do not retry until configuration is complete.
409IDEMPOTENCY_CONFLICTDo not retry. The same reference and direction were sent with a different body.
413Not providedReduce the body below 1 MB. Do not retry.
503TRANSACTIONS_UNAVAILABLE, AUTH_UNAVAILABLEApply your fallback and retry with the identical body.
500Not providedApply 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.