LHDN Integration APIs
APIs for integrating with the LHDN MyInvois platform directly. These endpoints proxy or wrap the official LHDN MyInvois APIs and require a valid company with an active LHDN intermediary token configured.
Base URL: https://api-etl.akaun.com/core2/tnt/dm/erp/e-invoice/lhdn-integrations
Required Permissions: API_TNT_DM_ERP_MY_E_INVOICE_LHDN_INTEGRATION_OWNER or API_TNT_DM_ERP_MY_E_INVOICE_LHDN_INTEGRATION_ADMIN
The fast path:
- Before invoicing a buyer,
POST https://api-etl.akaun.com/core2/tnt/dm/erp/e-invoice/lhdn-integrations/tin-validation/backoffice-epwithtin,idTypeandidValue(API 1).envTypedefaults toSANDBOX: sendPRODUCTIONwhen you go live.isValid: falsecan mean a TIN and ID mismatch, not a missing TIN. - If you do not know the TIN,
POST …/tin-search/backoffice-epwithtaxpayerName, oridTypeandidValue(more fields narrow the search, never widen it);envTypeagain defaults toSANDBOX(API 2). - To see why a submitted document failed,
POST …/submission-document-details/backoffice-epwithdocumentUIdortoIrbHdrGuid(API 3).
API 1 — Validate Taxpayer’s TIN
Validates whether a given TIN and ID combination is registered and active on the LHDN MyInvois system. Use this before invoicing a buyer to confirm their tax identity.
Endpoint
POST /tin-validation/backoffice-epRequired Permission: API_TNT_DM_ERP_MY_E_INVOICE_LHDN_INTEGRATION_READ
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
tin | String | Yes | The taxpayer’s TIN (Tax Identification Number) issued by LHDN |
idType | String | Yes | Type of identification. Allowed values: NRIC, PASSPORT, BRN, ARMY |
idValue | String | Yes | The actual identification document number corresponding to idType |
envType | String | No | LHDN environment to validate against. Allowed values: SANDBOX, PRODUCTION. Default: SANDBOX |
ID Type Reference
idType | Description |
|---|---|
NRIC | Malaysian National Registration Identity Card |
PASSPORT | Passport (for non-Malaysian individuals) |
BRN | Business Registration Number (for companies) |
ARMY | Malaysian Army ID |
Example Request
{
"tin": "C12345678900",
"idType": "BRN",
"idValue": "202301012345",
"envType": "PRODUCTION"
}Response Body
| Field | Type | Description |
|---|---|---|
tin | String | The TIN that was validated |
idType | String | The ID type used in the validation |
idValue | String | The ID value used in the validation |
isValid | Boolean | true if the TIN and ID combination is valid on LHDN |
validationMsg | String | Human-readable result or error message from LHDN |
envType | String | The environment used for validation (SANDBOX or PRODUCTION) |
Example Response
{
"tin": "C12345678900",
"idType": "BRN",
"idValue": "202301012345",
"isValid": true,
"validationMsg": "TIN is valid.",
"envType": "PRODUCTION"
}Notes
- This endpoint connects to the master database (
app_cfg_main) to retrieve LHDN credentials, not the tenant database. - Always validate against
PRODUCTIONwhen going live.SANDBOXis for testing only. - A result of
isValid: falsedoes not necessarily mean the TIN does not exist — LHDN may return a mismatch between TIN and ID.
API 2 — Search Taxpayer’s TIN
Searches for a taxpayer’s TIN on LHDN MyInvois using their name, ID type/value, or both. Use this when you know partial identity information and need to discover the TIN.
Endpoint
POST /tin-search/backoffice-epRequired Permission: API_TNT_DM_ERP_MY_E_INVOICE_LHDN_INTEGRATION_READ
Request Body
At least one of taxpayerName or the idType + idValue pair must be provided.
| Field | Type | Required | Description |
|---|---|---|---|
taxpayerName | String | Conditional | Full or partial taxpayer name. Required if idType and idValue are not provided |
idType | String | Conditional | Type of identification: NRIC, PASSPORT, BRN, ARMY. Required if idValue is provided |
idValue | String | Conditional | The identification document number. Required if idType is provided |
fileType | String | No | Differentiates individual vs non-individual taxpayers. 1 = individual (IG TIN), 2 = non-individual (non-IG TIN) |
envType | String | No | LHDN environment to search. Allowed values: SANDBOX, PRODUCTION. Default: SANDBOX |
Supported Search Combinations
| Combination | Fields Required |
|---|---|
| By name only | taxpayerName |
| By name + ID | taxpayerName, idType, idValue |
| By ID only | idType, idValue |
| By ID + file type | idType, idValue, fileType |
| All fields | taxpayerName, idType, idValue, fileType |
Example Request
{
"taxpayerName": "Acme Sdn Bhd",
"idType": "BRN",
"idValue": "202301012345",
"envType": "PRODUCTION"
}Response Body
| Field | Type | Description |
|---|---|---|
tin | String | The TIN found for the given search criteria. Empty if not found |
success | Boolean | true if a TIN was found successfully |
errorMessage | String | Error description if success is false |
statusCode | Integer | HTTP status code from the LHDN API (e.g. 200, 400, 404) |
Example Response (Success)
{
"tin": "C12345678900",
"success": true,
"errorMessage": null,
"statusCode": 200
}Example Response (Not Found)
{
"tin": null,
"success": false,
"errorMessage": "No taxpayer found matching the given criteria.",
"statusCode": 404
}Notes
- This endpoint also uses master database credentials, not tenant-specific credentials.
- When
fileTypeis omitted, LHDN searches across both individual and non-individual taxpayers. - All search combinations use AND logic — additional fields narrow the results.
API 3 — Get Submission / Document Details
Retrieves the detailed status and validation results of a previously submitted e-invoice document from LHDN. Useful for checking why a document failed validation or confirming its final LHDN status.
Endpoint
POST /submission-document-details/backoffice-epRequired Permission: API_TNT_DM_ERP_MY_E_INVOICE_LHDN_INTEGRATION_READ
Request Body
Provide either documentUId or toIrbHdrGuid (or both). At least one is required to identify the document.
| Field | Type | Required | Description |
|---|---|---|---|
documentUId | String | Conditional | The LHDN document UUID (also called longId) assigned at submission |
toIrbHdrGuid | UUID | Conditional | Internal BigLedger to_irb header record GUID for the submitted document |
Example Request
{
"documentUId": "F9D425P6DS7D8IU",
"toIrbHdrGuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}Response Body
The response wraps either a detailsResponse (on success) or an error (on failure).
detailsResponse Object
| Field | Type | Description |
|---|---|---|
uuid | String | LHDN UUID of the document |
submissionUid | String | Submission batch UID |
longId | String | LHDN long ID (used in QR code / MyInvois portal link) |
typeName | String | E-invoice type name (e.g. Invoice, Credit Note) |
typeVersionName | String | E-invoice format version |
issuerTin | String | TIN of the supplier/issuer |
issuerName | String | Name of the issuer |
receiverId | String | Buyer/receiver identification number |
receiverName | String | Buyer/receiver company name |
dateTimeIssued | String | ISO 8601 date/time when the invoice was issued |
dateTimeReceived | String | ISO 8601 date/time when LHDN received the document |
dateTimeValidated | String | ISO 8601 date/time when LHDN validated the document |
totalExcludingTax | Decimal | Total amount before tax |
totalDiscount | Decimal | Total discount amount |
totalNetAmount | Decimal | Net total after discount |
totalPayableAmount | Decimal | Final payable amount including tax |
status | String | Current LHDN document status (e.g. Valid, Invalid, Cancelled) |
documentStatusReason | String | Reason for status (e.g. rejection or cancellation reason) |
cancelDateTime | String | ISO 8601 cancellation datetime, if applicable |
rejectRequestDateTime | String | ISO 8601 rejection request datetime, if applicable |
validationResults | Object | Detailed validation result object from LHDN (includes error codes and messages) |
internalId | String | Supplier’s internal document reference number |
createdByUserId | String | LHDN user ID that submitted the document |
error Object
| Field | Type | Description |
|---|---|---|
error | Object | Present when LHDN returned an error. Contains error description and LHDN status code |
Example Response (Success)
{
"detailsResponse": {
"uuid": "F9D425P6DS7D8IU",
"submissionUid": "SUBM-0001",
"longId": "XXXXXXXXXXXXXXXXXXXX",
"typeName": "Invoice",
"typeVersionName": "1.0",
"issuerTin": "C12345678900",
"issuerName": "My Company Sdn Bhd",
"receiverId": "202301012345",
"receiverName": "Acme Buyer Sdn Bhd",
"dateTimeIssued": "2024-01-15T08:00:00Z",
"dateTimeReceived": "2024-01-15T08:01:00Z",
"dateTimeValidated": "2024-01-15T08:02:00Z",
"totalExcludingTax": 1000.00,
"totalDiscount": 50.00,
"totalNetAmount": 950.00,
"totalPayableAmount": 1008.00,
"status": "Valid",
"documentStatusReason": null,
"cancelDateTime": null,
"rejectRequestDateTime": null,
"internalId": "INV-2024-001",
"validationResults": null
},
"error": null,
"envType": "PRODUCTION"
}API 4 — Search Documents on LHDN
Queries e-invoice documents directly from LHDN MyInvois for the specified company. Returns a paginated list of documents matching the filter criteria.
Endpoint
POST /documents/{companyGuid}/backoffice-ep/query
Required Permission: API_TNT_DM_ERP_MY_E_INVOICE_LHDN_INTEGRATION_READ
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
companyGuid | UUID | Yes | The GUID of the company (bl_fi_mst_comp) whose LHDN intermediary token will be used to query documents |
Query Parameters
The search criteria travel in the query string, not in a request body. The handler binds them without
@RequestBody, so Spring fills them from query parameters and ignores any JSON body you send: a filter put
in the body is silently dropped, and LHDN is searched as though you had sent no filter at all. The mapping still declares
consumes = application/json, so send the Content-Type: application/json header (an empty body is fine).
Every parameter is optional. BigLedger forwards only the ones you send to LHDN’s document search and adds no defaults of its own, so an omitted page number or page size is whatever LHDN applies.
| Parameter | Type | Description |
|---|---|---|
uuid | String | Filter by a specific LHDN document UUID |
submissionDateFrom | Instant | Start of the submission date range, in UTC, e.g. 2024-01-01T00:00:00Z |
submissionDateTo | Instant | End of the submission date range, in UTC |
issueDateFrom | Instant | Start of the issue date range, in UTC |
issueDateTo | Instant | End of the issue date range, in UTC |
invoiceDirection | String | Filter by direction: Sent (outgoing) or Received (incoming) |
status | String | Filter by LHDN document status (e.g. Valid, Invalid, Cancelled, Submitted) |
documentType | String | Filter by document type (e.g. 01 for Invoice, 02 for Credit Note) |
searchQuery | String | Free-text search against the document |
pageNo | Integer | Page number, passed to LHDN as sent |
pageSize | Integer | Results per page, passed to LHDN as sent |
Write the dates in UTC with a trailing Z, as in the example. A local time with an offset such as +08:00
puts a + in the query string, where an unencoded + reads as a space; convert to UTC instead.
Example Request
POST /documents/3fa85f64-5717-4562-b3fc-2c963f66afa6/backoffice-ep/query?submissionDateFrom=2024-01-01T00:00:00Z&submissionDateTo=2024-01-31T23:59:59Z&invoiceDirection=Sent&status=Valid&pageNo=1&pageSize=25
Content-Type: application/jsonResponse Body
| Field | Type | Description |
|---|---|---|
result | Array | List of document objects (see fields below) |
metadata.totalPages | Integer | Total number of pages available |
metadata.totalCount | Integer | Total number of matching documents |
error | Object | Present if LHDN returned an error |
Each item in result
| Field | Type | Description |
|---|---|---|
uuid | String | LHDN document UUID |
submissionUid | String | Batch submission UID |
longId | String | LHDN long ID |
internalId | String | Supplier’s internal reference number |
typeName | String | Document type name (e.g. Invoice) |
typeVersionName | String | Format version |
supplierTIN | String | Supplier TIN |
supplierName | String | Supplier company name |
buyerTIN | String | Buyer TIN |
buyerName | String | Buyer company name |
issuerTIN | String | Issuer TIN |
issuerID | String | Issuer identification number |
issuerIDType | String | Issuer ID type |
receiverTIN | String | Receiver TIN |
receiverID | String | Receiver identification number |
receiverName | String | Receiver name |
receiverIDType | String | Receiver ID type |
dateTimeIssued | String | ISO 8601 issue datetime |
dateTimeReceived | String | ISO 8601 received by LHDN datetime |
dateTimeValidated | String | ISO 8601 validated datetime |
status | String | Current LHDN status |
documentStatusReason | String | Reason for status change |
cancelDateTime | String | Cancellation datetime (if applicable) |
rejectRequestDateTime | String | Rejection request datetime (if applicable) |
submissionChannel | String | How the document was submitted (e.g. API) |
intermediaryName | String | Intermediary company name |
intermediaryTIN | String | Intermediary TIN |
intermediaryROB | String | Intermediary registration number |
createdByUserId | String | LHDN user ID that submitted the document |
documentCurrency | String | Currency code (e.g. MYR) |
totalExcludingTax | Decimal | Total before tax |
totalDiscount | Decimal | Total discounts |
totalNetAmount | Decimal | Net total |
totalPayableAmount | Decimal | Final payable amount |
Example Response
{
"result": [
{
"uuid": "F9D425P6DS7D8IU",
"submissionUid": "SUBM-0001",
"longId": "XXXXXXXXXXXXXXXXXXXX",
"internalId": "INV-2024-001",
"typeName": "Invoice",
"typeVersionName": "1.0",
"supplierTIN": "C12345678900",
"supplierName": "My Company Sdn Bhd",
"buyerTIN": "C98765432100",
"buyerName": "Acme Buyer Sdn Bhd",
"dateTimeIssued": "2024-01-15T08:00:00Z",
"dateTimeReceived": "2024-01-15T08:01:00Z",
"dateTimeValidated": "2024-01-15T08:02:00Z",
"status": "Valid",
"documentCurrency": "MYR",
"totalExcludingTax": 1000.00,
"totalDiscount": 50.00,
"totalNetAmount": 950.00,
"totalPayableAmount": 1008.00
}
],
"metadata": {
"totalPages": 5,
"totalCount": 112
},
"error": null
}Notes
- The
companyGuidin the path determines which LHDN intermediary token is used for authentication. - Documents returned are those associated with the company’s LHDN account (either as supplier or receiver, depending on
invoiceDirection). - The same query-string criteria drive
POST /documents/refresh-to-irb/{companyGuid}/backoffice-ep/query, which runs this search and then writes the status LHDN reports onto each of your e-invoice records whose LHDN document UUID is in the result. It needsAPI_TNT_DM_ERP_MY_E_INVOICE_LHDN_INTEGRATION_UPDATE, notREAD.
API 5 — Read a taxpayer from an e-invoice QR code
POST /core2/tnt/dm/erp/e-invoice/lhdn-integrations/retrieve-tax-payer-by-qr-code/etl-ep
Controller: MyEInvoiceLHDNIntegrationController · Required Permission:
API_TNT_DM_ERP_MY_E_INVOICE_LHDN_INTEGRATION_READ
This endpoint is live, not planned. It takes a picture of a taxpayer’s LHDN QR code, not the
decoded text, as a multipart upload in the part imageFile. BigLedger reads the QR code out of the
image, asks LHDN MyInvois who the taxpayer is, and returns what LHDN answers: TIN, name, identity,
SST number, contact and address. The keys are LHDN’s, flat (tin, name, contactNumber,
addressLine0), so map them yourself before filling einvoice_buyer_entity_hdr_json or
einvoice_supplier_entity_hdr_json, whose fields are named differently.
/{envType}
segment defaults to SANDBOX. For a real customer’s details call
.../retrieve-tax-payer-by-qr-code/etl-ep/PRODUCTION.curl -X POST "https://api-etl.akaun.com/core2/tnt/dm/erp/e-invoice/lhdn-integrations/retrieve-tax-payer-by-qr-code/etl-ep/PRODUCTION" \
-H "AccessId: YOUR_ACCESS_ID" \
-H "AccessKey: YOUR_ACCESS_KEY" \
-H "tenantCode: YOUR_TENANT_CODE" \
-F "imageFile=@customer-qr.png"{
"code": "OK_RESPONSE",
"data": {
"tin": "C2584563200",
"name": "Syarikat Contoh Teknologi Sdn Bhd",
"idType": "BRN",
"idNumber": "202001234567",
"email": "accounts@contohtek.example",
"contactNumber": "+60312345678",
"addressLine0": "Lot 12, Jalan Contoh 3",
"postalZone": "47301",
"city": "Petaling Jaya",
"generatedTimestamp": "2026-10-21T09:15:02Z"
},
"message": ""
}An unreadable image answers with the error “Unable to read the uploaded image file”. A picture
without a readable QR code, or a refusal from LHDN, also answers with an error, whose wording varies.
Sent as JSON instead of multipart, the call is refused before it reaches LHDN with 417 and
“Content-Type ‘application/json’ is not supported”.