Invoice Query API
Search documents in the authenticated workspace. The same endpoint supports sales invoices, purchase invoices, and receipts in every supported country and in both sandbox and production.
Endpoint
POST /invoices/queryAuthentication
Send a bearer token or API key issued for the workspace. Never place a live key in source control.
export COMPLYANCE_API_KEY='YOUR_API_KEY'Every example below uses:
Authorization: Bearer <YOUR_API_KEY>
Content-Type: application/jsonThe workspace is derived from authentication. Documents from another workspace are never returned.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
country | string | Yes | Two-letter country code, such as MY, AE, or SA. |
type | string | Yes | sales, purchases, or receipts. |
environment | string | Yes | Target environment, normally sandbox or production. |
page | number | No | One-based page number. Defaults to 1. |
limit | number | No | Number of records per page. Defaults to 20. |
filters | array | No | Exact-match, multi-value, range, or existence filters. |
searches | array | No | Case-insensitive text searches. Every supplied search must match. |
columns | array | No | Fields to return in addition to document identifiers and type. |
sort | object | No | A field and asc or desc order. |
Basic query: sales invoices
curl --location 'http://localhost:4000/invoices/query' \
--header "Authorization: Bearer ${COMPLYANCE_API_KEY}" \
--header 'Content-Type: application/json' \
--data-raw '{
"country": "MY",
"type": "sales",
"environment": "sandbox",
"page": 1,
"limit": 20
}'Query a date range
Use getsDocument.header.issueDate for sales and purchases. The gte and lte bounds are inclusive and use YYYY-MM-DD values.
curl --location 'http://localhost:4000/invoices/query' \
--header "Authorization: Bearer ${COMPLYANCE_API_KEY}" \
--header 'Content-Type: application/json' \
--data-raw '{
"country": "MY",
"type": "sales",
"environment": "sandbox",
"page": 1,
"limit": 20,
"filters": [
{
"id": "getsDocument.header.issueDate",
"range": {
"gte": "2026-08-01",
"lte": "2026-08-31"
}
}
],
"sort": {
"field": "header.issueDate",
"order": "desc"
}
}'Filter by document type and currency
This returns Malaysian tax invoices in MYR. Omit documentType.modifiers when no modifier is required; an empty array adds no filter.
curl --location 'http://localhost:4000/invoices/query' \
--header "Authorization: Bearer ${COMPLYANCE_API_KEY}" \
--header 'Content-Type: application/json' \
--data-raw '{
"country": "MY",
"type": "sales",
"environment": "sandbox",
"filters": [
{
"id": "documentType.base",
"value": ["tax_invoice"]
},
{
"id": "getsDocument.header.currency",
"value": ["MYR"]
}
],
"sort": {
"field": "header.issueDate",
"order": "desc"
}
}'Filter by status
Use NEEDS_CORRECTION to retrieve invoices that failed validation and appear as INVALID in their status history. Include NON_COMPLIANT to also return invoices that reached a final rejected or non-compliant state.
This example demonstrates only the status filter. Add country-specific fields, date ranges, or document-type filters when your use case requires them.
curl --location 'http://localhost:4000/invoices/query' \
--header "Authorization: Bearer ${COMPLYANCE_API_KEY}" \
--header 'Content-Type: application/json' \
--data-raw '{
"country": "MY",
"type": "sales",
"environment": "sandbox",
"filters": [
{
"id": "meta.statuses.document.current",
"value": ["NEEDS_CORRECTION", "NON_COMPLIANT"]
}
]
}'Search by invoice number
Search is case-insensitive. Each object in searches is an additional condition, so use one object to search one field. Adding a seller-name search means the seller name must also contain the same term.
curl --location 'http://localhost:4000/invoices/query' \
--header "Authorization: Bearer ${COMPLYANCE_API_KEY}" \
--header 'Content-Type: application/json' \
--data-raw '{
"country": "MY",
"type": "sales",
"environment": "sandbox",
"searches": [
{
"field": "getsDocument.header.documentNumber",
"term": "INV"
}
],
"sort": {
"field": "header.issueDate",
"order": "desc"
}
}'Filter by total amount
Use a numeric range on getsDocument.totals.totalAmountIncludingTax.
curl --location 'http://localhost:4000/invoices/query' \
--header "Authorization: Bearer ${COMPLYANCE_API_KEY}" \
--header 'Content-Type: application/json' \
--data-raw '{
"country": "AE",
"type": "sales",
"environment": "production",
"filters": [
{
"id": "getsDocument.totals.totalAmountIncludingTax",
"range": {
"gte": 100,
"lte": 500
}
}
]
}'Query purchase invoices
Use purchases to query purchase invoices. Sales and purchase documents use the same nested getsDocument.* filter and search fields.
curl --location 'http://localhost:4000/invoices/query' \
--header "Authorization: Bearer ${COMPLYANCE_API_KEY}" \
--header 'Content-Type: application/json' \
--data-raw '{
"country": "MY",
"type": "purchases",
"environment": "sandbox",
"filters": [
{
"id": "getsDocument.header.issueDate",
"range": {
"gte": "2026-08-01",
"lte": "2026-08-31"
}
}
]
}'Query receipts
Receipts use flat paths without the getsDocument. prefix.
curl --location 'http://localhost:4000/invoices/query' \
--header "Authorization: Bearer ${COMPLYANCE_API_KEY}" \
--header 'Content-Type: application/json' \
--data-raw '{
"country": "SA",
"type": "receipts",
"environment": "sandbox",
"filters": [
{
"id": "header.issueDate",
"range": {
"gte": "2026-08-01",
"lte": "2026-08-31"
}
},
{
"id": "countryDocumentStatus",
"value": ["ISSUED"]
}
]
}'Select columns and paginate
When columns is supplied, the API returns those fields plus documentId and documentType. Sort fields use accessor keys such as header.issueDate, without the getsDocument. prefix.
curl --location 'http://localhost:4000/invoices/query' \
--header "Authorization: Bearer ${COMPLYANCE_API_KEY}" \
--header 'Content-Type: application/json' \
--data-raw '{
"country": "MY",
"type": "sales",
"environment": "sandbox",
"page": 2,
"limit": 50,
"columns": [
"header.documentNumber",
"header.issueDate",
"header.currency"
],
"sort": {
"field": "header.issueDate",
"order": "desc"
}
}'Supported filter and search fields
| Document type | Date, amount, and currency filter fields | Text-search fields |
|---|---|---|
sales, purchases | getsDocument.header.issueDate, getsDocument.header.currency, getsDocument.totals.totalAmountIncludingTax | getsDocument.header.documentNumber, getsDocument.parties.seller.name, getsDocument.parties.buyer.name |
receipts | header.issueDate, header.currency, totals.totalAmountIncludingTax | header.documentNumber, parties.seller.name, parties.buyer.name |
All document types also support documentId, documentType.base, documentType.modifiers, documentType.variants, meta.statuses.document.current, meta.statuses.delivery.current, meta.statuses.business.current, and meta.statuses.userfacing filters. The route rejects unsupported field names with 400 Bad Request.
Sample successful response
The API returns up to the requested limit of matching documents. This example shows three documents with different current lifecycle states. The statusHistory array can contain additional events in an actual response.
{
"status": "success",
"data": {
"documents": [
{
"documentId": "01JQEXAMPLE8YQVK1T8DD98VY6N",
"documentType": {
"base": "tax_invoice",
"modifiers": []
},
"getsDocument": {
"header": {
"documentNumber": "MY-INV-1001"
}
},
"statusHistory": [
{
"internalStatus": "INTERNAL_VALIDATION_FAILED",
"userFacingStatus": "INVALID",
"documentStatus": "ON_HOLD",
"countryDocumentStatus": "VALIDATION_FAILED",
"occurredAtUtc": "2026-08-19T09:01:25.563Z"
}
],
"meta": {
"statuses": {
"document": { "current": "NEEDS_CORRECTION" },
"delivery": { "current": "NOT_STARTED" },
"business": { "current": "NOT_STARTED" },
"internal": "NEEDS_CORRECTION"
}
}
},
{
"documentId": "01JQEXAMPLE8YQVK1T8DD98VY6P",
"documentType": {
"base": "tax_invoice",
"modifiers": []
},
"getsDocument": {
"header": {
"documentNumber": "MY-INV-1002"
}
},
"statusHistory": [
{
"internalStatus": "LHDN_VALIDATED",
"userFacingStatus": "VALID",
"documentStatus": "APPROVED",
"countryDocumentStatus": "VALIDATED",
"occurredAtUtc": "2026-08-18T08:36:44.285Z"
}
],
"meta": {
"statuses": {
"document": { "current": "COMPLIANT" },
"delivery": { "current": "NOT_STARTED" },
"business": { "current": "NOT_STARTED" },
"internal": "COMPLIANT"
}
}
},
{
"documentId": "01JQEXAMPLE8YQVK1T8DD98VY6Q",
"documentType": {
"base": "tax_invoice",
"modifiers": []
},
"getsDocument": {
"header": {
"documentNumber": "MY-INV-1003"
}
},
"statusHistory": [
{
"internalStatus": "FLOW_INITIATED",
"userFacingStatus": "DRAFT",
"documentStatus": "DRAFT",
"countryDocumentStatus": "DRAFT",
"occurredAtUtc": "2026-08-11T11:08:05.956Z"
}
],
"meta": {
"statuses": {
"document": { "current": "READY_TO_PROCESS" },
"delivery": { "current": "NOT_STARTED" },
"business": { "current": "NOT_STARTED" },
"internal": "READY_TO_PROCESS"
}
}
}
]
},
"pagination": {
"page": 1,
"perPage": 20,
"total": 663,
"totalPages": 34
}
}data.documents is the result array. An empty array with pagination.total: 0 means that no document in the authenticated workspace matches every supplied condition.
Errors
| Status | Meaning |
|---|---|
400 | Invalid JSON, unsupported filter or search field, invalid request shape, or an invalid sort order. |
401 | Missing or invalid authentication. |
403 | The request is not authorized for the workspace. |
500 | Unexpected service or storage error. |