Skip to content

Payment Voucher

A payment voucher records money paid out, normally settling one or more purchase invoices. Like every financial document it uses the shared document API, with the document type as a path segment.

Base path: https://api-etl.akaun.com/core2/tnt/dm/erp/gen-doc Path segment ({docType}): internal-payment-vouchers server_doc_type field value: INTERNAL_PAYMENT_VOUCHER

Authentication covers credentials, Data API the envelope and query parameters, and ERP Core API the shared errors and filters. The Purchase Invoice page documents the shared container and the full endpoint set; this page covers what is specific to payment vouchers.

The path segment is not the same string as the server_doc_type field. The URL takes internal-payment-vouchers; the field inside the document takes INTERNAL_PAYMENT_VOUCHER. Putting the upper-case form in the URL returns 404, which reads as “no such document” rather than “no such route”. The body varies by endpoint: query and get-by-guid return an empty body, while create and delete return {"code": "OK_NODATA", ...}, an “OK” code on a 404.

A related type, internal-reimbursement-payment-vouchers (INTERNAL_REIMBURSEMENT_PAYMENT_VOUCHER), uses exactly the same endpoints with its own permission set.

The fast path: paying one supplier invoice, in three calls under the base path.

  1. POST /internal-payment-vouchers/etl-ep as a DRAFT, with server_doc_type INTERNAL_PAYMENT_VOUCHER on the header (it is not inferred from the URL), a PNS line for what is paid and one STL_MTHD line per settlement method, each naming its cashbook_guid and the settlement-method item’s item_guid and item_code. Keep the two totals equal, allowing for amounts offset by contra, yourself: the API does not check them. The URL takes internal-payment-vouchers; the upper-case form there returns 404.
  2. POST /arap-contras/etl-ep, one contra per invoice, with guid_doc_1_hdr, guid_doc_2_hdr and amount_contra (settling invoices). Check amount_contra against the invoice’s open balance yourself; nothing caps it. The route needs its own API_TNT_DM_ERP_FI_GENERIC_DOC_ARAP_CONTRA_OWNER or _ADMIN permission, and POST /arap-contras/etl-ep/multi is not a contra endpoint.
  3. Verify the lines and contras, then PUT /internal-payment-vouchers/etl-ep/update-posting-status/{guid} with {"posting_status": "FINAL"}, then re-read the purchase invoice, with a timeout, until its amount_open_balance has dropped by the settled amount. To cancel a finalised voucher, void it with PUT /internal-payment-vouchers/etl-ep/void/{guid} and {"posting_status": "VOID"} in the body (an empty body voids nothing and still answers 200), retrying on RACE_CONDITION. Deleting a posted voucher does not reverse the settlement.

Permissions

OperationPermission
CreateTNT_API_DOC_INTERNAL_PAYMENT_VOUCHER_CREATE_TGT_GUID
ReadTNT_API_DOC_INTERNAL_PAYMENT_VOUCHER_READ_TGT_GUID
UpdateTNT_API_DOC_INTERNAL_PAYMENT_VOUCHER_UPDATE_TGT_GUID
DeleteTNT_API_DOC_INTERNAL_PAYMENT_VOUCHER_DELETE_TGT_GUID

Permission names keep the upper-case singular form. These are target-scoped against the document’s guid_comp, guid_branch, guid_store and guid_store_2. See the note on the purchase invoice page.


Endpoints

Identical to the purchase invoice endpoints with internal-payment-vouchers as {docType}. The ones you will use most:

