Internal GL account holder
Quick start

OneView documentation

Internal GL account holder

Transactions posted to one of your general ledger accounts, with balanced ledger postings.

Internal GL account holder

Use accountHolderType: "INTERNAL_GL" when the entry posts to one of your institution's own general ledger (GL) accounts rather than a customer account. Typical examples are suspense clearing, fee income sweeps, vendor payments from an expense GL, and manual journals posted by staff.

No account holder field is required for this type, so "accountHolder": {} is valid. Always send glCode anyway: without it, GL rules have nothing to match and analysts cannot see which ledger moved the money.

Such a transaction usually has isGlTransaction: true, paymentMethod: "INTERNAL_TRANSFER", channel: "CORE_BANKING", and a ledgerPostings array describing the journal.

GL-to-GL journal with balanced postings

A staff member clears a suspense GL into a fee income GL.

POST /api/transactions
Authorization: Bearer <oneview-service-account-api-key>
Content-Type: application/json

{
  "transactionReference": "JRN-20260921-004471",
  "externalReference": "CBS-JRN-77120",
  "direction": "OUTBOUND",
  "accountHolderType": "INTERNAL_GL",
  "paymentMethod": "INTERNAL_TRANSFER",
  "channel": "CORE_BANKING",
  "status": "POSTED",
  "isInternal": true,
  "isGlTransaction": true,
  "isCrossBorder": false,
  "amount": "750000.00",
  "currency": "NGN",
  "narration": "Clear unapplied fee suspense to fee income",
  "executedAt": "2026-09-21T16:30:00Z",
  "accountHolder": {
    "glCode": "19050",
    "glName": "Unapplied fees suspense",
    "glCategory": "SUSPENSE",
    "branchCode": "LAG-001",
    "postingActor": {
      "actorType": "STAFF",
      "staffId": "STF-0192",
      "authorizationLevel": "L2"
    }
  },
  "counterparty": {
    "counterpartyType": "INTERNAL_GL",
    "accountName": "Fee income",
    "glCode": "40210"
  },
  "ledgerPostings": [
    {
      "postingId": "JRN-20260921-004471-1",
      "glCode": "19050",
      "glName": "Unapplied fees suspense",
      "entryType": "DEBIT",
      "amount": "750000.00",
      "currency": "NGN",
      "costCenter": "FIN-OPS"
    },
    {
      "postingId": "JRN-20260921-004471-2",
      "glCode": "40210",
      "glName": "Fee income",
      "entryType": "CREDIT",
      "amount": "750000.00",
      "currency": "NGN",
      "costCenter": "FIN-OPS"
    }
  ]
}

Account holder fields for a GL

FieldWhat it meansWhy it matters for monitoring
glCodeThe ledger code the money leaves.GL rules match on it, and it can match a GL code on your blacklist.
glNameThe ledger's display name.Helps analysts read the transaction.
glCategoryYour classification, such as SUSPENSE, INCOME or EXPENSE.Rules such as "any outflow from a suspense GL above a threshold". Use consistent values.
branchCodeThe branch that owns or posted the entry.Branch-level rules and investigation.
postingActorA nested object describing who posted the entry: actorType, staffId and authorizationLevel.Rules can detect staff posting above their authority or unusual manual journals. Send the staff ID your institution uses internally, not a name.
isGlTransactionMarks a transaction that touches a GL.Rules can apply only to GL activity. Defaults to false, so set it explicitly.
isInternalBoth sides are within your institution.Separates internal movements from money leaving the bank.

glCategory and postingActor.authorizationLevel are not OneView-defined enums. SUSPENSE, INCOME and EXPENSE above, and L2/L3, are this example's own values, not a fixed or validated list — OneView accepts and stores whatever text you send in either field, up to the object's normal 512-character value limit. Use your institution's own classification and authority-level scheme consistently, because rules that read these fields compare the text exactly.

Ledger posting legs

Each element of ledgerPostings is one debit or credit leg. Only the fields below are accepted. Any other key, such as narration, rejects the request.

FieldRequiredFormatMeaning
entryTypeYesDEBIT or CREDITThe accounting side of the leg.
glCodeYesUp to 32 letters, digits, ., _ or -, starting with a letter or digitThe ledger the leg posts to. Every leg's GL code can match your blacklist.
amountYesNon-negative decimal string, up to 4 decimal placesAlways positive. entryType gives the direction.
currencyYesThree uppercase lettersThe leg's currency.
postingIdNoReference format, up to 64 charactersYour ID for the leg.
glNameNoText, up to 128 charactersThe ledger's display name.
accountNumberNoReference format, up to 64 charactersA customer or settlement account tied to the leg.
costCenterNoCode format, up to 64 charactersBranch, department or cost centre.

Rules for the array:

  • It can hold up to 64 legs. OneView keeps them in the order you send them.
  • Validation stops at the first invalid leg, and the error names it by position, for example ledgerPostings.1.
  • Legs are optional. Leave ledgerPostings out when a transaction has no journal detail to add.

OneView does not enforce that a journal balances. A ledgerPostings array whose total debits and credits differ is accepted and stored exactly as sent — there is no server-side check. Only a rule your administrator configures to compare the legs can catch an unbalanced journal, and only if one is configured. Always send complete journals where total debits equal total credits; do not rely on OneView to reject an incomplete one.

The shape of a ledger validation error

A leg is validated in isolation, so an invalid leg produces one error naming its position, not the whole array:

{
  "message": "Transaction payload validation failed",
  "status": false,
  "statusCode": 400,
  "code": "VALIDATION_FAILED",
  "errors": [
    {
      "field": "ledgerPostings.1",
      "code": "required",
      "message": "ledgerPostings.1.glCode is required"
    }
  ]
}

