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.
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.
POST /internal-payment-vouchers/etl-epas aDRAFT, withserver_doc_typeINTERNAL_PAYMENT_VOUCHERon the header (it is not inferred from the URL), aPNSline for what is paid and oneSTL_MTHDline per settlement method, each naming itscashbook_guidand the settlement-method item’sitem_guidanditem_code. Keep the two totals equal, allowing for amounts offset by contra, yourself: the API does not check them. The URL takesinternal-payment-vouchers; the upper-case form there returns404.POST /arap-contras/etl-ep, one contra per invoice, withguid_doc_1_hdr,guid_doc_2_hdrandamount_contra(settling invoices). Checkamount_contraagainst the invoice’s open balance yourself; nothing caps it. The route needs its ownAPI_TNT_DM_ERP_FI_GENERIC_DOC_ARAP_CONTRA_OWNERor_ADMINpermission, andPOST /arap-contras/etl-ep/multiis not a contra endpoint.- 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 itsamount_open_balancehas dropped by the settled amount. To cancel a finalised voucher, void it withPUT /internal-payment-vouchers/etl-ep/void/{guid}and{"posting_status": "VOID"}in the body (an empty body voids nothing and still answers200), retrying onRACE_CONDITION. Deleting a posted voucher does not reverse the settlement.
Permissions
| Operation | Permission |
|---|---|
| Create | TNT_API_DOC_INTERNAL_PAYMENT_VOUCHER_CREATE_TGT_GUID |
| Read | TNT_API_DOC_INTERNAL_PAYMENT_VOUCHER_READ_TGT_GUID |
| Update | TNT_API_DOC_INTERNAL_PAYMENT_VOUCHER_UPDATE_TGT_GUID |
| Delete | TNT_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:
| Operation | Path (relative to the base path) |
|---|---|
| Create | POST /internal-payment-vouchers/etl-ep |
| Create several | POST /internal-payment-vouchers/etl-ep/multi |
| Update | PUT /internal-payment-vouchers/etl-ep |
| Delete | DELETE /internal-payment-vouchers/etl-ep/{guid} |
| List all | GET /internal-payment-vouchers/etl-ep |
| Get one | GET /internal-payment-vouchers/etl-ep/{guid} |
| Query | GET /internal-payment-vouchers/etl-ep/query |
| Set posting status | PUT /internal-payment-vouchers/etl-ep/update-posting-status/{guid} |
| Void | PUT /internal-payment-vouchers/etl-ep/void/{guid} |
| Discard | PUT /internal-payment-vouchers/discard/etl-ep/{guid} |
| Purge | DELETE /internal-payment-vouchers/purge/etl-ep/{guid} |
| Print to PDF | GET /internal-payment-vouchers/etl-ep/print-jasper-pdf/{guid} |
Settlement against invoices uses a separate endpoint group, covered under Settling invoices:
| Operation | Path |
|---|---|
| Create a contra | POST /arap-contras/etl-ep |
| Recalculate | PUT /arap-contras/recalculation/etl-ep |
| Delete a contra | DELETE /arap-contras/etl-ep/{guid} |
| List all | GET /arap-contras/etl-ep |
| Get one | GET /arap-contras/etl-ep/{guid} |
| Query | GET /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:
- The header, carrying the supplier, company, currency and totals.
- Lines, which come in two kinds: what is being paid, and how it is being paid.
- 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_type | Meaning | Key fields |
|---|---|---|
PNS | What is being paid: the payable amount. | amount_txn, quantity_base, item_guid, item_code, arap_pns_amount |
STL_MTHD | How 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
| Field | Type | On create | Description |
|---|---|---|---|
guid | UUID | Optional | Generated if omitted. Required on update. |
guid_comp | UUID | Required | Paying company. Also a permission target. |
guid_store | UUID | Required | Store/location. Also a permission target. |
guid_branch | UUID | Optional | Branch. Also a permission target. |
doc_entity_hdr_guid | UUID | Required | The supplier being paid. |
doc_ccy | string | Recommended | Payment currency, e.g. MYR. |
date_txn | datetime | Recommended | Payment date. Defaults to now. |
doc_reference | string | Optional | Cheque number, transfer reference, or similar. |
doc_desc, doc_remarks | string | Optional | Description and free-text remarks. |
server_doc_type | string | Required | INTERNAL_PAYMENT_VOUCHER. Required on the header; it is not inferred from the URL. Lines inherit it. |
amount_txn | decimal | Recommended | Amount paid, as a negative number on the header (money out). |
amount_net | decimal | Recommended | Net amount, also negative. |
amount_signum | decimal | Required | 1 on the payment voucher header. Validated on the header only. |
amount_internal_settlement | decimal | Server-maintained | Amount applied against outstanding documents. |
amount_open_balance | decimal | Server-maintained | Remaining unapplied amount. |
arap_stlm_amount, arap_doc_open, arap_bal | decimal | Server-maintained | AR/AP settlement tracking. |
posting_status | string | Optional | DRAFT or FINAL. |
posting_cashbook, posting_journal | string | Server-maintained | Whether each posting has run. |
status | string | Optional | Defaults to ACTIVE. |
revision | UUID | Optional | Generated if omitted. Send the value you read back when updating. |
client_key, client_source | string | Optional | Your system’s identifiers. |
client_doc_1 … client_doc_5 | string | Optional | Whatever your own system calls this voucher — the generic document. |
property_json | object | Optional | Arbitrary extra properties. |
server_doc_1 … server_doc_5 | string | Server-assigned | Running numbers BigLedger assigns: tenant, company, branch. See create for when each arrives. |
pin_code, pin_type, pin_hash, pin_algorithm | string | Server-assigned | Generated 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": { } }| Field | Type | Description |
|---|---|---|
guid | UUID | Generated if omitted. |
guid_doc_1_hdr | UUID | Required. One of the two documents. |
server_doc_type_doc_1 | string | Its server_doc_type, upper-case form. Recommended. |
guid_doc_2_hdr | UUID | Required. The other document. |
server_doc_type_doc_2 | string | Its server_doc_type, upper-case form. Recommended. |
amount_contra | decimal | Required. Amount applied between the two. |
date_txn, date_doc_1, date_doc_2 | datetime | Optional dates. |
status | string | Defaults to ACTIVE. |
revision | UUID | Generated if omitted. |
posting_status, posting_journal, posting_arap | string | Posting state. |
forex_doc_hdr_guid, forex_gain_loss_base | UUID / decimal | Foreign-exchange gain or loss, where relevant. |
doc_remarks | string | Optional remarks. |
client_source, gen_doc_client_key_1, gen_doc_client_value_1, gen_doc_client_key_2, gen_doc_client_value_2 | string | Your 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:
| Value | Meaning |
|---|---|
DRAFT | Created but not posted. Freely editable. |
FINAL | Final 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_MTHDline points at - Data API — hosts, headers, paging, the response envelope and limits