OperationPath (relative to the base path)
CreatePOST /internal-payment-vouchers/etl-ep
Create severalPOST /internal-payment-vouchers/etl-ep/multi
UpdatePUT /internal-payment-vouchers/etl-ep
DeleteDELETE /internal-payment-vouchers/etl-ep/{guid}
List allGET /internal-payment-vouchers/etl-ep
Get oneGET /internal-payment-vouchers/etl-ep/{guid}
QueryGET /internal-payment-vouchers/etl-ep/query
Set posting statusPUT /internal-payment-vouchers/etl-ep/update-posting-status/{guid}
VoidPUT /internal-payment-vouchers/etl-ep/void/{guid}
DiscardPUT /internal-payment-vouchers/discard/etl-ep/{guid}
PurgeDELETE /internal-payment-vouchers/purge/etl-ep/{guid}
Print to PDFGET /internal-payment-vouchers/etl-ep/print-jasper-pdf/{guid}

Settlement against invoices uses a separate endpoint group, covered under Settling invoices:

OperationPath
Create a contraPOST /arap-contras/etl-ep
RecalculatePUT /arap-contras/recalculation/etl-ep
Delete a contraDELETE /arap-contras/etl-ep/{guid}
List allGET /arap-contras/etl-ep
Get oneGET /arap-contras/etl-ep/{guid}
QueryGET /arap-contras/etl-ep/query

How a payment voucher is put together

A payment voucher is not one object. It is three things, and getting this wrong is the main reason a voucher fails validation:

  1. The header, carrying the supplier, company, currency and totals.
  2. Lines, which come in two kinds: what is being paid, and how it is being paid.
  3. ARAP contra rows, which attach the voucher to the specific invoices it settles. These are created separately, through their own endpoint.

The two kinds of line

Every line carries a txn_type saying which kind it is. Both values are platform constants, not tenant configuration.

txn_typeMeaningKey fields
PNSWhat is being paid: the payable amount.amount_txn, quantity_base, item_guid, item_code, arap_pns_amount
STL_MTHDHow it is being paid: one row per settlement method.amount_txn, cashbook_guid, settlement_type, item_guid, item_code

Line signums are set for you. On a payment voucher the server assigns amount_signum of 1 and quantity_signum of 0 to every line, on create and on update, so you can leave both out of your payload. server_doc_type is likewise inherited from the header.

Existing vouchers may show different line signums, so build from the values above rather than copying an older record.

cashbook_guid lives on the STL_MTHD line, not on the header. That is how a voucher says which cashbook the money leaves from. A voucher paid half by bank transfer and half in cash has two STL_MTHD lines, each naming its own cashbook.

settlement_type accepts CASH, BANK_TRANSFER, CHEQUE, E_WALLET, CREDIT_CARD, DEBIT_CARD, VOUCHER and OTHERS.

Both line kinds require item_guid and item_code. For a STL_MTHD line the item is the settlement-method item, an item record whose txn_class is STL_MTHD. Look yours up before building a voucher.

Keeping the two sides balanced

A voucher’s PNS total should equal its STL_MTHD total, allowing for any amounts offset by ARAP contra. What you are paying and what you are paying it with need to agree.

The API does not check this for you on a payment voucher, so build the balance in as you construct the document rather than relying on the create call to catch it.


The container

The GenericDocumentContainer is the same shape. What differs is which parts you populate.

bl_fi_generic_doc_hdr: payment voucher fields