Only the first invalid leg is reported. Fix it and resend the whole request; OneView does not accept a partial correction to just one leg. See Errors specific to a GL transaction below.

GL paying an external vendor

The institution pays a supplier from an expense GL through NIP. The counterparty is the supplier's bank account.

POST /api/transactions
Authorization: Bearer <oneview-service-account-api-key>
Content-Type: application/json

{
  "transactionReference": "PAY-20260921-000318",
  "direction": "OUTBOUND",
  "accountHolderType": "INTERNAL_GL",
  "paymentMethod": "NIP",
  "channel": "CORE_BANKING",
  "status": "POSTED",
  "isInternal": false,
  "isGlTransaction": true,
  "isCrossBorder": false,
  "amount": "2450000.00",
  "currency": "NGN",
  "feeAmount": "53.75",
  "taxAmount": "4.03",
  "narration": "Facility maintenance invoice FM-2026-044",
  "executedAt": "2026-09-21T14:05:00Z",
  "accountHolder": {
    "glCode": "50110",
    "glName": "Facility maintenance expense",
    "glCategory": "EXPENSE",
    "branchCode": "HQ-001",
    "postingActor": {
      "actorType": "STAFF",
      "staffId": "STF-0457",
      "authorizationLevel": "L3"
    }
  },
  "counterparty": {
    "counterpartyType": "EXTERNAL_BANK",
    "accountName": "Demo Facility Services Limited",
    "accountNumber": "9000000005",
    "institutionCode": "999999",
    "country": "NG"
  },
  "ledgerPostings": [
    {
      "postingId": "PAY-20260921-000318-1",
      "glCode": "50110",
      "glName": "Facility maintenance expense",
      "entryType": "DEBIT",
      "amount": "2450000.00",
      "currency": "NGN"
    },
    {
      "postingId": "PAY-20260921-000318-2",
      "glCode": "10420",
      "glName": "NIP settlement",
      "entryType": "CREDIT",
      "amount": "2450000.00",
      "currency": "NGN"
    }
  ]
}

Here isInternal is false because the money leaves the institution. The counterparty follows the same rules as any external bank payment. See External bank counterparty.

Response

A GL transaction returns the same envelope as any other. This is the response to the suspense-clearing journal above:

{
  "status": true,
  "message": "Transaction evaluated",
  "data": {
    "transaction": {
      "id": "6b4e1a72-d3f0-4c86-9a15-7e2b0c4d8f31",
      "transactionReference": "JRN-20260921-004471",
      "direction": "OUTBOUND",
      "receivedAt": "2026-09-21T16:30:00.204Z",
      "replayed": false
    },
    "decision": {
      "outcome": "PASS",
      "riskScore": 0,
      "evaluatedAt": "2026-09-21T16:30:00.221Z",
      "evaluationMs": 8.113,
      "complete": true,
      "rulesEvaluated": 6,
      "rulesetSnapshot": "2f1e8a7c6d5b4930a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e1d0c9b8",
      "gradingVersion": 3,
      "triggeredRules": []
    }
  }
}

There is no generated ID for the journal or its legs beyond transaction.id: ledgerPostings[].postingId is whatever you sent, echoed back only if you later read the transaction, not in the ingest response. rulesEvaluated is typically lower than for customer activity, because most institutions scope customer-facing rules away from internal GL postings. Status is 201 for a new journal, 200 for a replay. See Decision responses for every outcome.

Errors specific to a GL transaction

Causefieldcodemessage
A ledger leg has no glCodeledgerPostings.0requiredledgerPostings.0.glCode is required
A ledger leg has no currencyledgerPostings.0requiredledgerPostings.0.currency is required
A ledger leg's entryType is missing or not DEBIT/CREDITledgerPostings.0invalidledgerPostings.0.entryType must be DEBIT or CREDIT
A ledger leg has a key outside the eight allowed leg fields, such as narrationledgerPostings.0unknownledgerPostings.0 contains the unrecognised field narration
ledgerPostings is not an array, or has more than 64 legsledgerPostingsinvalidledgerPostings must be an array of at most 64
The INTERNAL_GL counterparty has no accountNamecounterpartyrequiredcounterparty.accountName is required for this counterparty type

accountHolder itself has no required fields for INTERNAL_GL — "accountHolder": {} always passes. See Errors for the complete catalogue.

Idempotency

A GL journal is deduplicated the same way as any other transaction: by transactionReference and direction together, including everything in ledgerPostings. Resubmitting the identical body, byte for byte, returns 200 with replayed: true and does not re-post or re-evaluate the journal — it does not create a second set of legs. Resubmitting the same reference and direction with a different body, including a different leg, is rejected with 409 IDEMPOTENCY_CONFLICT rather than silently overwriting the original journal. If your core banking retry logic cannot guarantee the exact same body on a retry after a timeout, treat a 409 here the same way you would treat a duplicate-posting risk in your own ledger: investigate before reposting under a new reference. See Idempotency in Submitting transactions.

GL account holders are not screened by name

PEP and sanction screening, when licensed and enabled, resolves a screenable name from firstName/lastName, then companyName, then accountName — see Customer account holder. None of the account holder fields on this page (glCode, glName, glCategory, branchCode, postingActor) are in that list, so an INTERNAL_GL account holder is never screened by name, with or without accountName present. Rules and blacklist matching still apply normally to glCode and any other field you send — only name-based PEP and sanction screening is out of scope for this account holder type. The INTERNAL_GL counterparty case is different: see Internal GL counterparty.