FieldTypeOn createDescription
guidUUIDOptionalGenerated if omitted. Required on update.
guid_compUUIDRequiredPaying company. Also a permission target.
guid_storeUUIDRequiredStore/location. Also a permission target.
guid_branchUUIDOptionalBranch. Also a permission target.
doc_entity_hdr_guidUUIDRequiredThe supplier being paid.
doc_ccystringRecommendedPayment currency, e.g. MYR.
date_txndatetimeRecommendedPayment date. Defaults to now.
doc_referencestringOptionalCheque number, transfer reference, or similar.
doc_desc, doc_remarksstringOptionalDescription and free-text remarks.
server_doc_typestringRequiredINTERNAL_PAYMENT_VOUCHER. Required on the header; it is not inferred from the URL. Lines inherit it.
amount_txndecimalRecommendedAmount paid, as a negative number on the header (money out).
amount_netdecimalRecommendedNet amount, also negative.
amount_signumdecimalRequired1 on the payment voucher header. Validated on the header only.
amount_internal_settlementdecimalServer-maintainedAmount applied against outstanding documents.
amount_open_balancedecimalServer-maintainedRemaining unapplied amount.
arap_stlm_amount, arap_doc_open, arap_baldecimalServer-maintainedAR/AP settlement tracking.
posting_statusstringOptionalDRAFT or FINAL.
posting_cashbook, posting_journalstringServer-maintainedWhether each posting has run.
statusstringOptionalDefaults to ACTIVE.
revisionUUIDOptionalGenerated if omitted. Send the value you read back when updating.
client_key, client_sourcestringOptionalYour system’s identifiers.
client_doc_1 … client_doc_5stringOptionalWhatever your own system calls this voucher — the generic document.
property_jsonobjectOptionalArbitrary extra properties.
server_doc_1 … server_doc_5stringServer-assignedRunning numbers BigLedger assigns: tenant, company, branch. See create for when each arrives.
pin_code, pin_type, pin_hash, pin_algorithmstringServer-assignedGenerated on create when no PIN is supplied. Do not set these.

bl_fi_generic_doc_line

Present on every line of both kinds: guid, generic_doc_hdr_guid, guid_comp, guid_store, txn_type, item_guid, item_code, item_name, amount_txn, amount_net, amount_signum, quantity_signum, status, revision, plus the server-assigned audit columns.

Additionally on STL_MTHD lines: cashbook_guid (always), settlement_type, arap_stlm_amount, arap_pns_amount, unit_price_net, unit_price_txn.

Additionally on PNS lines: quantity_base, amount_std, amount_discount, arap_pns_amount, arap_stlm_amount, item_sub_type, item_txn_type, guid_branch, and optionally guid_glcode.


Settling invoices

Attaching a voucher to the invoices it pays is a separate call against the ARAP contra endpoint. It is not part of the voucher body.

Base path: https://api-etl.akaun.com/core2/tnt/dm/erp/gen-doc/arap-contras

{ "bl_fi_generic_doc_arap_contra": { } }
FieldTypeDescription
guidUUIDGenerated if omitted.
guid_doc_1_hdrUUIDRequired. One of the two documents.
server_doc_type_doc_1stringIts server_doc_type, upper-case form. Recommended.
guid_doc_2_hdrUUIDRequired. The other document.
server_doc_type_doc_2stringIts server_doc_type, upper-case form. Recommended.
amount_contradecimalRequired. Amount applied between the two.
date_txn, date_doc_1, date_doc_2datetimeOptional dates.
statusstringDefaults to ACTIVE.
revisionUUIDGenerated if omitted.
posting_status, posting_journal, posting_arapstringPosting state.
forex_doc_hdr_guid, forex_gain_loss_baseUUID / decimalForeign-exchange gain or loss, where relevant.
doc_remarksstringOptional remarks.
client_source, gen_doc_client_key_1, gen_doc_client_value_1, gen_doc_client_key_2, gen_doc_client_value_2stringYour own correlation identifiers.

The pairing is not order-sensitive: both INTERNAL_PURCHASE_INVOICE as doc 1 and as doc 2 occur in live data.

Post contras one at a time. Bulk contra creation is available to the back-office applet through POST /arap-contras/multi/backoffice-ep, but there is no /etl-ep equivalent yet.

POST /arap-contras/etl-ep/multi is not a contra endpoint. It matches the generic document route and expects a list of GenericDocumentContainer, so use the single-contra POST /arap-contras/etl-ep instead.


Document lifecycle

Same as a purchase invoice:

ValueMeaning
DRAFTCreated but not posted. Freely editable.
FINALFinal and numbered. Finalise queues background jobs for the cashbook, journal and settlement entries and does not wait for them, so a 200 does not mean they are written yet (Finalize).

Create as DRAFT, verify the lines and contras, then move to FINAL. A create sent as FINAL stores FINAL without Finalise’s checks or its posting chain, so it writes no journal; only the e-invoice job is queued for it. To cancel a finalized voucher, void it: that reverses the settlement and puts the balance back on the invoice. Deleting a posted voucher does not.


Create a payment voucher

POST /core2/tnt/dm/erp/gen-doc/internal-payment-vouchers/etl-ep

The example below pays the RM 600.00 purchase invoice from the purchase invoice page in full, by bank transfer from a single cashbook.

curl -X POST "https://api-etl.akaun.com/core2/tnt/dm/erp/gen-doc/internal-payment-vouchers/etl-ep" \
  -H "AccessId: YOUR_ACCESS_ID" \
  -H "AccessKey: YOUR_ACCESS_KEY" \
  -H "tenantCode: YOUR_TENANT_CODE" \
  -H "Content-Type: application/json" \
  -d '{
    "bl_fi_generic_doc_hdr": {
      "server_doc_type": "INTERNAL_PAYMENT_VOUCHER",
      "guid_comp": "3f2a7c10-9b4e-4d61-8a22-1c5e7f9d0b33",
      "guid_store": "6b8d1f42-7e35-4c09-ad81-1d4e8b2c5a70",
      "doc_entity_hdr_guid": "e7b2d48f-1c93-4a05-8e67-4f9a3b1d5c26",
      "doc_source_type": "INTERNAL",
      "doc_ccy": "MYR",
      "date_txn": "2026-10-28T00:00:00+08:00",
      "doc_reference": "TRF-20261028-0031",
      "doc_desc": "Payment for supplier invoice INV-ACW-2611",
      "amount_net": -600.00,
      "amount_txn": -600.00,
      "amount_signum": 1,
      "posting_status": "DRAFT",
      "status": "ACTIVE"
    },
    "bl_fi_generic_doc_line": [
      {
        "txn_type": "PNS",
        "item_guid": "2d7f9b04-6c31-4a85-9e12-8b3d5f7a1c60",
        "item_code": "AP-SETTLE",
        "item_name": "Accounts payable settlement",
        "quantity_base": 1,
        "amount_std": 600.00,
        "amount_net": 600.00,
        "amount_txn": 600.00
      },
      {
        "txn_type": "STL_MTHD",
        "item_guid": "5a1c8e36-9d24-4f70-b83e-2c6f4a9b7d51",
        "item_code": "STL-BANK",
        "item_name": "Bank transfer",
        "settlement_type": "BANK_TRANSFER",
        "cashbook_guid": "1a7e9c53-6b28-4d90-8f41-5c3b7a2e6d19",
        "amount_net": 600.00,
        "amount_txn": 600.00
      }
    ],
    "bl_fi_generic_doc_ext": [],
    "bl_fi_generic_doc_link": [],
    "bl_fi_generic_doc_event": []
  }'

The answer is the voucher as created, in the generic document envelope. Shortened:

{
  "code": "OK_RESPONSE",
  "data": {
    "bl_fi_generic_doc_hdr": {
      "guid": "7e3c9a52-4b18-4f70-8d26-5a1f8c3b6e94",
      "server_doc_type": "INTERNAL_PAYMENT_VOUCHER",
      "doc_entity_hdr_guid": "e7b2d48f-1c93-4a05-8e67-4f9a3b1d5c26",
      "amount_txn": -600.0,
      "posting_status": "DRAFT",
      "revision": "3d8f2a61-5c47-4e09-b8a3-9f1c6e4d2b75"
    },
    "bl_fi_generic_doc_line": [
      { "txn_type": "PNS", "item_code": "AP-SETTLE", "amount_txn": 600.0 },
      { "txn_type": "STL_MTHD", "item_code": "STL-BANK", "cashbook_guid": "1a7e9c53-6b28-4d90-8f41-5c3b7a2e6d19", "amount_txn": 600.0 }
    ]
  },
  "message": ""
}

Keep the returned guid. server_doc_1 (the tenant number) shows null in this response but is written just after it: read the voucher back to see it. The company, branch and location numbers arrive when you finalise, or at create for a voucher sent already FINAL through /etl-ep (since mid-October 2026).

Then attach it to the invoice it pays:

curl -X POST "https://api-etl.akaun.com/core2/tnt/dm/erp/gen-doc/arap-contras/etl-ep" \
  -H "AccessId: YOUR_ACCESS_ID" \
  -H "AccessKey: YOUR_ACCESS_KEY" \
  -H "tenantCode: YOUR_TENANT_CODE" \
  -H "Content-Type: application/json" \
  -d '{
    "bl_fi_generic_doc_arap_contra": {
      "guid_doc_1_hdr": "4d8b2f61-3a07-4e95-b12c-7f6a9d3e5c84",
      "server_doc_type_doc_1": "INTERNAL_PURCHASE_INVOICE",
      "guid_doc_2_hdr": "7e3c9a52-4b18-4f70-8d26-5a1f8c3b6e94",
      "server_doc_type_doc_2": "INTERNAL_PAYMENT_VOUCHER",
      "amount_contra": 600.00,
      "date_txn": "2026-10-28T00:00:00+08:00",
      "status": "ACTIVE"
    }
  }'
{
  "code": "OK_RESPONSE",
  "data": {
    "bl_fi_generic_doc_arap_contra": {
      "guid": "a6c1e8f3-2b94-4d57-8e30-7f5b9d2c4a18",
      "guid_doc_1_hdr": "4d8b2f61-3a07-4e95-b12c-7f6a9d3e5c84",
      "guid_doc_2_hdr": "7e3c9a52-4b18-4f70-8d26-5a1f8c3b6e94",
      "amount_contra": 600.0,
      "status": "ACTIVE"
    }
  },
  "message": ""
}

Check amount_contra against the invoice’s open balance yourself. The create check only requires it to be present; nothing we read caps it at what the invoice still owes.

The contra route checks a permission of its own, API_TNT_DM_ERP_FI_GENERIC_DOC_ARAP_CONTRA_OWNER or _ADMIN (or the tenant owner or administrator permission): the voucher permissions above do not cover it.


Finalize a payment voucher

PUT /core2/tnt/dm/erp/gen-doc/internal-payment-vouchers/etl-ep/update-posting-status/{guid}

curl -X PUT "https://api-etl.akaun.com/core2/tnt/dm/erp/gen-doc/internal-payment-vouchers/etl-ep/update-posting-status/7e3c9a52-4b18-4f70-8d26-5a1f8c3b6e94" \
  -H "AccessId: YOUR_ACCESS_ID" \
  -H "AccessKey: YOUR_ACCESS_KEY" \
  -H "tenantCode: YOUR_TENANT_CODE" \
  -H "Content-Type: application/json" \
  -d '{"posting_status": "FINAL"}'
{
  "code": "OK_RESPONSE",
  "data": {
    "bl_fi_generic_doc_hdr": {
      "guid": "7e3c9a52-4b18-4f70-8d26-5a1f8c3b6e94",
      "posting_status": "FINAL"
    }
  },
  "message": ""
}

The postings run in the background after this answers. Re-read the purchase invoice until its amount_open_balance has dropped by the settled amount, with a timeout: that is the end-to-end check that your integration is wired up correctly.


Void a payment voucher

PUT /core2/tnt/dm/erp/gen-doc/internal-payment-vouchers/etl-ep/void/{guid}

Send posting_status in the body. The void route is shared by every document type and sets VOID only when that key is present; an empty {} answers 200 OK_RESPONSE and leaves the voucher FINAL (purchase invoice). A void sent straight after finalising can also be refused with RACE_CONDITION while the postings are still writing to the document.

curl -X PUT "https://api-etl.akaun.com/core2/tnt/dm/erp/gen-doc/internal-payment-vouchers/etl-ep/void/7e3c9a52-4b18-4f70-8d26-5a1f8c3b6e94" \
  -H "AccessId: YOUR_ACCESS_ID" \
  -H "AccessKey: YOUR_ACCESS_KEY" \
  -H "tenantCode: YOUR_TENANT_CODE" \
  -H "Content-Type: application/json" \
  -d '{"posting_status": "VOID", "void_reason": "Paid twice; refund agreed"}'
{
  "code": "OK_RESPONSE",
  "data": {
    "bl_fi_generic_doc_hdr": {
      "guid": "7e3c9a52-4b18-4f70-8d26-5a1f8c3b6e94",
      "posting_status": "VOID",
      "void_reason": "Paid twice; refund agreed"
    }
  },
  "message": ""
}

Voiding reverses the settlement: the invoice’s outstanding balance goes back up.


Query payment vouchers

GET /core2/tnt/dm/erp/gen-doc/internal-payment-vouchers/etl-ep/query

Takes the same criteria as purchase invoices. For vouchers, has_line=true is the flag that matters, since the lines carry the settlement detail. Vouchers do not use bl_fi_generic_doc_link, so has_link=true adds nothing here.

curl -G "https://api-etl.akaun.com/core2/tnt/dm/erp/gen-doc/internal-payment-vouchers/etl-ep/query" \
  -H "AccessId: YOUR_ACCESS_ID" \
  -H "AccessKey: YOUR_ACCESS_KEY" \
  -H "tenantCode: YOUR_TENANT_CODE" \
  --data-urlencode "doc_entity_hdr_guid=e7b2d48f-1c93-4a05-8e67-4f9a3b1d5c26" \
  --data-urlencode "hdr_posting_status=FINAL" \
  --data-urlencode "has_line=true" \
  --data-urlencode "limit=50" \
  --data-urlencode "calcTotalRecords=true"

The paged envelope, one container per voucher, lines included:

{
  "totalRecords": 1,
  "offset": 0,
  "limit": 50,
  "code": "OK_RESPONSE",
  "message": "",
  "data": [
    {
      "bl_fi_generic_doc_hdr": {
        "guid": "7e3c9a52-4b18-4f70-8d26-5a1f8c3b6e94",
        "server_doc_type": "INTERNAL_PAYMENT_VOUCHER",
        "posting_status": "FINAL",
        "amount_txn": -600.0
      },
      "bl_fi_generic_doc_line": [
        { "txn_type": "PNS", "amount_txn": 600.0 },
        { "txn_type": "STL_MTHD", "cashbook_guid": "1a7e9c53-6b28-4d90-8f41-5c3b7a2e6d19", "amount_txn": 600.0 }
      ],
      "bl_fi_generic_doc_event": [],
      "bl_fi_generic_doc_ext": [],
      "bl_fi_generic_doc_link": [],
      "bl_fi_generic_doc_budget_sub_line": []
    }
  ]
}

To see which invoices a voucher settled, query the contras instead:

curl -G "https://api-etl.akaun.com/core2/tnt/dm/erp/gen-doc/arap-contras/etl-ep/query" \
  -H "AccessId: YOUR_ACCESS_ID" \
  -H "AccessKey: YOUR_ACCESS_KEY" \
  -H "tenantCode: YOUR_TENANT_CODE" \
  --data-urlencode "limit=50" \
  --data-urlencode "calcTotalRecords=true"

On a tenant with no settlements yet, the answer is an empty page, not an error:

{
  "totalRecords": 0,
  "offset": 0,
  "limit": 50,
  "code": "OK_RESPONSE",
  "message": "",
  "data": []
}

Related

  • Accounting API — the full generated route list for the payment and receipt vouchers and the AR/AP contras
  • Purchase Invoice — the document a voucher settles, and the shared generic-document rules
  • Cashbook — the account a STL_MTHD line points at
  • Data API — hosts, headers, paging, the response envelope and limits
Last updated on